@zenera/cli 1.1.22 → 1.1.23

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.
@@ -75,12 +75,18 @@ In a setup step, in this order:
75
75
 
76
76
  ```sh
77
77
  if [ "${FORCE:-0}" = 0 ] && zen rag docs ready --dir "$OUT" --quiet; then exit 3; fi
78
+ if [ "${CHECK:-0}" = 1 ]; then zen rag docs ready --dir "$OUT" || true; exit 4; fi
78
79
  if [ "${FORCE:-0}" = 0 ] && [ -f "$OUT/manifest.json" ]; then
79
80
  zen rag docs restore --dir "$OUT"; exit 0
80
81
  fi
81
82
  # … otherwise index it from the documents
82
83
  ```
83
84
 
85
+ Exit 4 is what `scripts/_setup.sh --check` reports as `needs setup`. Dropping
86
+ `--quiet` there is deliberate: `ready` prints what is missing and the command
87
+ that fixes it, and repeating that in the step would be a second opinion to keep
88
+ in step with this one.
89
+
84
90
  ## Which index gets read
85
91
 
86
92
  Every reading command takes `-d`, `--dir`. Without one:
@@ -610,7 +610,7 @@ can act on.
610
610
  ## Inspecting and repairing it
611
611
 
612
612
  ```
613
- zen memory [stats|ls|show|export|forget] [args] [options]
613
+ zen memory [stats|ls|show|export|merge|forget] [args] [options]
614
614
  ```
615
615
 
616
616
  `zen memory export --open` is the one to reach for: one self-contained HTML
@@ -627,6 +627,7 @@ so inspection is free and offline.
627
627
  | What has been corrected? | `zen memory ls --stale` |
628
628
  | What files are being kept? | `zen memory ls --files` |
629
629
  | Why was that recalled? | `zen memory show <id>` |
630
+ | Fold parallel warmup graphs into one | `zen memory merge <dir...>` |
630
631
  | That should never have been written down | `zen memory forget <id>` |
631
632
 
632
633
  The directory is not fixed. `zen run --memory <dir>` sends one run's memory
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: zen-memory-warmup
3
- description: Pre-populating an agent project's memory graph through warm-up queries before production or deployment — isolating scratch memory and workspaces in timestamped .tmp directories, using stronger reasoning models, validating machine-readable JSON output, categorizing queries with limits, shuffling, and dry-run execution, inspecting the graph with zen memory stats/ls, atomic promotion into the project, and generating automated population scripts in scripts/memory_warmup.sh.
3
+ description: Pre-populating an agent project's memory graph through warm-up queries before production or deployment — isolating scratch memory and workspaces in timestamped .tmp directories, using stronger reasoning models, validating machine-readable JSON output, categorizing queries with limits, shuffling, dry-run execution, fanning queries out across parallel runs and folding the results back with zen memory merge, inspecting the graph with zen memory stats/ls, atomic promotion into the project, and generating automated population scripts in scripts/memory_warmup.sh.
4
4
  ---
5
5
 
6
6
  # Memory Warmup
@@ -33,7 +33,7 @@ synthesize, connect, and commit high-value knowledge nodes.
33
33
 
34
34
  ## Core Architecture & Safety Rules
35
35
 
36
- Warmup is an offline staging process. It must adhere to five safety rules:
36
+ Warmup is an offline staging process. It must adhere to six safety rules:
37
37
 
38
38
  ### 1. Never warm up directly into the project's active `memory/`
39
39
 
@@ -101,6 +101,56 @@ warmup queries, and do not overwrite the project's `memory/` until the final
101
101
  graph in `.tmp/warmup-<timestamp>/memory` has been verified to contain expected
102
102
  nodes and vector embeddings.
103
103
 
