Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions mqlaunch/commands/recommendations/recommendations-doctor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Health check for the recommendations consumer. Read-only — opens nothing.
# Non-zero exit on any hard problem.
# recommendations-doctor.sh
set -euo pipefail

LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../lib/recommendations" && pwd)"
source "$LIB/errors.sh"
source "$LIB/resolve.sh"
source "$LIB/parse.sh"
source "$LIB/doctor.sh"

run_recommendations_doctor
19 changes: 19 additions & 0 deletions mqlaunch/commands/recommendations/recommendations-list.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# List recommended command patterns (default-visible, by rank). Read-only.
# recommendations-list.sh
set -euo pipefail

LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../lib/recommendations" && pwd)"
source "$LIB/errors.sh"
source "$LIB/resolve.sh"
source "$LIB/parse.sh"
source "$LIB/render.sh"

path="$(assert_recommended_json)" || exit 1

if [[ "$(rec_visible_count "$path")" -eq 0 ]]; then
render_empty_recommendations_state "$path"
exit 0
fi

render_recommendations_list "$path"
26 changes: 26 additions & 0 deletions mqlaunch/commands/recommendations/recommendations-show.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Show full detail for one recommended pattern. Read-only — renders the command
# template for show/copy; never executes it.
# recommendations-show.sh <pattern_id>
set -euo pipefail

LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../lib/recommendations" && pwd)"
source "$LIB/errors.sh"
source "$LIB/resolve.sh"
source "$LIB/parse.sh"
source "$LIB/render.sh"

