@@ -174,7 +174,9 @@ PROJECT=$(echo "$RESULT" | python3 -c "import json,sys; d=json.loads(sys.stdin.r
174174if ! SEARCH=$( cli search_graph --project " $PROJECT " --name-pattern compute) ; then
175175 echo " FAIL: search_graph (flag form) exited non-zero" ; cat " $CLI_STDERR " ; exit 1
176176fi
177- TOTAL=$( echo " $SEARCH " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(d.get('total',0))" 2> /dev/null || echo " 0" )
177+ # search_graph default output is TOON (key: value scalars + header/rows tables)
178+ TOTAL=$( echo " $SEARCH " | sed -n ' s/^total: //p' | head -1)
179+ TOTAL=${TOTAL:- 0}
178180if [ " $TOTAL " -lt 1 ]; then
179181 echo " FAIL: search_graph for 'compute' returned 0 results"
180182 exit 1
@@ -185,7 +187,9 @@ echo "OK: search_graph found $TOTAL result(s) for 'compute'"
185187if ! TRACE=$( cli trace_path --project " $PROJECT " --function-name compute --direction inbound --depth 1) ; then
186188 echo " FAIL: trace_path (flag form) exited non-zero" ; cat " $CLI_STDERR " ; exit 1
187189fi
188- CALLERS=$( echo " $TRACE " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('callers',[])))" 2> /dev/null || echo " 0" )
190+ # trace_path default output is TOON: the callers[N]{...} header carries the count
191+ CALLERS=$( echo " $TRACE " | sed -n ' s/^callers\[\([0-9]*\)\].*/\1/p' | head -1)
192+ CALLERS=${CALLERS:- 0}
189193if [ " $CALLERS " -lt 1 ]; then
190194 echo " FAIL: trace_path found 0 callers for 'compute'"
191195 exit 1
@@ -207,7 +211,8 @@ echo "OK: schema has $LABELS node labels"
207211if ! FOLDERS=$( cli search_graph --project " $PROJECT " --label Folder) ; then
208212 echo " FAIL: search_graph --label Folder exited non-zero" ; cat " $CLI_STDERR " ; exit 1
209213fi
210- FOLDER_COUNT=$( echo " $FOLDERS " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(d.get('total',0))" 2> /dev/null || echo " 0" )
214+ FOLDER_COUNT=$( echo " $FOLDERS " | sed -n ' s/^total: //p' | head -1)
215+ FOLDER_COUNT=${FOLDER_COUNT:- 0}
211216if [ " $FOLDER_COUNT " -lt 2 ]; then
212217 echo " FAIL: expected at least 2 Folder nodes (src, src/pkg), got $FOLDER_COUNT "
213218 exit 1
@@ -217,7 +222,8 @@ echo "OK: $FOLDER_COUNT Folder nodes (init.py didn't clobber them)"
217222# 3d-cypher: query_graph Cypher capabilities
218223# #238 WITH DISTINCT — all functions share label "Function" → collapses to 1 row.
219224CYPHER_WD=$( cli query_graph --project " $PROJECT " --query " MATCH (f:Function) WITH DISTINCT f.label AS lbl RETURN lbl" )
220- WD_ROWS=$( echo " $CYPHER_WD " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
225+ WD_ROWS=$( echo " $CYPHER_WD " | sed -n ' s/^total: //p' | head -1)
226+ WD_ROWS=${WD_ROWS:- 0}
221227if [ " $WD_ROWS " -lt 1 ]; then
222228 echo " FAIL: query_graph WITH DISTINCT returned 0 rows"
223229 echo " $CYPHER_WD "
@@ -227,7 +233,8 @@ echo "OK: query_graph WITH DISTINCT returned $WD_ROWS row(s)"
227233
228234# #241 WHERE label test — f:Function is true for every Function node.
229235CYPHER_LBL=$( cli query_graph --project " $PROJECT " --query " MATCH (f:Function) WHERE f:Function RETURN f.name" )
230- LBL_ROWS=$( echo " $CYPHER_LBL " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
236+ LBL_ROWS=$( echo " $CYPHER_LBL " | sed -n ' s/^total: //p' | head -1)
237+ LBL_ROWS=${LBL_ROWS:- 0}
231238if [ " $LBL_ROWS " -lt 1 ]; then
232239 echo " FAIL: query_graph WHERE label-test returned 0 rows"
233240 echo " $CYPHER_LBL "
@@ -237,7 +244,8 @@ echo "OK: query_graph WHERE f:Function returned $LBL_ROWS row(s)"
237244
238245# #242 label alternation — (n:Function|Module) seeds either label.
239246CYPHER_ALT=$( cli query_graph --project " $PROJECT " --query " MATCH (n:Function|Module) RETURN n.name" )
240- ALT_ROWS=$( echo " $CYPHER_ALT " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
247+ ALT_ROWS=$( echo " $CYPHER_ALT " | sed -n ' s/^total: //p' | head -1)
248+ ALT_ROWS=${ALT_ROWS:- 0}
241249if [ " $ALT_ROWS " -lt 1 ]; then
242250 echo " FAIL: query_graph label alternation returned 0 rows"
243251 echo " $CYPHER_ALT "
@@ -247,7 +255,8 @@ echo "OK: query_graph (n:Function|Module) returned $ALT_ROWS row(s)"
247255
248256# #239 count(DISTINCT) — must parse and return a single aggregate row.
249257CYPHER_CD=$( cli query_graph --project " $PROJECT " --query " MATCH (f:Function) RETURN count(DISTINCT f.label)" )
250- CD_ROWS=$( echo " $CYPHER_CD " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
258+ CD_ROWS=$( echo " $CYPHER_CD " | sed -n ' s/^total: //p' | head -1)
259+ CD_ROWS=${CD_ROWS:- 0}
251260if [ " $CD_ROWS " -ne 1 ]; then
252261 echo " FAIL: query_graph count(DISTINCT) expected 1 row, got $CD_ROWS "
253262 echo " $CYPHER_CD "
@@ -261,7 +270,7 @@ cyp_first_cell() {
261270 # argv token, so string-literal args (e.g. replace(f.name,"a","A")) and Cypher
262271 # metacharacters {}|=~<>" need no JSON escaping.
263272 cli query_graph --project " $PROJECT " --query " $1 " |
264- python3 -c " import json,sys; d=json.loads(sys.stdin.read()); rows=d.get(' rows',[]); print(rows[0][0] if rows and rows[0] else '') " 2> /dev/null || echo " "
273+ sed -n ' /^ rows\[/{n;p;} ' | sed ' s/^ // ' | sed ' s/^"//;s/"$//;s/\\"/"/g '
265274}
266275
267276# labels(n) → JSON list like ["Function"]
@@ -316,15 +325,17 @@ echo "OK: query_graph coalesce(f.nonesuch, f.name) = $COALV"
316325
317326# EXISTS { } pattern predicate (edge-type-specific existence)
318327CYPHER_EX=$( cli query_graph --project " $PROJECT " --query " MATCH (f:Function) WHERE EXISTS { (f)-[:CALLS]->() } RETURN f.name" )
319- EX_ROWS=$( echo " $CYPHER_EX " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
328+ EX_ROWS=$( echo " $CYPHER_EX " | sed -n ' s/^total: //p' | head -1)
329+ EX_ROWS=${EX_ROWS:- 0}
320330if [ " $EX_ROWS " -lt 1 ]; then
321331 echo " FAIL: query_graph EXISTS{} predicate returned 0 rows" ; echo " $CYPHER_EX " ; exit 1
322332fi
323333echo " OK: query_graph EXISTS { (f)-[:CALLS]->() } returned $EX_ROWS row(s)"
324334
325335# =~ regex match in WHERE
326336CYPHER_RX=$( cli query_graph --project " $PROJECT " --query ' MATCH (f:Function) WHERE f.name =~ ".+" RETURN f.name' )
327- RX_ROWS=$( echo " $CYPHER_RX " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('rows',[])))" 2> /dev/null || echo " 0" )
337+ RX_ROWS=$( echo " $CYPHER_RX " | sed -n ' s/^total: //p' | head -1)
338+ RX_ROWS=${RX_ROWS:- 0}
328339if [ " $RX_ROWS " -lt 1 ]; then
329340 echo " FAIL: query_graph WHERE =~ regex returned 0 rows" ; echo " $CYPHER_RX " ; exit 1
330341fi
@@ -347,7 +358,7 @@ LEFTV=$(cyp_first_cell 'MATCH (f:Function) RETURN left(f.name, 3) AS l LIMIT 1')
347358
348359# NOT EXISTS dead-code query (functions with no caller)
349360CYPHER_NX=$( cli query_graph --project " $PROJECT " --query " MATCH (f:Function) WHERE NOT EXISTS { (f)<-[:CALLS]-() } RETURN f.name" )
350- NX_OK=$( echo " $CYPHER_NX " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(' rows' in d) " 2> /dev/null || echo " False" )
361+ NX_OK=$( echo " $CYPHER_NX " | grep -qE ' ^ rows\[[0-9]+\]\{ ' && echo " True " || echo " False" )
351362[ " $NX_OK " = " True" ] && echo " OK: query_graph NOT EXISTS dead-code query executed" || { echo " FAIL: NOT EXISTS query" ; echo " $CYPHER_NX " | head -c 300; exit 1; }
352363
353364# CASE expression in RETURN
@@ -370,19 +381,33 @@ esac
370381if ! ARCH=$( cli get_architecture --project " $PROJECT " --aspects clusters) ; then
371382 echo " FAIL: get_architecture (flag form) exited non-zero" ; cat " $CLI_STDERR " ; exit 1
372383fi
373- NCLUST=$( echo " $ARCH " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); print(len(d.get('clusters',[])))" 2> /dev/null || echo " 0" )
384+ # get_architecture default output is TOON: clusters[N]{...} header carries the count
385+ NCLUST=$( echo " $ARCH " | sed -n ' s/^clusters\[\([0-9]*\)\].*/\1/p' | head -1)
386+ NCLUST=${NCLUST:- 0}
374387if [ " $NCLUST " -lt 1 ]; then
375388 echo " FAIL: get_architecture returned 0 community clusters" ; echo " $ARCH " | head -c 400; exit 1
376389fi
377390echo " OK: get_architecture returned $NCLUST community cluster(s)"
378391
379392# 3g: search_code — basic search reports elapsed_ms + matches
380393SC=$( cli search_code --project " $PROJECT " --pattern cbm_ --mode compact --limit 5)
381- echo " $SC " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); assert 'elapsed_ms' in d; print('OK: search_code elapsed_ms='+str(d['elapsed_ms'])+' total_grep_matches='+str(d.get('total_grep_matches')))" 2> /dev/null || { echo " FAIL: search_code basic / no elapsed_ms" ; echo " $SC " | head -c 400; exit 1; }
394+ # compact mode emits TOON scalars: `elapsed_ms: N` + `total_grep_matches: N`
395+ SC_ELAPSED=$( echo " $SC " | sed -n ' s/^elapsed_ms: //p' | head -1)
396+ SC_GREPM=$( echo " $SC " | sed -n ' s/^total_grep_matches: //p' | head -1)
397+ if [ -n " $SC_ELAPSED " ]; then
398+ echo " OK: search_code elapsed_ms=$SC_ELAPSED total_grep_matches=${SC_GREPM:- 0} "
399+ else
400+ echo " FAIL: search_code basic / no elapsed_ms" ; echo " $SC " | head -c 400; exit 1
401+ fi
382402
383403# 3g: search_code — literal '|' under regex=false must surface a warning (#282)
384404SCW=$( cli search_code --project " $PROJECT " --pattern " cbm_init|cbm_nope" --regex false --limit 5)
385- echo " $SCW " | python3 -c " import json,sys; d=json.loads(sys.stdin.read()); w=' '.join(d.get('warnings',[])); assert 'regex=true' in w; print('OK: search_code literal-| warning surfaced')" 2> /dev/null || { echo " FAIL: search_code literal-| warning missing" ; echo " $SCW " | head -c 400; exit 1; }
405+ # TOON scalar `warning: ... regex=true ...`
406+ if echo " $SCW " | grep -q " regex=true" ; then
407+ echo " OK: search_code literal-| warning surfaced"
408+ else
409+ echo " FAIL: search_code literal-| warning missing" ; echo " $SCW " | head -c 400; exit 1
410+ fi
386411
387412# 3g: search_code — '&' in file_pattern accepted, not rejected as invalid (#272)
388413SCA=$( cli search_code --project " $PROJECT " --pattern cbm_ --file-pattern " *R&D*.c" --limit 5)
@@ -396,36 +421,39 @@ echo "=== Phase 3h: CLI input-mode guards (flags / stdin / --args-file / --help
396421
397422# Small helper: assert its stdin is a JSON object (exit non-zero otherwise).
398423assert_json_obj () { python3 -c " import json,sys; d=json.loads(sys.stdin.read()); sys.exit(0 if isinstance(d,dict) else 1)" 2> /dev/null; }
424+ # search_graph emits TOON by default: a results/semantic table header proves
425+ # the tool parsed its typed flags and produced a well-formed response.
426+ assert_toon_table () { grep -qE ' ^(results|semantic)\[[0-9]+\]\{' ; }
399427
400- # B1: INTEGER flag — --limit is schema-typed integer; must parse to valid JSON .
428+ # B1: INTEGER flag — --limit is schema-typed integer; must parse and answer .
401429if ! IM_INT=$( cli search_graph --project " $PROJECT " --name-pattern compute --limit 5) ; then
402430 echo " FAIL B1: search_graph --limit 5 exited non-zero" ; cat " $CLI_STDERR " ; exit 1
403431fi
404- if echo " $IM_INT " | assert_json_obj ; then
405- echo " OK B1: INTEGER flag (--limit 5) parsed → valid JSON "
432+ if echo " $IM_INT " | assert_toon_table ; then
433+ echo " OK B1: INTEGER flag (--limit 5) parsed → TOON results table "
406434else
407- echo " FAIL B1: --limit 5 did not produce valid JSON " ; echo " $IM_INT " | head -c 300; exit 1
435+ echo " FAIL B1: --limit 5 did not produce a TOON results table " ; echo " $IM_INT " | head -c 300; exit 1
408436fi
409437
410438# B2: BOOLEAN bare flag — --exclude-entry-points with no value → true; must succeed.
411439if ! IM_BOOL=$( cli search_graph --project " $PROJECT " --exclude-entry-points) ; then
412440 echo " FAIL B2: search_graph --exclude-entry-points exited non-zero" ; cat " $CLI_STDERR " ; exit 1
413441fi
414- if echo " $IM_BOOL " | assert_json_obj ; then
442+ if echo " $IM_BOOL " | assert_toon_table ; then
415443 echo " OK B2: BOOLEAN bare flag (--exclude-entry-points) → success"
416444else
417- echo " FAIL B2: --exclude-entry-points did not produce valid JSON " ; echo " $IM_BOOL " | head -c 300; exit 1
445+ echo " FAIL B2: --exclude-entry-points did not produce a TOON results table " ; echo " $IM_BOOL " | head -c 300; exit 1
418446fi
419447
420448# B3: ARRAY flag — repeated --semantic-query accumulates into a JSON array.
421- # semantic_results may be empty (index-mode dependent); only assert valid JSON .
449+ # Semantic-only calls emit ONLY the semantic table (may be empty, header stays) .
422450if ! IM_ARR=$( cli search_graph --project " $PROJECT " --semantic-query send --semantic-query publish) ; then
423451 echo " FAIL B3: search_graph repeated --semantic-query exited non-zero" ; cat " $CLI_STDERR " ; exit 1
424452fi
425- if echo " $IM_ARR " | assert_json_obj ; then
426- echo " OK B3: ARRAY flag (repeated --semantic-query) → valid JSON "
453+ if echo " $IM_ARR " | grep -qE ' ^semantic\[[0-9]+\]\{ ' ; then
454+ echo " OK B3: ARRAY flag (repeated --semantic-query) → semantic TOON table "
427455else
428- echo " FAIL B3: repeated --semantic-query did not produce valid JSON " ; echo " $IM_ARR " | head -c 300; exit 1
456+ echo " FAIL B3: repeated --semantic-query did not produce a semantic table " ; echo " $IM_ARR " | head -c 300; exit 1
429457fi
430458
431459# B4: STDIN — piped JSON resolves; this path must NOT emit a deprecation warning.
0 commit comments