@thebassclef/lite 1.9.4 → 1.9.6
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/README.md +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/bassclef-orientation.md +20 -0
- package/dist/lite/.claude/hooks/session-reflection.sh +23 -0
- package/dist/lite/.claude/rules/blocked-items.md +18 -0
- package/dist/lite/.claude/skills/longrun/SKILL.md +66 -0
- package/dist/lite/.claude/skills/onboard-repo/SKILL.md +37 -4
- package/dist/lite/.claude/skills/temperance/SKILL.md +1 -3
- package/dist/lite/bassclef-version.json +5 -5
- package/dist/lite/lib/ancestor-claude-check.sh +26 -4
- package/dist/lite/lib/capability-probe.sh +11 -17
- package/dist/lite/lib/statusline-version.sh +103 -0
- package/dist/lite/presence/cli/bassclef-statusline.dispatcher.sh +26 -7
- package/dist/lite/presence/install/bassclef-sync.template.sh +43 -0
- package/dist/lite/scripts/generate-lite-manifest.sh +12 -6
- package/dist/lite/scripts/interpret-input/extract-repo.sh +239 -0
- package/dist/lite/scripts/interpret-input.sh +212 -0
- package/dist/lite/scripts/launch/local-serve-stop.sh +41 -0
- package/dist/lite/scripts/launch/local-serve.sh +122 -0
- package/dist/lite/scripts/launch/parse-input-args.sh +177 -0
- package/dist/lite/scripts/longrun-preset-detect.sh +87 -0
- package/dist/lite/standards/lite-manifest-schema-changes.md +36 -0
- package/dist/lite/standards/lite-manifest.json +71 -15
- package/dist/lite/standards/session-start-banner-discipline.md +18 -5
- package/package.json +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# scripts/interpret-input/extract-repo.sh — Phase C repo extractor.
|
|
4
|
+
#
|
|
5
|
+
# Walks a local repo directory and extracts:
|
|
6
|
+
# - README excerpt (first 30 lines of README.md or equivalent)
|
|
7
|
+
# - Title (first H1 in README, or repo dirname)
|
|
8
|
+
# - Domain entity candidates (Prisma models + directory-pattern heuristic)
|
|
9
|
+
# - Boundary objects (Next.js routes, pages, API endpoints)
|
|
10
|
+
# - Actor candidates (role/auth keywords in source)
|
|
11
|
+
#
|
|
12
|
+
# Outputs JSON to stdout. Wrapper composes into the InputArtifact.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bash scripts/interpret-input/extract-repo.sh <local-repo-path>
|
|
16
|
+
#
|
|
17
|
+
# Exit codes:
|
|
18
|
+
# 0 = success (even partial extraction is success — empty arrays for missing signals)
|
|
19
|
+
# 2 = bad input (missing path or not a directory)
|
|
20
|
+
#
|
|
21
|
+
# Pairs with: scripts/interpret-input.sh + standards/input-handler-interface.md
|
|
22
|
+
|
|
23
|
+
set -u
|
|
24
|
+
|
|
25
|
+
REPO_PATH="${1:-}"
|
|
26
|
+
|
|
27
|
+
if [ -z "$REPO_PATH" ]; then
|
|
28
|
+
echo "extract-repo: requires <repo-path>" >&2
|
|
29
|
+
exit 2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ ! -d "$REPO_PATH" ]; then
|
|
33
|
+
echo "extract-repo: $REPO_PATH is not a directory" >&2
|
|
34
|
+
exit 2
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Use absolute path so cd doesn't break relative output
|
|
38
|
+
REPO_PATH="$(cd "$REPO_PATH" && pwd)"
|
|
39
|
+
cd "$REPO_PATH" || { echo "extract-repo: cannot cd to $REPO_PATH" >&2; exit 2; }
|
|
40
|
+
|
|
41
|
+
# === README excerpt + title ===
|
|
42
|
+
README_EXCERPT=""
|
|
43
|
+
README_PATH=""
|
|
44
|
+
for candidate in README.md README.MD readme.md README.rst README.txt; do
|
|
45
|
+
if [ -f "$candidate" ]; then
|
|
46
|
+
README_PATH="$candidate"
|
|
47
|
+
README_EXCERPT=$(head -30 "$candidate")
|
|
48
|
+
break
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
TITLE=""
|
|
53
|
+
if [ -n "$README_PATH" ]; then
|
|
54
|
+
TITLE=$(grep -m1 '^# ' "$README_PATH" 2>/dev/null | sed 's/^# *//')
|
|
55
|
+
fi
|
|
56
|
+
if [ -z "$TITLE" ]; then
|
|
57
|
+
TITLE=$(basename "$REPO_PATH")
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# === Entity candidates ===
|
|
61
|
+
|
|
62
|
+
# Strategy 1: Prisma models
|
|
63
|
+
ENTITIES_PRISMA=()
|
|
64
|
+
for schema in prisma/schema.prisma prisma/*.prisma; do
|
|
65
|
+
if [ -f "$schema" ]; then
|
|
66
|
+
while IFS= read -r model_name; do
|
|
67
|
+
[ -z "$model_name" ] && continue
|
|
68
|
+
ENTITIES_PRISMA+=("$model_name")
|
|
69
|
+
done < <(grep -E '^model [A-Z][a-zA-Z]+' "$schema" 2>/dev/null | awk '{print $2}')
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Strategy 2: directory-pattern entities
|
|
74
|
+
# A directory containing >=3 files/subdirs of consistent shape suggests a collection.
|
|
75
|
+
# The singularized dirname is a candidate entity.
|
|
76
|
+
ENTITIES_DIR=()
|
|
77
|
+
|
|
78
|
+
# Collect candidate dirs at depth <=4, skipping known noise
|
|
79
|
+
CANDIDATE_DIRS=$(find . -maxdepth 4 -mindepth 1 -type d \
|
|
80
|
+
-not -path '*/node_modules*' \
|
|
81
|
+
-not -path '*/.git*' \
|
|
82
|
+
-not -path '*/.next*' \
|
|
83
|
+
-not -path '*/dist*' \
|
|
84
|
+
-not -path '*/build*' \
|
|
85
|
+
-not -path '*/coverage*' \
|
|
86
|
+
-not -path '*/__pycache__*' \
|
|
87
|
+
2>/dev/null)
|
|
88
|
+
|
|
89
|
+
while IFS= read -r dir; do
|
|
90
|
+
[ -z "$dir" ] && continue
|
|
91
|
+
DIRNAME=$(basename "$dir")
|
|
92
|
+
case "$DIRNAME" in
|
|
93
|
+
.|..|node_modules|.git|.next|dist|build|coverage|__tests__|tests|test|src|lib|components|utils|public|static|scripts|bin|hooks) continue ;;
|
|
94
|
+
esac
|
|
95
|
+
# Only consider directories whose name suggests a collection (plural-ish)
|
|
96
|
+
case "$DIRNAME" in
|
|
97
|
+
*s|*ies)
|
|
98
|
+
# Count direct children
|
|
99
|
+
COUNT=$(find "$dir" -maxdepth 1 -mindepth 1 \
|
|
100
|
+
\( -type d -o -name '*.md' -o -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.json' -o -name '*.yml' -o -name '*.yaml' \) \
|
|
101
|
+
2>/dev/null | wc -l | tr -d ' ')
|
|
102
|
+
if [ "$COUNT" -ge 3 ]; then
|
|
103
|
+
# Singularize: ies → y; ses/xes/zes/ches/shes → strip "es"; else strip "s"
|
|
104
|
+
case "$DIRNAME" in
|
|
105
|
+
*ies) SINGULAR="${DIRNAME%ies}y" ;;
|
|
106
|
+
*ses|*xes|*zes|*ches|*shes) SINGULAR="${DIRNAME%es}" ;;
|
|
107
|
+
*s) SINGULAR="${DIRNAME%s}" ;;
|
|
108
|
+
esac
|
|
109
|
+
# Skip too-short or too-noisy singulars
|
|
110
|
+
if [ "${#SINGULAR}" -ge 3 ]; then
|
|
111
|
+
ENTITIES_DIR+=("$SINGULAR")
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
done <<<"$CANDIDATE_DIRS"
|
|
117
|
+
|
|
118
|
+
# Combine + dedupe entities
|
|
119
|
+
ALL_ENTITIES=()
|
|
120
|
+
SEEN=""
|
|
121
|
+
for e in "${ENTITIES_PRISMA[@]:-}" "${ENTITIES_DIR[@]:-}"; do
|
|
122
|
+
[ -z "$e" ] && continue
|
|
123
|
+
case " $SEEN " in
|
|
124
|
+
*" $e "*) ;;
|
|
125
|
+
*) ALL_ENTITIES+=("$e"); SEEN="$SEEN $e" ;;
|
|
126
|
+
esac
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
# === Boundary objects (routes + pages + API endpoints) ===
|
|
130
|
+
BOUNDARY_OBJECTS=()
|
|
131
|
+
|
|
132
|
+
# Next.js App Router routes (app/**/page.{ts,tsx,js,jsx})
|
|
133
|
+
for app_dir in app src/app; do
|
|
134
|
+
if [ -d "$app_dir" ]; then
|
|
135
|
+
while IFS= read -r f; do
|
|
136
|
+
[ -z "$f" ] && continue
|
|
137
|
+
# Convert path to route: app/deals/page.tsx → /deals
|
|
138
|
+
ROUTE=$(echo "$f" | sed -E "s|^./${app_dir}||" | sed 's|/page\.[a-zA-Z]*$||')
|
|
139
|
+
[ -z "$ROUTE" ] && ROUTE="/"
|
|
140
|
+
BOUNDARY_OBJECTS+=("route:$ROUTE")
|
|
141
|
+
done < <(find "./$app_dir" -maxdepth 6 \( -name 'page.tsx' -o -name 'page.ts' -o -name 'page.js' -o -name 'page.jsx' \) 2>/dev/null | head -50)
|
|
142
|
+
fi
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
# Next.js Pages Router routes (pages/**/*.{ts,tsx,js,jsx})
|
|
146
|
+
for pages_dir in pages src/pages; do
|
|
147
|
+
if [ -d "$pages_dir" ]; then
|
|
148
|
+
while IFS= read -r f; do
|
|
149
|
+
[ -z "$f" ] && continue
|
|
150
|
+
ROUTE=$(echo "$f" | sed -E "s|^./${pages_dir}||" | sed 's/\.[a-zA-Z]*$//')
|
|
151
|
+
[ -z "$ROUTE" ] && ROUTE="/"
|
|
152
|
+
BOUNDARY_OBJECTS+=("page:$ROUTE")
|
|
153
|
+
done < <(find "./$pages_dir" -maxdepth 6 -type f \( -name '*.tsx' -o -name '*.ts' -o -name '*.js' -o -name '*.jsx' \) -not -name '_*' -not -name 'api' 2>/dev/null | head -30)
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
# API routes
|
|
158
|
+
for api_dir in app/api src/app/api pages/api src/pages/api; do
|
|
159
|
+
if [ -d "$api_dir" ]; then
|
|
160
|
+
while IFS= read -r f; do
|
|
161
|
+
[ -z "$f" ] && continue
|
|
162
|
+
ROUTE=$(echo "$f" | sed -E "s|^./${api_dir}||" | sed 's|/route\.[a-zA-Z]*$||' | sed 's/\.[a-zA-Z]*$//')
|
|
163
|
+
[ -z "$ROUTE" ] && ROUTE="/"
|
|
164
|
+
BOUNDARY_OBJECTS+=("api:$ROUTE")
|
|
165
|
+
done < <(find "./$api_dir" -maxdepth 6 -type f \( -name 'route.ts' -o -name 'route.tsx' -o -name 'route.js' -o -name '*.ts' \) 2>/dev/null | head -20)
|
|
166
|
+
fi
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
# === Actor candidates ===
|
|
170
|
+
# Heuristic: grep role/auth keywords in source code
|
|
171
|
+
ACTOR_CANDIDATES=()
|
|
172
|
+
ACTOR_RAW=$(grep -rEho \
|
|
173
|
+
'\b(admin|owner|operator|viewer|editor|guest|member|customer|client|reviewer|approver|manager)\b' \
|
|
174
|
+
--include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.prisma' --include='*.md' \
|
|
175
|
+
--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=.next --exclude-dir=dist --exclude-dir=build \
|
|
176
|
+
. 2>/dev/null | tr '[:upper:]' '[:lower:]' | sort -u | head -10)
|
|
177
|
+
|
|
178
|
+
while IFS= read -r kw; do
|
|
179
|
+
[ -z "$kw" ] && continue
|
|
180
|
+
ACTOR_CANDIDATES+=("$kw")
|
|
181
|
+
done <<<"$ACTOR_RAW"
|
|
182
|
+
|
|
183
|
+
# === Compose extracted_text prose ===
|
|
184
|
+
ENTITY_LIST=$(IFS=,; echo "${ALL_ENTITIES[*]:-}")
|
|
185
|
+
ACTOR_LIST=$(IFS=,; echo "${ACTOR_CANDIDATES[*]:-}")
|
|
186
|
+
BOUNDARY_COUNT=${#BOUNDARY_OBJECTS[@]}
|
|
187
|
+
|
|
188
|
+
EXTRACTED_TEXT="Repository: $TITLE
|
|
189
|
+
Path: $REPO_PATH
|
|
190
|
+
|
|
191
|
+
Detected entities (${#ALL_ENTITIES[@]}): ${ENTITY_LIST:-none}
|
|
192
|
+
Detected actors (${#ACTOR_CANDIDATES[@]}): ${ACTOR_LIST:-none}
|
|
193
|
+
Detected boundary objects: ${BOUNDARY_COUNT} routes/pages/api endpoints"
|
|
194
|
+
|
|
195
|
+
if [ -n "$README_EXCERPT" ]; then
|
|
196
|
+
EXTRACTED_TEXT="${EXTRACTED_TEXT}
|
|
197
|
+
|
|
198
|
+
README excerpt:
|
|
199
|
+
${README_EXCERPT}"
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# === Build JSON output ===
|
|
203
|
+
# Convert arrays to JSON; handle empty arrays explicitly (bash 3.x compat — no namerefs).
|
|
204
|
+
if [ ${#ALL_ENTITIES[@]} -eq 0 ]; then
|
|
205
|
+
ENTITIES_JSON='[]'
|
|
206
|
+
else
|
|
207
|
+
ENTITIES_JSON=$(printf '%s\n' "${ALL_ENTITIES[@]}" | jq -R . | jq -s -c '. | unique')
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
if [ ${#ACTOR_CANDIDATES[@]} -eq 0 ]; then
|
|
211
|
+
ACTORS_JSON='[]'
|
|
212
|
+
else
|
|
213
|
+
ACTORS_JSON=$(printf '%s\n' "${ACTOR_CANDIDATES[@]}" | jq -R . | jq -s -c '. | unique')
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
if [ ${#BOUNDARY_OBJECTS[@]} -eq 0 ]; then
|
|
217
|
+
BOUNDARY_JSON='[]'
|
|
218
|
+
else
|
|
219
|
+
BOUNDARY_JSON=$(printf '%s\n' "${BOUNDARY_OBJECTS[@]}" | jq -R . | jq -s -c '. | unique')
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
# Output
|
|
223
|
+
jq -n \
|
|
224
|
+
--arg extracted_text "$EXTRACTED_TEXT" \
|
|
225
|
+
--arg title "$TITLE" \
|
|
226
|
+
--arg readme_excerpt "$README_EXCERPT" \
|
|
227
|
+
--argjson entities_seen "$ENTITIES_JSON" \
|
|
228
|
+
--argjson actor_candidates "$ACTORS_JSON" \
|
|
229
|
+
--argjson boundary_objects "$BOUNDARY_JSON" \
|
|
230
|
+
'{
|
|
231
|
+
extracted_text: $extracted_text,
|
|
232
|
+
structural_hints: {
|
|
233
|
+
entities_seen: $entities_seen,
|
|
234
|
+
actor_candidates: $actor_candidates,
|
|
235
|
+
boundary_objects: $boundary_objects,
|
|
236
|
+
readme_excerpt: $readme_excerpt,
|
|
237
|
+
title: $title
|
|
238
|
+
}
|
|
239
|
+
}'
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# scripts/interpret-input.sh — input-handler CLI wrapper.
|
|
4
|
+
#
|
|
5
|
+
# Phase B: text-mode fully; other modes return stub InputArtifacts
|
|
6
|
+
# (schema-valid; extracted_text="deferred to Phase C").
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash scripts/interpret-input.sh "<source>" [--type=auto|text|url|image|repo|transcript|napkin|mixed] [--persist] [--slug=<slug>]
|
|
10
|
+
#
|
|
11
|
+
# Output:
|
|
12
|
+
# --persist OFF (default): InputArtifact JSON to stdout
|
|
13
|
+
# --persist ON: JSON written to docs/input-artifacts/<slug>.json; stdout = path
|
|
14
|
+
#
|
|
15
|
+
# Exit codes:
|
|
16
|
+
# 0 = success
|
|
17
|
+
# 2 = invalid input
|
|
18
|
+
# 3 = jq construction failed
|
|
19
|
+
# 4 = filesystem error (overwrite or mkdir)
|
|
20
|
+
# 5 = repo extraction setup failed (clone error, mktemp error)
|
|
21
|
+
#
|
|
22
|
+
# Pairs with: standards/input-handler-interface.md + .claude/skills/interpret-input/SKILL.md
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
27
|
+
|
|
28
|
+
usage() {
|
|
29
|
+
cat <<'EOF'
|
|
30
|
+
/interpret-input — produce schema-validated InputArtifact from any input
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
bash scripts/interpret-input.sh "<source>" [flags]
|
|
34
|
+
|
|
35
|
+
Flags:
|
|
36
|
+
--type=<type> text|url|image|repo|transcript|napkin|mixed (default: auto)
|
|
37
|
+
--persist write to docs/input-artifacts/<slug>.json
|
|
38
|
+
--slug=<slug> override auto-generated slug
|
|
39
|
+
--help show this message
|
|
40
|
+
|
|
41
|
+
Auto-detection:
|
|
42
|
+
^https?:// → url
|
|
43
|
+
\.(png|jpg|jpeg|gif|webp|svg)$ → image
|
|
44
|
+
\.git$ or contains github.com → repo
|
|
45
|
+
(else) → text
|
|
46
|
+
|
|
47
|
+
Phase B: text fully; other modes stub (extracted_text="deferred to Phase C").
|
|
48
|
+
EOF
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
SOURCE=""
|
|
52
|
+
TYPE="auto"
|
|
53
|
+
PERSIST=0
|
|
54
|
+
SLUG=""
|
|
55
|
+
|
|
56
|
+
while [ $# -gt 0 ]; do
|
|
57
|
+
case "$1" in
|
|
58
|
+
--type=*) TYPE="${1#--type=}"; shift ;;
|
|
59
|
+
--persist) PERSIST=1; shift ;;
|
|
60
|
+
--slug=*) SLUG="${1#--slug=}"; shift ;;
|
|
61
|
+
--help|-h) usage; exit 0 ;;
|
|
62
|
+
--*) echo "interpret-input: unknown flag '$1'" >&2; usage >&2; exit 2 ;;
|
|
63
|
+
*)
|
|
64
|
+
if [ -z "$SOURCE" ]; then SOURCE="$1"; shift
|
|
65
|
+
else echo "interpret-input: multiple positional sources not supported in v0" >&2; exit 2
|
|
66
|
+
fi
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
if [ -z "$SOURCE" ]; then
|
|
72
|
+
echo "interpret-input: requires <source>" >&2
|
|
73
|
+
usage >&2
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Auto-detect type
|
|
78
|
+
if [ "$TYPE" = "auto" ]; then
|
|
79
|
+
if echo "$SOURCE" | grep -qE '^https?://'; then
|
|
80
|
+
TYPE="url"
|
|
81
|
+
elif echo "$SOURCE" | grep -qiE '\.(png|jpg|jpeg|gif|webp|svg)$'; then
|
|
82
|
+
TYPE="image"
|
|
83
|
+
elif echo "$SOURCE" | grep -qE '\.git$'; then
|
|
84
|
+
TYPE="repo"
|
|
85
|
+
elif [ -d "$SOURCE" ] && [ -d "$SOURCE/.git" ]; then
|
|
86
|
+
# Local directory that's a git repo
|
|
87
|
+
TYPE="repo"
|
|
88
|
+
else
|
|
89
|
+
TYPE="text"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Validate type against schema enum
|
|
94
|
+
case "$TYPE" in
|
|
95
|
+
text|url|image|repo|transcript|napkin|mixed) ;;
|
|
96
|
+
*)
|
|
97
|
+
echo "interpret-input: unknown type '$TYPE' (must be: text|url|image|repo|transcript|napkin|mixed)" >&2
|
|
98
|
+
exit 2
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
# Generate slug if not provided
|
|
103
|
+
if [ -z "$SLUG" ]; then
|
|
104
|
+
TS=$(date -u +%Y-%m-%d)
|
|
105
|
+
HASH=$(printf '%s' "$SOURCE" | md5 -q 2>/dev/null \
|
|
106
|
+
|| printf '%s' "$SOURCE" | md5sum 2>/dev/null | cut -c1-6 \
|
|
107
|
+
|| echo "abc123")
|
|
108
|
+
HASH="${HASH:0:6}"
|
|
109
|
+
SLUG="${TS}-${TYPE}-${HASH}"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Validate slug pattern (matches schema's id pattern)
|
|
113
|
+
if ! echo "$SLUG" | grep -qE '^[a-z0-9][a-z0-9-]*$'; then
|
|
114
|
+
echo "interpret-input: slug '$SLUG' does not match required pattern ^[a-z0-9][a-z0-9-]*$" >&2
|
|
115
|
+
exit 2
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
119
|
+
|
|
120
|
+
STRUCTURAL_HINTS='{}'
|
|
121
|
+
|
|
122
|
+
case "$TYPE" in
|
|
123
|
+
text)
|
|
124
|
+
EXTRACTED_TEXT="$SOURCE"
|
|
125
|
+
EXTERNAL_URLS=$(printf '%s' "$SOURCE" | grep -oE 'https?://[^[:space:]<>"`]+' \
|
|
126
|
+
| sed 's/[.,;:)]*$//' \
|
|
127
|
+
| jq -R . | jq -s -c . 2>/dev/null || echo '[]')
|
|
128
|
+
;;
|
|
129
|
+
repo)
|
|
130
|
+
# Phase C: walk a local repo or clone a URL, extract structural signals.
|
|
131
|
+
# Resolve source to a local path. If the source isn't a URL or an existing
|
|
132
|
+
# directory, fall back to stub mode (preserves Phase B contract for
|
|
133
|
+
# not-yet-extractable sources).
|
|
134
|
+
CLEANUP_TMP=0
|
|
135
|
+
REPO_LOCAL=""
|
|
136
|
+
if echo "$SOURCE" | grep -qE '^https?://'; then
|
|
137
|
+
TMP_DIR=$(mktemp -d 2>/dev/null) || { echo "interpret-input: mktemp failed" >&2; exit 5; }
|
|
138
|
+
if ! git clone --depth=1 --quiet "$SOURCE" "$TMP_DIR" 2>/dev/null; then
|
|
139
|
+
rm -rf "$TMP_DIR"
|
|
140
|
+
echo "interpret-input: git clone failed for $SOURCE" >&2
|
|
141
|
+
exit 5
|
|
142
|
+
fi
|
|
143
|
+
REPO_LOCAL="$TMP_DIR"
|
|
144
|
+
CLEANUP_TMP=1
|
|
145
|
+
elif [ -d "$SOURCE" ]; then
|
|
146
|
+
REPO_LOCAL="$SOURCE"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [ -z "$REPO_LOCAL" ]; then
|
|
150
|
+
# Source can't be resolved; return Phase B-style stub (still valid InputArtifact)
|
|
151
|
+
EXTRACTED_TEXT="deferred to Phase C"
|
|
152
|
+
STRUCTURAL_HINTS='{}'
|
|
153
|
+
else
|
|
154
|
+
EXTRACT_OUT=$(bash "$REPO_ROOT/scripts/interpret-input/extract-repo.sh" "$REPO_LOCAL" 2>/dev/null)
|
|
155
|
+
EXTRACT_RC=$?
|
|
156
|
+
|
|
157
|
+
if [ "$CLEANUP_TMP" = "1" ]; then
|
|
158
|
+
rm -rf "$TMP_DIR"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
if [ "$EXTRACT_RC" != "0" ] || [ -z "$EXTRACT_OUT" ]; then
|
|
162
|
+
EXTRACTED_TEXT="repo extraction returned no signal for $SOURCE"
|
|
163
|
+
STRUCTURAL_HINTS='{}'
|
|
164
|
+
else
|
|
165
|
+
EXTRACTED_TEXT=$(echo "$EXTRACT_OUT" | jq -r '.extracted_text')
|
|
166
|
+
STRUCTURAL_HINTS=$(echo "$EXTRACT_OUT" | jq -c '.structural_hints')
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
EXTERNAL_URLS='[]'
|
|
170
|
+
;;
|
|
171
|
+
*)
|
|
172
|
+
EXTRACTED_TEXT="deferred to Phase C"
|
|
173
|
+
EXTERNAL_URLS='[]'
|
|
174
|
+
;;
|
|
175
|
+
esac
|
|
176
|
+
|
|
177
|
+
ARTIFACT=$(jq -n \
|
|
178
|
+
--arg id "$SLUG" \
|
|
179
|
+
--arg type "$TYPE" \
|
|
180
|
+
--arg source "$SOURCE" \
|
|
181
|
+
--arg extracted_text "$EXTRACTED_TEXT" \
|
|
182
|
+
--arg created_at "$NOW" \
|
|
183
|
+
--argjson external_urls "$EXTERNAL_URLS" \
|
|
184
|
+
--argjson structural_hints "$STRUCTURAL_HINTS" \
|
|
185
|
+
'{
|
|
186
|
+
id: $id,
|
|
187
|
+
type: $type,
|
|
188
|
+
source: $source,
|
|
189
|
+
extracted_text: $extracted_text,
|
|
190
|
+
structural_hints: $structural_hints,
|
|
191
|
+
external_urls: $external_urls,
|
|
192
|
+
created_at: $created_at
|
|
193
|
+
}')
|
|
194
|
+
|
|
195
|
+
if [ -z "$ARTIFACT" ]; then
|
|
196
|
+
echo "interpret-input: jq failed to construct InputArtifact JSON" >&2
|
|
197
|
+
exit 3
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
if [ "$PERSIST" = "1" ]; then
|
|
201
|
+
TARGET_DIR="docs/input-artifacts"
|
|
202
|
+
TARGET_PATH="${TARGET_DIR}/${SLUG}.json"
|
|
203
|
+
if [ -f "$TARGET_PATH" ]; then
|
|
204
|
+
echo "interpret-input: $TARGET_PATH already exists; --slug=<new> to write a different one" >&2
|
|
205
|
+
exit 4
|
|
206
|
+
fi
|
|
207
|
+
mkdir -p "$TARGET_DIR" || { echo "interpret-input: mkdir $TARGET_DIR failed" >&2; exit 4; }
|
|
208
|
+
echo "$ARTIFACT" | jq . > "$TARGET_PATH" || { echo "interpret-input: write $TARGET_PATH failed" >&2; exit 4; }
|
|
209
|
+
echo "$TARGET_PATH"
|
|
210
|
+
else
|
|
211
|
+
echo "$ARTIFACT" | jq .
|
|
212
|
+
fi
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# local-serve-stop.sh — stop a local-serve.sh server by slug
|
|
4
|
+
#
|
|
5
|
+
# Per bassclef#923 A2. Companion to scripts/launch/local-serve.sh.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/launch/local-serve-stop.sh <gallery-slug>
|
|
9
|
+
#
|
|
10
|
+
# If a server is running for the slug, kills the PID and removes the
|
|
11
|
+
# state file. If no server is running, exits 0 silently.
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
SLUG="${1:-}"
|
|
16
|
+
if [ -z "$SLUG" ]; then
|
|
17
|
+
echo "ERROR: gallery-slug required" >&2
|
|
18
|
+
echo "usage: $0 <gallery-slug>" >&2
|
|
19
|
+
exit 2
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
STATE_DIR="${REPO_ROOT:-$(pwd)}/state/markers/local-serve"
|
|
23
|
+
STATE_FILE="$STATE_DIR/${SLUG}.state"
|
|
24
|
+
|
|
25
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
26
|
+
echo "local-serve-stop: no state file for $SLUG — nothing to stop" >&2
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
PID=$(awk -F= '/^pid=/{print $2}' "$STATE_FILE" 2>/dev/null)
|
|
31
|
+
URL=$(awk -F= '/^url=/{print $2}' "$STATE_FILE" 2>/dev/null)
|
|
32
|
+
|
|
33
|
+
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
|
34
|
+
kill "$PID" 2>/dev/null || true
|
|
35
|
+
echo "local-serve-stop: stopped server at $URL (pid=$PID)" >&2
|
|
36
|
+
else
|
|
37
|
+
echo "local-serve-stop: state file present but PID $PID not alive — cleaning up" >&2
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
rm -f "$STATE_FILE" "${STATE_DIR}/${SLUG}.log"
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# local-serve.sh — serve a /launch local-mode prototype gallery via python http.server
|
|
4
|
+
#
|
|
5
|
+
# Per bassclef#923 A2: when /launch runs with --local flag, the Phase 12
|
|
6
|
+
# bind-subdomain step is replaced with this helper. Gallery served on
|
|
7
|
+
# localhost; no vendor accounts required.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bash scripts/launch/local-serve.sh <gallery-dir> [port-hint]
|
|
11
|
+
#
|
|
12
|
+
# Args:
|
|
13
|
+
# gallery-dir — path to the directory containing the gallery (e.g.
|
|
14
|
+
# docs/prototypes/<sprint-slug>/)
|
|
15
|
+
# port-hint — optional starting port; defaults to 8000. Helper walks
|
|
16
|
+
# up from this port until it finds an open one.
|
|
17
|
+
#
|
|
18
|
+
# What this does:
|
|
19
|
+
# 1. Verifies python3 is on the path
|
|
20
|
+
# 2. Verifies gallery-dir exists and contains at least an index.html
|
|
21
|
+
# 3. Finds an open port starting from port-hint (default 8000)
|
|
22
|
+
# 4. Starts python -m http.server in background, bound to 127.0.0.1
|
|
23
|
+
# 5. Writes the PID + URL to state/markers/local-serve/<slug>.state
|
|
24
|
+
# 6. Prints the URL to stdout (one line) for the caller to capture
|
|
25
|
+
#
|
|
26
|
+
# Idempotent: safe to re-run. If a server is already running for the
|
|
27
|
+
# same gallery (state file exists, PID still alive), prints the
|
|
28
|
+
# existing URL and exits 0.
|
|
29
|
+
#
|
|
30
|
+
# Cleanup: rely on the operator running scripts/launch/local-serve-stop.sh
|
|
31
|
+
# or just killing the PID. State file lists the PID for reference.
|
|
32
|
+
|
|
33
|
+
set -e
|
|
34
|
+
|
|
35
|
+
GALLERY_DIR="${1:-}"
|
|
36
|
+
PORT_HINT="${2:-8000}"
|
|
37
|
+
|
|
38
|
+
if [ -z "$GALLERY_DIR" ]; then
|
|
39
|
+
echo "ERROR: gallery-dir required" >&2
|
|
40
|
+
echo "usage: $0 <gallery-dir> [port-hint]" >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
45
|
+
echo "ERROR: python3 not on PATH. Install python3 first." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ ! -d "$GALLERY_DIR" ]; then
|
|
50
|
+
echo "ERROR: gallery directory does not exist: $GALLERY_DIR" >&2
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if [ ! -f "$GALLERY_DIR/index.html" ]; then
|
|
55
|
+
echo "WARN: $GALLERY_DIR has no index.html — serving anyway, but the gallery may not render cleanly" >&2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Derive a slug for the state file from the gallery dir's basename
|
|
59
|
+
GALLERY_SLUG=$(basename "$GALLERY_DIR")
|
|
60
|
+
STATE_DIR="${REPO_ROOT:-$(pwd)}/state/markers/local-serve"
|
|
61
|
+
STATE_FILE="$STATE_DIR/${GALLERY_SLUG}.state"
|
|
62
|
+
mkdir -p "$STATE_DIR"
|
|
63
|
+
|
|
64
|
+
# Idempotency check: if state file exists and PID is alive, return existing URL
|
|
65
|
+
if [ -f "$STATE_FILE" ]; then
|
|
66
|
+
EXISTING_PID=$(awk -F= '/^pid=/{print $2}' "$STATE_FILE" 2>/dev/null || true)
|
|
67
|
+
EXISTING_URL=$(awk -F= '/^url=/{print $2}' "$STATE_FILE" 2>/dev/null || true)
|
|
68
|
+
if [ -n "$EXISTING_PID" ] && kill -0 "$EXISTING_PID" 2>/dev/null; then
|
|
69
|
+
echo "local-serve: already running for $GALLERY_SLUG (pid=$EXISTING_PID)" >&2
|
|
70
|
+
echo "$EXISTING_URL"
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
# Stale state file — remove
|
|
74
|
+
rm -f "$STATE_FILE"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Find an open port starting from PORT_HINT
|
|
78
|
+
PORT="$PORT_HINT"
|
|
79
|
+
MAX_PORT=$((PORT_HINT + 100))
|
|
80
|
+
|
|
81
|
+
while [ "$PORT" -lt "$MAX_PORT" ]; do
|
|
82
|
+
if ! nc -z 127.0.0.1 "$PORT" 2>/dev/null; then
|
|
83
|
+
break
|
|
84
|
+
fi
|
|
85
|
+
PORT=$((PORT + 1))
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
if [ "$PORT" -ge "$MAX_PORT" ]; then
|
|
89
|
+
echo "ERROR: could not find open port in range $PORT_HINT..$MAX_PORT" >&2
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Start http.server in background, bound to 127.0.0.1 only (no external exposure)
|
|
94
|
+
LOG_FILE="$STATE_DIR/${GALLERY_SLUG}.log"
|
|
95
|
+
nohup python3 -m http.server "$PORT" --bind 127.0.0.1 --directory "$GALLERY_DIR" \
|
|
96
|
+
> "$LOG_FILE" 2>&1 &
|
|
97
|
+
SERVER_PID=$!
|
|
98
|
+
|
|
99
|
+
# Brief sleep to let server bind; verify by checking PID
|
|
100
|
+
sleep 1
|
|
101
|
+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
102
|
+
echo "ERROR: http.server failed to start. Log: $LOG_FILE" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
URL="http://127.0.0.1:${PORT}/"
|
|
107
|
+
|
|
108
|
+
# Write state file
|
|
109
|
+
cat > "$STATE_FILE" <<EOF
|
|
110
|
+
slug=${GALLERY_SLUG}
|
|
111
|
+
pid=${SERVER_PID}
|
|
112
|
+
port=${PORT}
|
|
113
|
+
url=${URL}
|
|
114
|
+
gallery_dir=${GALLERY_DIR}
|
|
115
|
+
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
116
|
+
EOF
|
|
117
|
+
|
|
118
|
+
echo "local-serve: gallery serving at $URL (pid=$SERVER_PID)" >&2
|
|
119
|
+
echo "local-serve: stop with: kill $SERVER_PID OR bash scripts/launch/local-serve-stop.sh $GALLERY_SLUG" >&2
|
|
120
|
+
|
|
121
|
+
# Print URL to stdout for the caller
|
|
122
|
+
echo "$URL"
|