if [[ $# -ne 1 ]]; then
rec_error "usage: recommendations-show.sh <pattern_id>"
exit 2
fi

path="$(assert_recommended_json)" || exit 1

if ! rec_pattern_exists "$path" "$1"; then
rec_error "pattern_id not present in recommended.json: $1"
rec_info "list valid ids with: recommendations-list.sh"
exit 2
fi

render_recommendation_detail "$path" "$1"
28 changes: 28 additions & 0 deletions mqlaunch/lib/recommendations/doctor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Health check for the recommendations consumer. Read-only — opens/executes
# nothing, never writes. Non-zero exit on any hard problem (missing/unreadable/
# invalid/wrong-schema source). Depends on: errors.sh, resolve.sh, parse.sh.

run_recommendations_doctor() {
local path
if ! path="$(assert_recommended_json)"; then
return 1 # assert_* already emitted the precise reason
fi
rec_ok "recommended.json found: $path"
rec_ok "schema: $(jq -r '.schema' "$path") (generated_at: $(jq -r '.generated_at // "?"' "$path"))"
rec_ok "allowed actions: $(rec_allowed_actions "$path")"

local total visible hidden
total="$(rec_total_count "$path")"
visible="$(rec_visible_count "$path")"
hidden=$(( total - visible ))
rec_ok "$total patterns total"
rec_ok "$visible visible (risk: $(rec_default_visible_risk "$path"))"
if [[ "$hidden" -gt 0 ]]; then
rec_warn "$hidden hidden (non-default risk, e.g. mutating) — opt-in only"
fi
if [[ "$visible" -eq 0 ]]; then
rec_warn "no visible recommendations — list will show the empty state"
fi
return 0
}
8 changes: 8 additions & 0 deletions mqlaunch/lib/recommendations/errors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# recommendations consumer — consistent messaging to stderr. Read-only.
# Mirrors lib/mqobsidian/errors.sh so the two consumers feel the same.

rec_error() { printf '\033[0;31m[recommend][error]\033[0m %s\n' "$*" >&2; }
rec_warn() { printf '\033[0;33m[recommend][warn]\033[0m %s\n' "$*" >&2; }
rec_info() { printf '\033[0;37m[recommend]\033[0m %s\n' "$*" >&2; }
rec_ok() { printf '\033[0;32m[recommend][ok]\033[0m %s\n' "$*" >&2; }
48 changes: 48 additions & 0 deletions mqlaunch/lib/recommendations/parse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Read-only accessors over recommended.json. All queries go through jq — we do
# NOT re-implement ranking or scoring here; the producer already baked rank,
# score, signals, and ordered slices into the file. We only read them.
#
# Visibility honors the file's own contract: only patterns whose risk_class is
# in .default_visible_risk are listed by default (mutating patterns are hidden,
# exactly as the producer intends). Depends on: errors.sh.

# Echo default-visible pattern ids, ordered by the producer's rank.
rec_visible_ids() {
local path="$1"
jq -r '
.default_visible_risk as $vr
| [ .patterns[] | select(.risk_class as $r | $vr | index($r)) ]
| sort_by(.rank)
| .[].id
' "$path"
}

# Count of default-visible patterns.
rec_visible_count() {
local path="$1"
jq '[ .default_visible_risk as $vr | .patterns[] | select(.risk_class as $r | $vr | index($r)) ] | length' "$path"
}

# Total pattern count (including hidden / non-default risk).
rec_total_count() { jq '.patterns | length' "$1"; }

# True if a pattern_id exists in the file at all (visible or not).
rec_pattern_exists() {
local path="$1" id="$2"
jq -e --arg id "$id" 'any(.patterns[]; .id == $id)' "$path" >/dev/null 2>&1
}

# Echo a single scalar field for a pattern (empty if absent).
rec_field() {
local path="$1" id="$2" field="$3"
jq -r --arg id "$id" --arg f "$field" '
.patterns[] | select(.id == $id) | .[$f] // empty
' "$path"
}

# Echo the contract's allowed actions, comma-joined (e.g. "show, copy").
rec_allowed_actions() { jq -r '.allowed_actions | join(", ")' "$1"; }

# Echo the contract's default-visible risk classes, comma-joined.
rec_default_visible_risk() { jq -r '.default_visible_risk | join(", ")' "$1"; }
63 changes: 63 additions & 0 deletions mqlaunch/lib/recommendations/render.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Presentation for the recommendations consumer. Read-only — renders to stdout,
# opens/executes nothing. Depends on: errors.sh, parse.sh.

# Compact, rank-ordered list of default-visible patterns.
render_recommendations_list() {
local path="$1"
printf 'Recommended command patterns — %s, by rank (read-only)\n' "$(rec_default_visible_risk "$path")"
printf -- '-------------------------------------------------------------\n'
jq -r '
.default_visible_risk as $vr
| [ .patterns[] | select(.risk_class as $r | $vr | index($r)) ]
| sort_by(.rank)
| .[]
| " #\(.rank) \(.id) · score \(.score)\n \(.name)\n use when: \(.use_when)"
' "$path"

local total visible hidden
total="$(rec_total_count "$path")"
visible="$(rec_visible_count "$path")"
hidden=$(( total - visible ))
printf -- '-------------------------------------------------------------\n'
printf '%s of %s shown' "$visible" "$total"
if [[ "$hidden" -gt 0 ]]; then
printf ' · %s hidden (non-default risk, e.g. mutating)' "$hidden"
fi
printf '\n'
printf 'detail: recommendations-show.sh <pattern_id> (actions: %s)\n' "$(rec_allowed_actions "$path")"
}

# Full detail for one pattern. Renders the command_template for show/copy only —
# it is never executed by this consumer.
render_recommendation_detail() {
local path="$1" id="$2"
jq -r --arg id "$id" '
.patterns[] | select(.id == $id) |
"Pattern: \(.id)",
"Name: \(.name)",
"Risk: \(.risk_class) Scope: \(.repo_scope)",
"Rank/score: #\(.rank) · \(.score) Tags: \(.task_tags | join(", "))",
"",
.description,
"",
"Use when: \(.use_when)",
"Avoid when: \(.avoid_when)",
(if .preconditions then "Preconditions: \(.preconditions)" else empty end),
(if .recovery then "Recovery: \(.recovery)" else empty end),
"",
"Command template (show / copy only — not executed by mqlaunch):",
" \(.command_template)",
"",
"Signals: frequency=\(.signals.frequency) success=\(.signals.success // "n/a") reuse=\(.signals.reuse) prior_n=\(.signals.prior_n)"
' "$path"
}

# Deliberate empty state — distinguishes "no recommendations yet" from "broken".
render_empty_recommendations_state() {
local path="$1"
printf 'No recommended patterns are currently visible.\n'
printf 'The file resolved and parsed cleanly — there just is nothing to show.\n'
printf ' source: %s\n' "$path"
printf ' next: add observations + run build_views.py in the vault to populate it.\n'
}
65 changes: 65 additions & 0 deletions mqlaunch/lib/recommendations/resolve.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Single source of truth for locating the producer's recommended.json. Read-only.
#
# The recommendations file is PRODUCED by mqobsidian (build_views.py) and only
# CONSUMED here. We do not generate, rank, or rewrite it. To avoid path sprawl
# we reuse the vault resolver (lib/mqobsidian/resolve.sh) and append the one
# stable relative path the producer writes to.
#
# Override order:
# 1. $MQ_RECOMMENDED_JSON — explicit absolute path wins
# 2. $MQ_OBSIDIAN_DIR/<rel> — resolved via the shared vault resolver
# 3. clear error — never guess
#
# Depends on: errors.sh (rec_*), lib/mqobsidian/{errors,resolve}.sh.

_REC_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../mqobsidian/errors.sh
source "$_REC_LIB/../mqobsidian/errors.sh"
# shellcheck source=../mqobsidian/resolve.sh
source "$_REC_LIB/../mqobsidian/resolve.sh" # provides resolve_mqobsidian_dir

# The single stable output path build_views.py writes to, relative to vault root.
REC_RELATIVE_PATH="memory/commands/mqlaunch/recommended.json"
REC_SCHEMA="command-recommendations.v1"

resolve_recommended_json_path() {
if [[ -n "${MQ_RECOMMENDED_JSON:-}" ]]; then
printf '%s\n' "$MQ_RECOMMENDED_JSON"
return 0
fi
local dir
dir="$(resolve_mqobsidian_dir)"
printf '%s\n' "$dir/$REC_RELATIVE_PATH"
}

# Validate source presence, readability, JSON validity, and schema. Echoes the
# resolved path on success; non-zero with a precise error on any failure.
assert_recommended_json() {
if ! command -v jq >/dev/null 2>&1; then
rec_error "jq is required to read recommended.json but was not found on PATH"
return 1
fi
local path
path="$(resolve_recommended_json_path)"
if [[ ! -f "$path" ]]; then
rec_error "recommended.json not found: $path"
rec_info "set MQ_RECOMMENDED_JSON, or MQ_OBSIDIAN_DIR, or run build_views.py in the vault"
return 1
fi
if [[ ! -r "$path" ]]; then
rec_error "recommended.json is not readable: $path"
return 1
fi
if ! jq -e . "$path" >/dev/null 2>&1; then
rec_error "recommended.json is not valid JSON: $path"
return 1
fi
local schema
schema="$(jq -r '.schema // empty' "$path")"
if [[ "$schema" != "$REC_SCHEMA" ]]; then
rec_error "unexpected schema: ${schema:-<missing>} (expected $REC_SCHEMA): $path"
return 1
fi
printf '%s\n' "$path"
}
Loading