104
+ ### 6. Give every parallel run its own memory directory
105
+
106
+ The memory lock is **per directory**. Two runs pointed at the same `--memory`
107
+ do not interleave — the second one refuses to start. So a warmup that wants to
108
+ use the wall clock has to fan out into one memory per run:
109
+
110
+ ```sh
111
+ .tmp/warmup-$STAMP/memory-1
112
+ .tmp/warmup-$STAMP/memory-2
113
+ ...
114
+ ```
115
+
116
+ and fold them back together afterwards with `zen memory merge`, which is
117
+ offline, contacts no model, and preserves node ids:
118
+
119
+ ```sh
120
+ zen memory merge .tmp/warmup-$STAMP/memory-* --dir .tmp/warmup-$STAMP/memory
121
+ ```
122
+
123
+ Merging is what makes `--incremental` safe under fan-out: every shard starts
124
+ from a copy of the same project memory, so most of what comes back is a shared
125
+ ancestor rather than a collision. Shared ancestors reconcile silently; two runs
126
+ that genuinely changed the same memory in different ways stop the merge and
127
+ name the ids, because picking a winner silently is how a merge loses an answer.
128
+
129
+ ---
130
+
131
+ ## Fanning Out
132
+
133
+ ```mermaid
134
+ flowchart LR
135
+ A["Selected queries"] --> B1["zen run → memory-1"]
136
+ A --> B2["zen run → memory-2"]
137
+ A --> B3["zen run → memory-N"]
138
+ B1 --> M["zen memory merge memory-* --dir memory"]
139
+ B2 --> M
140
+ B3 --> M
141
+ M --> V["zen memory stats --dir memory"]
142
+ V --> P["Backup & promote to memory/"]
143
+ ```
144
+
145
+ Bound the fan-out with `--jobs <n>`. Each run is a model call **and** a Podman
146
+ container, so the useful ceiling is set by the machine and the provider's rate
147
+ limit rather than by the number of queries; 4 is a sane default.
148
+
149
+ Sequential warmup is still the better choice when the queries build on each
150
+ other, because a later query can recall what an earlier one committed. Fan-out
151
+ trades that away for wall-clock time, and `merge` gives back everything except
152
+ the cross-query recall.
153
+
104
154
  ---
105
155
 
106
156
  ## The Warmup Cycle
@@ -181,6 +231,7 @@ The script should automate the entire pipeline:
181
231
  - `--shuffle`: Randomize query order before applying limits.
182
232
  - `--category <name>`: Filter queries to a specific category.
183
233
  - `--model <provider:model>`: Override model with a reasoning flagship.
234
+ - `--jobs <n>`: Run up to $n$ queries at once, each into its own memory.
184
235
  - `--incremental`: Copy existing project memory into staging before warming up.
185
236
  - `-y`, `--yes`: Promote memory without interactive confirmation.
186
237
  2. Create timestamped staging paths under `.tmp/` (e.g.
@@ -189,8 +240,9 @@ The script should automate the entire pipeline:
189
240
  4. If `--dry-run` is active, print the execution plan and exit 0 before touching disk.
190
241
  5. Invoke `zen run` with `--memory`, `--workspace`, `--model`, and `--json`.
191
242
  6. Parse each outcome with `jq`, halt if a turn fails, and report per-query execution time and total elapsed time since start.
192
- 7. Print graph statistics via `zen memory stats --dir ...` between queries to track graph growth, followed by a final validation.
193
- 8. Safely backup and replace the project's `memory/` directory with the warmed graph.
243
+ 7. Under `--jobs <n>`, give each run its own `memory-N` directory and fold them back with `zen memory merge memory-* --dir memory`.
244
+ 8. Print graph statistics via `zen memory stats --dir ...` between queries to track graph growth, followed by a final validation.
245
+ 9. Safely backup and replace the project's `memory/` directory with the warmed graph.
194
246
 
195
247
  ### Recommended Script Template (`scripts/memory_warmup.sh`)
196
248
 
@@ -211,6 +263,7 @@ pattern:
211
263
  # --shuffle Randomize query order before applying limits
212
264
  # --category <name> Filter queries to a specific category
213
265
  # --model <ref> Override model (e.g. anthropic:claude-sonnet-4-5)
266
+ # --jobs <n> Run up to n queries at once, one memory each (default 1)
214
267
  # --incremental Seed staging memory with current memory/ before runs
215
268
  # -y, --yes Assume yes for memory promotion
216
269
  # -h, --help Show this help
@@ -219,6 +272,7 @@ pattern:
219
272
  # ./scripts/memory_warmup.sh --dry-run
220
273
  # ./scripts/memory_warmup.sh --dry-run --category workflows --limit 2
221
274
  # ./scripts/memory_warmup.sh --shuffle --limit 5 --model anthropic:claude-sonnet-4-5
275
+ # ./scripts/memory_warmup.sh --jobs 4
222
276
  # ./scripts/memory_warmup.sh --incremental
223
277
  #
224
278
 
@@ -234,6 +288,7 @@ INCREMENTAL=false
234
288
  ASSUME_YES=false
235
289
  DRY_RUN=false
236
290
  LIMIT=0
291
+ JOBS=1
237
292
  SHUFFLE=false
238
293
  CATEGORY_FILTER=""
239
294
 
@@ -275,6 +330,10 @@ while [[ $# -gt 0 ]]; do
275
330
  MODEL="$2"
276
331
  shift 2
277
332
  ;;
333
+ --jobs)
334
+ JOBS="$2"
335
+ shift 2
336
+ ;;
278
337
  --incremental)
279
338
  INCREMENTAL=true
280
339
  shift
@@ -292,6 +351,7 @@ while [[ $# -gt 0 ]]; do
292
351
  echo " --shuffle Randomize query order before applying limits"
293
352
  echo " --category <name> Filter queries to a specific category"
294
353
  echo " --model <ref> Override model (e.g. anthropic:claude-sonnet-4-5)"
354
+ echo " --jobs <n> Run up to n queries at once, one memory each (default 1)"
295
355
  echo " --incremental Seed staging memory with current memory/ before runs"
296
356
  echo " -y, --yes Assume yes for memory promotion"
297
357
  echo " -h, --help Show this help"
@@ -378,6 +438,7 @@ if [ "$DRY_RUN" = true ]; then
378
438
  echo "Category filter: ${CATEGORY_FILTER:-<all>}"
379
439
  echo "Shuffled: $SHUFFLE"
380
440
  echo "Limit: ${LIMIT:-none}"
441
+ echo "Jobs: $JOBS"
381
442
  echo "Selected queries: ${#SELECTED[@]} of ${#ALL_QUERIES[@]}"
382
443
  echo ""
383
444
  echo "Planned queries:"
@@ -389,7 +450,15 @@ if [ "$DRY_RUN" = true ]; then
389
450
  echo ""
390
451
  echo "Sample command that would execute:"
391
452
  SAMPLE_Q="${SELECTED[0]#*|}"
392
- echo " zen run --memory \"$STAGING_MEM\" --workspace \"$WORKSPACES_DIR/run-1-<timestamp>\" ${MODEL:+--model \"$MODEL\" }--json \"$SAMPLE_Q\""
453
+ if [ "$JOBS" -le 1 ]; then
454
+ SAMPLE_MEM="$STAGING_MEM"
455
+ else
456
+ SAMPLE_MEM="$SCRATCH_DIR/memory-1"
457
+ fi
458
+ echo " zen run --memory \"$SAMPLE_MEM\" --workspace \"$WORKSPACES_DIR/run-1-<timestamp>\" ${MODEL:+--model \"$MODEL\" }--json \"$SAMPLE_Q\""
459
+ if [ "$JOBS" -gt 1 ]; then
460
+ echo " zen memory merge \"$SCRATCH_DIR\"/memory-* --dir \"$STAGING_MEM\" --yes"
461
+ fi
393
462
  echo " zen memory stats --dir \"$STAGING_MEM\""
394
463
  echo ""
395
464
  echo "Post-run validation that would run:"
@@ -421,78 +490,139 @@ cleanup() {
421
490
  }
422
491
  trap cleanup EXIT
423
492
 
424
- # Prepare staging memory
425
- mkdir -p "$STAGING_MEM" "$WORKSPACES_DIR"
493
+ # Prepare staging paths
494
+ mkdir -p "$WORKSPACES_DIR"
426
495
 
427
- if [ "$INCREMENTAL" = true ] && [ -d "$PROJECT_MEM" ]; then
428
- echo "Copying existing memory for incremental warmup..."
429
- cp -R "$PROJECT_MEM/." "$STAGING_MEM/"
430
- fi
431
-
432
- echo "Running ${#SELECTED[@]} warmup queries..."
433
- echo "----------------------------------------"
434
-
435
- COUNT=0
436
- for item in "${SELECTED[@]}"; do
437
- COUNT=$((COUNT + 1))
438
- CATEGORY="${item%%|*}"
439
- QUERY="${item#*|}"
440
-
441
- RUN_STAMP="$(date +%s)"
442
- RUN_WORKSPACE="$WORKSPACES_DIR/run-$COUNT-$RUN_STAMP"
443
- mkdir -p "$RUN_WORKSPACE"
496
+ seed_memory() {
497
+ mkdir -p "$1"
498
+ if [ "$INCREMENTAL" = true ] && [ -d "$PROJECT_MEM" ]; then
499
+ cp -R "$PROJECT_MEM/." "$1/"
500
+ fi
501
+ }
444
502
 
445
- echo "[$COUNT/${#SELECTED[@]}] [$CATEGORY] \"$QUERY\""
446
- echo " -> Workspace: $RUN_WORKSPACE"
503
+ # One query, into the memory directory it is handed. The lock is per directory,
504
+ # so this is the unit that more than one of can be in flight at a time.
505
+ run_query() {
506
+ local index="$1" category="$2" query="$3" mem="$4"
507
+ local workspace="$WORKSPACES_DIR/run-$index-$(date +%s)"
508
+ mkdir -p "$workspace"
447
509
 
448
- # Assemble arguments
449
- CMD=(zen run --memory "$STAGING_MEM" --workspace "$RUN_WORKSPACE" --json)
510
+ local cmd=(zen run --memory "$mem" --workspace "$workspace" --json)
450
511
  if [ -n "$MODEL" ]; then
451
- CMD+=(--model "$MODEL")
512
+ cmd+=(--model "$MODEL")
452
513
  fi
453
- CMD+=("$QUERY")
454
-
455
- QUERY_START=$(date +%s)
514
+ cmd+=("$query")
456
515
 
457
- # Run query and capture JSON output
458
- JSON_OUT="$("${CMD[@]}")"
459
- EXIT_CODE=$?
516
+ echo "[$index/${#SELECTED[@]}] [$category] \"$query\""
517
+ echo " -> Workspace: $workspace"
460
518
 
461
- QUERY_END=$(date +%s)
462
- QUERY_DURATION=$((QUERY_END - QUERY_START))
463
- TOTAL_ELAPSED=$((QUERY_END - WARMUP_START))
464
-
465
- if [ $EXIT_CODE -ne 0 ]; then
466
- echo " -> Command exited with code $EXIT_CODE (query: $(format_duration $QUERY_DURATION), total elapsed: $(format_duration $TOTAL_ELAPSED))" >&2
467
- exit $EXIT_CODE
519
+ local start out
520
+ start=$(date +%s)
521
+ if ! out="$("${cmd[@]}")"; then
522
+ echo " -> Command failed" >&2
523
+ return 1
468
524
  fi
469
525
 
470
- # Parse outcome via jq if available
471
526
  if command -v jq >/dev/null 2>&1; then
472
- STOP_REASON=$(echo "$JSON_OUT" | jq -r '.stopReason // empty')
473
- AGENT=$(echo "$JSON_OUT" | jq -r '.agent // empty')
474
- echo " -> Completed: agent=$AGENT stopReason=$STOP_REASON"
475
-
476
- if [ "$STOP_REASON" != "final" ]; then
477
- echo " -> Unexpected stop reason: $STOP_REASON" >&2
478
- exit 1
527
+ local stop agent
528
+ stop=$(echo "$out" | jq -r '.stopReason // empty')
529
+ agent=$(echo "$out" | jq -r '.agent // empty')
530
+ echo " -> Completed: agent=$agent stopReason=$stop"
531
+ if [ "$stop" != "final" ]; then
532
+ echo " -> Unexpected stop reason: $stop" >&2
533
+ return 1
479
534
  fi
480
535
  else
481
536
  echo " -> Turn completed successfully."
482
537
  fi
483
538
 
484
- echo " -> Timing: query took $(format_duration $QUERY_DURATION) | elapsed since start: $(format_duration $TOTAL_ELAPSED)"
539
+ local now
540
+ now=$(date +%s)
541
+ echo " -> Timing: query took $(format_duration $((now - start))) | elapsed since start: $(format_duration $((now - WARMUP_START)))"
542
+ }
543
+
544
+ # `wait` with no arguments always reports success, so every child is waited for
545
+ # by pid. Batches of $JOBS rather than a rolling queue: `wait -n` wants bash
546
+ # 4.3 and macOS still ships 3.2.
547
+ PIDS=()
548
+ FAILED=false
549
+ drain() {
550
+ if [ ${#PIDS[@]} -eq 0 ]; then
551
+ return 0
552
+ fi
553
+ local pid
554
+ for pid in "${PIDS[@]}"; do
555
+ wait "$pid" || FAILED=true
556
+ done
557
+ PIDS=()
558
+ }
559
+
560
+ echo "Running ${#SELECTED[@]} warmup queries (jobs: $JOBS)..."
561
+ echo "----------------------------------------"
562
+
563
+ if [ "$JOBS" -le 1 ]; then
564
+ seed_memory "$STAGING_MEM"
565
+ COUNT=0
566
+ for item in "${SELECTED[@]}"; do
567
+ COUNT=$((COUNT + 1))
568
+ run_query "$COUNT" "${item%%|*}" "${item#*|}" "$STAGING_MEM"
485
569
 
486
- # Print memory stats between queries to track graph growth
487
- if [ -f "$STAGING_MEM/manifest.json" ]; then
570
+ # Print memory stats between queries to track graph growth
571
+ if [ -f "$STAGING_MEM/manifest.json" ]; then
572
+ echo ""
573
+ echo "Memory stats after query $COUNT:"
574
+ zen memory stats --dir "$STAGING_MEM"
575
+ else
576
+ echo " -> Memory stats: No memories committed yet."
577
+ fi
488
578
  echo ""
489
- echo "Memory stats after query $COUNT:"
490
- zen memory stats --dir "$STAGING_MEM"
491
- else
492
- echo " -> Memory stats: No memories committed yet."
579
+ done
580
+ else
581
+ SHARDS=()
582
+ COUNT=0
583
+ for item in "${SELECTED[@]}"; do
584
+ COUNT=$((COUNT + 1))
585
+ SHARD="$SCRATCH_DIR/memory-$COUNT"
586
+ seed_memory "$SHARD"
587
+ SHARDS+=("$SHARD")
588
+ run_query "$COUNT" "${item%%|*}" "${item#*|}" "$SHARD" \
589
+ >"$SCRATCH_DIR/run-$COUNT.log" 2>&1 &
590
+ PIDS+=($!)
591
+ if [ ${#PIDS[@]} -ge "$JOBS" ]; then
592
+ drain
593
+ fi
594
+ done
595
+ drain
596
+
597
+ for log in "$SCRATCH_DIR"/run-*.log; do
598
+ if [ -f "$log" ]; then
599
+ cat "$log"
600
+ echo ""
601
+ fi
602
+ done
603
+
604
+ if [ "$FAILED" = true ]; then
605
+ echo "One or more warmup runs failed; nothing was promoted." >&2
606
+ exit 1
493
607
  fi
608
+
609
+ # Only a memory something was committed to can be merged; a run that found
610
+ # nothing worth keeping never writes a manifest.
611
+ MERGE=()
612
+ for shard in "${SHARDS[@]}"; do
613
+ if [ -f "$shard/manifest.json" ]; then
614
+ MERGE+=("$shard")
615
+ fi
616
+ done
617
+ if [ ${#MERGE[@]} -eq 0 ]; then
618
+ echo "No warmup run committed anything." >&2
619
+ exit 1
620
+ fi
621
+
622
+ echo "Merging ${#MERGE[@]} memories into $STAGING_MEM..."
623
+ zen memory merge "${MERGE[@]}" --dir "$STAGING_MEM" --yes
494
624
  echo ""
495
- done
625
+ fi
496
626
 
497
627
  echo "----------------------------------------"
498
628
  TOTAL_RUN_TIME=$(( $(date +%s) - WARMUP_START ))
@@ -111,11 +111,12 @@ However small the diff:
111
111
  last.
112
112
  2. **`scripts/_setup.sh` has run to completion in this session, and re-runs
113
113
  clean.** A one-line specification change can still break a setup step, and a
114
- pass that did not run it does not know. `skipped` is only a pass if the
115
- artefact really is usable: for every index the project builds, check
116
- `zen rag <subject> ready --dir <dir>` agrees. A step that tests for a
117
- committed `manifest.json` skips on a fresh clone whose git-ignored `lance/`
118
- vectors are missing, and the project cannot search.
114
+ pass that did not run it does not know. Then `scripts/_setup.sh --check` must
115
+ report every step `skipped`: it makes each step's own readiness test without
116
+ doing the work, which is what tells a real `skipped` from a step that only
117
+ looked done. A step that tests for a committed `manifest.json` skips on a
118
+ fresh clone whose git-ignored `lance/` vectors are missing, and the project
119
+ cannot search.
119
120
  3. **§9 of the copilot instructions, worked as a checklist**, group by group.
120
121
  4. **The capability obligations in §0.1.** For every capability the project has
121
122
  turned on, load that capability's skill and re-check what it requires - in
@@ -28,6 +28,8 @@ something in it — and report what was done in enough detail to be checked.
28
28
  are the steps it runs in order. A step is safe to run twice and reports
29
29
  `skipped` the second time — testing for something that survives a clone, not
30
30
  for a file `.gitignore` excludes.
31
+ - `scripts/_setup.sh --check` asks the same questions and does none of the work,
32
+ so what is missing can be read without paying to find out.
31
33
  - Currently: no steps. Nothing has to be prepared before a run.
32
34
 
33
35
  ## Agents
@@ -4,6 +4,7 @@
4
4
  #
5
5
  # scripts/_setup.sh do whatever is not done yet
6
6
  # scripts/_setup.sh --force do all of it again
7
+ # scripts/_setup.sh --check say what is not done, and do none of it
7
8
  #
8
9
  # Anything that has to be in place before a run — an index built, a document
9
10
  # fetched, a file generated — is a script in this directory rather than a
@@ -17,6 +18,11 @@
17
18
  # - takes no arguments, and reads $FORCE to know whether to redo finished work
18
19
  # - exits 0 when it did the work, 3 when there was nothing to do, non-zero
19
20
  # when it failed
21
+ # - under $CHECK, does none of the work: it runs the same "already done" test,
22
+ # exits 3 when the answer is yes, and otherwise says what is missing and
23
+ # exits 4. A check must not assume an earlier step has run — a missing
24
+ # prerequisite is a 4 of its own, not a hard failure, or one unbuilt thing
25
+ # hides the rest of the list
20
26
  # - writes under .tmp/ and moves the result into place, so an interrupted run
21
27
  # never leaves half an artefact behind
22
28
  #
@@ -27,6 +33,10 @@
27
33
  # first search fails for a reason nobody connects to setup. Ask the tool instead
28
34
  # of guessing at files — `zen rag docs ready` answers exactly that question.
29
35
  #
36
+ # That test is the whole of `--check`, which is why this is one script and not
37
+ # two: the steps already know what "done" means, and a second script asking the
38
+ # same question would be a second answer to keep in step with this one.
39
+ #
30
40
  # Running this twice has to be safe, and the second run is what proves it: every
31
41
  # step should report `skipped`.
32
42
 
@@ -34,10 +44,17 @@ set -eu
34
44
  cd "$(dirname "$0")/.."
35
45
 
36
46
  FORCE=0
37
- if [ "${1:-}" = "--force" ]; then
38
- FORCE=1
39
- fi
40
- export FORCE
47
+ CHECK=0
48
+ case "${1:-}" in
49
+ '') ;;
50
+ --force) FORCE=1 ;;
51
+ --check) CHECK=1 ;;
52
+ *)
53
+ echo "usage: scripts/_setup.sh [--force|--check]" >&2
54
+ exit 2
55
+ ;;
56
+ esac
57
+ export FORCE CHECK
41
58
 
42
59
  LOGS=.tmp/logs
43
60
 
@@ -58,6 +75,12 @@ LOGS=.tmp/logs
58
75
  # echo "$OUT is already built"
59
76
  # exit 3
60
77
  # fi
78
+ # if [ "${CHECK:-0}" = 1 ]; then
79
+ # # Without --quiet it names what is missing and the command that fixes
80
+ # # it; `|| true` because not-ready is the exit code we came for.
81
+ # zen rag docs ready --dir "$OUT" || true
82
+ # exit 4
83
+ # fi
61
84
  # # Committed index, git-ignored vectors: re-embed from the copies the index
62
85
  # # already holds rather than indexing the documents over again.
63
86
  # if [ "${FORCE:-0}" = 0 ] && [ -f "$OUT/manifest.json" ]; then
@@ -80,9 +103,16 @@ fi
80
103
  mkdir -p "$LOGS"
81
104
  summary=""
82
105
  failed=0
106
+ pending=0
83
107
 
84
108
  for name in $STEPS; do
85
- log="$LOGS/setup-$name.log"
109
+ # A dry run keeps its own log: overwriting the build's would lose the
110
+ # record of the build to a question about it.
111
+ if [ "$CHECK" = 1 ]; then
112
+ log="$LOGS/check-$name.log"
113
+ else
114
+ log="$LOGS/setup-$name.log"
115
+ fi
86
116
  echo
87
117
  echo "-- $name $(date '+%H:%M:%S') ($log)"
88
118
 
@@ -96,6 +126,10 @@ for name in $STEPS; do
96
126
  case "$code" in
97
127
  0) word=ok ;;
98
128
  3) word=skipped ;;
129
+ 4)
130
+ word="needs setup"
131
+ pending=1
132
+ ;;
99
133
  *)
100
134
  word="failed ($code)"
101
135
  failed=1
@@ -105,12 +139,20 @@ for name in $STEPS; do
105
139
  "
106
140
  # Later steps depend on earlier ones, so a failure stops rather than
107
141
  # producing a second failure that is only a consequence of the first.
142
+ # `needs setup` does not stop it: the point of asking is the whole list.
108
143
  if [ "$failed" = 1 ]; then
109
144
  break
110
145
  fi
111
146
  done
112
147
 
113
148
  echo
114
- echo "setup:"
149
+ if [ "$CHECK" = 1 ]; then
150
+ echo "setup check:"
151
+ else
152
+ echo "setup:"
153
+ fi
115
154
  printf '%s' "$summary"
116
- exit "$failed"
155
+ if [ "$failed" = 1 ] || [ "$pending" = 1 ]; then
156
+ exit 1
157
+ fi
158
+ exit 0