nobrainer-tech-flow 2.4.0 → 2.5.0

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.
Files changed (33) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/AGENTS.md +1 -0
  5. package/CLAUDE.md +1 -0
  6. package/README.md +9 -6
  7. package/assets/nobrainer-tech-logo.svg +1 -1
  8. package/assets/plugin-logo.png +0 -0
  9. package/docs/INSTALL.md +9 -6
  10. package/docs/MIGRATION_TO_FLOW.md +14 -0
  11. package/docs/RELEASING.md +24 -0
  12. package/docs/SKILL_CURATION.md +8 -2
  13. package/docs/releases/v2.4.1.md +63 -0
  14. package/docs/releases/v2.5.0.md +190 -0
  15. package/gemini-extension.json +1 -1
  16. package/package.json +1 -1
  17. package/plugin.json +1 -1
  18. package/scripts/install.py +13 -6
  19. package/scripts/install.sh +137 -16
  20. package/scripts/install_skills.py +34 -5
  21. package/scripts/recommend_flow_setup.py +126 -21
  22. package/scripts/validate_skills.py +2 -1
  23. package/skills/nobrainer-context/SKILL.md +113 -0
  24. package/skills/nobrainer-context/references/clients.md +115 -0
  25. package/skills/nobrainer-context/references/consolidate.md +66 -0
  26. package/skills/nobrainer-context/scripts/context_inventory.py +425 -0
  27. package/skills/nobrainer-skill-doctor/SKILL.md +4 -4
  28. package/skills/nobrainer-tech-flow/VERSION +1 -1
  29. package/skills/nobrainer-tech-flow/references/optional-decisions.md +5 -3
  30. package/skills/nobrainer-tech-flow/references/routing.md +1 -1
  31. package/skills/nobrainer-tech-flow/scripts/typed_decisions.py +20 -9
  32. package/skills/nobrainer-codex-context/SKILL.md +0 -111
  33. /package/skills/{nobrainer-codex-context → nobrainer-context}/scripts/codex_context.py +0 -0
@@ -19,7 +19,7 @@ unset CDPATH
19
19
  # The eighteen skills, and the names earlier layouts used. Tests keep these lists
20
20
  # equal to the ones in scripts/install_skills.py.
21
21
  SKILLS='nobrainer-auto-fine-tune nobrainer-autoimprove nobrainer-browser nobrainer-build
22
- nobrainer-codex-context nobrainer-decide nobrainer-dispatcher nobrainer-rca
22
+ nobrainer-context nobrainer-decide nobrainer-dispatcher nobrainer-rca
23
23
  nobrainer-research nobrainer-review nobrainer-security nobrainer-sessions
24
24
  nobrainer-skill-doctor nobrainer-spec-driven-development nobrainer-team
25
25
  nobrainer-tech-flow nobrainer-wiki nobrainer-writing'
@@ -28,13 +28,13 @@ deep-audit deep-autoresearch deep-autoreview deep-bugs-finder deep-code-review
28
28
  deep-decide deep-rca dispatching-parallel-agents engineering-standards
29
29
  karpathy-auto-improver karpathy-llm-wiki llm-wiki nb-add nb-dispatcher nb-flow nb-get
30
30
  nb-multi nb-tidy nb-workflow nb-write nobrainer-autopilot nobrainer-capture-lesson
31
- nobrainer-continuous-improvement nobrainer-fast-audit nobrainer-human-like
31
+ nobrainer-codex-context nobrainer-continuous-improvement nobrainer-fast-audit nobrainer-human-like
32
32
  nobrainer-memory nobrainer-memory-memsearch nobrainer-npm-secure nobrainer-reddit
33
33
  nobrainer-simplifier nobrainer-skill-browser nobrainer-starter nobrainer-style
34
34
  nobrainer-team-builder nobrainer-ultra nobrainer-ultracode-workflow nobrainer-wiki-add
35
35
  nobrainer-wiki-get nobrainer-wiki-tidy playwright-cli security-review session-handoff
36
36
  wiki-add wiki-get wiki-tidy'
37
- RETIRED='nobrainer-reddit'
37
+ RETIRED='nobrainer-codex-context nobrainer-reddit'
38
38
  START='<!-- NOBRAINER-TECH-FLOW:START -->'
39
39
  END='<!-- NOBRAINER-TECH-FLOW:END -->'
40
40
  LEGACY_TEXT='(^|[^[:alnum:]_])nobrainer-ultra([^[:alnum:]_]|$)|^[[:space:]]*#{1,6}[[:space:]]+nobrainer([ .]?tech)?[[:space:]]+flow([^[:alnum:]_]|$)'
@@ -169,7 +169,9 @@ physical() {
169
169
  return
170
170
  fi
171
171
  done
172
- _p_head=$(cd -P "$_p_head" 2>/dev/null && pwd -P) || _p_head=$1 _p_tail=
172
+ # The slash keeps a trailing line break of a folder name across the substitution.
173
+ _p_head=$(cd -P "$_p_head" 2>/dev/null && printf '%s/' "$PWD") || _p_head=$1/ _p_tail=
174
+ _p_head=${_p_head%/}
173
175
  printf '%s%s\n' "${_p_head%/}" "$_p_tail"
174
176
  }
175
177
 
@@ -283,6 +285,38 @@ is_our_link() {
283
285
  [ "$_o_resolved" = "$2" ]
284
286
  }
285
287
 
288
+ # Put a claimed entry ($1) back at its name ($2) unless something new stands there; the
289
+ # private folder it was moved into goes too. A blocked restore keeps the claim and says where.
290
+ unclaim() {
291
+ # Restore through the same native no-replace primitive as the Python installer.
292
+ # Normal shell installs need no Python; without it, a recovery claim stays intact.
293
+ if command -v python3 >/dev/null 2>&1 && python3 - "$root/scripts" "$1" "$2" <<'PYTHON'
294
+ import sys
295
+ from pathlib import Path
296
+ sys.path.insert(0, sys.argv[1])
297
+ from install_skills import restore_claim
298
+ try:
299
+ restore_claim(Path(sys.argv[3]), Path(sys.argv[2]))
300
+ except (OSError, RuntimeError) as exc:
301
+ print(exc, file=sys.stderr)
302
+ sys.exit(1)
303
+ PYTHON
304
+ then
305
+ return 0
306
+ fi
307
+ err "PRESERVED: atomic restore unavailable or blocked; could not put $2 back; it is kept at $1"
308
+ return 1
309
+ }
310
+
311
+ # A recorded skill link this checkout made: $1 is the link, $2 the skill name. The link of
312
+ # a retired module dangles, so it is compared as written.
313
+ owns_link() {
314
+ case " $RETIRED " in
315
+ *" $2 "*) is_exact_legacy_link "$1" "$2" ;;
316
+ *) is_our_link "$1" "$root/skills/$2" ;;
317
+ esac
318
+ }
319
+
286
320
  # Resolve links before "..", including dangling final links. A trailing slash
287
321
  # preserves a pathname's trailing newlines across command substitution.
288
322
  resolve_legacy_path() (
@@ -408,7 +442,8 @@ read_state() {
408
442
  ' ]' | ' ],') _r_in= ;;
409
443
  *)
410
444
  json_field "\": ${_r_line# }" || return 1
411
- case " $(printf '%s' "$SKILLS" | tr '\n' ' ') " in *" $json_value "*) ;; *) return 1 ;; esac
445
+ # A record from an earlier release may hold a module since retired.
446
+ case " $(printf '%s' "$SKILLS $RETIRED" | tr '\n' ' ') " in *" $json_value "*) ;; *) return 1 ;; esac
412
447
  case " $st_created " in *" $json_value "*) return 1 ;; esac
413
448
  st_created="$st_created $json_value" ;;
414
449
  esac
@@ -543,7 +578,9 @@ under_link() {
543
578
  # The folders a record lists must be ones the setup can have made: each an absolute,
544
579
  # plain path listed once, that is, or lies above, the skills folder, the folder of the
545
580
  # instruction file or the folder of the record. Anything else is a damaged record that an undo must not act on. A path
546
- # may be spelled with or without the links on the way, so both spellings count.
581
+ # may be spelled with or without the links on the way, so both spellings count. Two entries
582
+ # may still lead to one folder: a link put in place of one of them since the setup does that.
583
+ # The undo removes no folder of such a group.
547
584
  above() {
548
585
  case "$2/" in "$1"/*) return 0 ;; esac
549
586
  return 1
@@ -591,9 +628,9 @@ check_dirs() {
591
628
  fi
592
629
  [ -n "$_k_ok" ] || exit 1
593
630
  case $_k_seen in *"
594
- $_k_real
631
+ $_k_dir
595
632
  "*) exit 1 ;; esac
596
- _k_seen="$_k_seen$_k_real
633
+ _k_seen="$_k_seen$_k_dir
597
634
  "
598
635
  done
599
636
  }
@@ -677,7 +714,7 @@ check_record() {
677
714
  }
678
715
 
679
716
  plan_skills() {
680
- to_link= link_count=0 kept_count=0 problems=
717
+ to_link= link_count=0 kept_count=0 problems= to_retire=
681
718
  for _p_name in $SKILLS; do
682
719
  _p_source=$root/skills/$_p_name
683
720
  [ -f "$_p_source/SKILL.md" ] || die 2 "this checkout is incomplete: $_p_source/SKILL.md is missing"
@@ -704,7 +741,13 @@ CONFLICT: $_p_name: $_p_source -> $_p_target"
704
741
  if ! is_exact_legacy_link "$_p_target" "$_p_name"; then
705
742
  err "PRESERVED_FOREIGN: $_p_name: retired from nobrainer-tech-flow; left unchanged: $_p_target"
706
743
  continue
707
- fi ;;
744
+ fi
745
+ # This setup's record lists the link, as an undo would remove it.
746
+ case " $st_created " in
747
+ *" $_p_name "*)
748
+ to_retire="$to_retire $_p_name"
749
+ continue ;;
750
+ esac ;;
708
751
  esac
709
752
  problems="$problems
710
753
  LEGACY: $_p_name: $_p_target"
@@ -815,6 +858,9 @@ variable_note() {
815
858
  preview() {
816
859
  say "$source_line for $label"
817
860
  say "SKILLS: $link_count to link into $skills_dir ($kept_count already linked)"
861
+ for _v_name in $to_retire; do
862
+ say "REMOVE_RETIRED: $_v_name: made by this setup, now renamed: $skills_dir/$_v_name"
863
+ done
818
864
  if [ "$inherit" = 1 ]; then
819
865
  say "INHERITS_CODEX: $target imports $codex_file; no duplicate block added"
820
866
  else
@@ -910,6 +956,10 @@ write_instructions() {
910
956
  }
911
957
 
912
958
  install() {
959
+ # plan_skills needs the retired links the record lists; check_record judges the record
960
+ # after the skills, as before, so a record that cannot be read only lists none here.
961
+ st_created=
962
+ if [ -f "$state" ] && [ ! -L "$state" ]; then read_state "$state" || st_created=; fi
913
963
  plan_skills
914
964
  plan_instructions
915
965
  check_record
@@ -922,6 +972,16 @@ install() {
922
972
  sha256 /dev/null >/dev/null || die 2 "this script needs sha256sum, shasum or openssl to record the setup"
923
973
 
924
974
  created= dirs_to_make=
975
+ # A folder this run makes is recorded as it really is, links resolved. A line break or a
976
+ # control character in that form would leave a record no undo can read, so such a run
977
+ # stops before anything is written.
978
+ note_dirs "$home"
979
+ if [ "$link_count" -gt 0 ]; then note_dirs "$skills_dir"; fi
980
+ if [ "$file_status" != present ] && [ "$inherit" = 0 ]; then note_dirs "${target%/*}"; fi
981
+ printf '%s' "$dirs_to_make" | while IFS= read -r _a_dir; do
982
+ reject_control "$(physical "$_a_dir")" "the real path of $_a_dir"
983
+ done || exit 2
984
+ dirs_to_make=
925
985
  # The record's folder is made before anything else is written: when it cannot be made,
926
986
  # the run stops here instead of leaving links and a block that no record describes. It
927
987
  # counts among the folders the setup made: a home that does not exist yet is made for
@@ -972,8 +1032,43 @@ install() {
972
1032
  elif [ "$wrote" = 1 ]; then
973
1033
  new_target=$target new_backup=$made_backup new_hash=$(sha256 "$target")
974
1034
  fi
975
- if [ -n "$created" ] || [ "$wrote" = 1 ] || { [ "$record" = 1 ] && [ "$new_hash" != "$st_hash" ]; }; then
976
- new_created=$(printf '%s\n' $st_created $created | LC_ALL=C sort -u | tr '\n' ' ')
1035
+ # The retired links the record lists go last, after every other write. Each is moved into
1036
+ # a private folder and checked there; one that changed after the check goes back as it is.
1037
+ # They come back if the record cannot be saved, and are deleted only once it is.
1038
+ retired_claims= retirement_error= retirement_restore_error=
1039
+ for _a_name in $to_retire; do
1040
+ _a_link=$skills_dir/$_a_name
1041
+ is_exact_legacy_link "$_a_link" "$_a_name" || continue
1042
+ _a_text=$(readlink "$_a_link") || continue
1043
+ if ! _a_claim=$(mktemp -d "$skills_dir/.nobrainer-retire-XXXXXX"); then
1044
+ retirement_error=1
1045
+ break
1046
+ fi
1047
+ if ! mv "$_a_link" "$_a_claim/$_a_name" 2>/dev/null; then
1048
+ rmdir "$_a_claim"
1049
+ continue
1050
+ fi
1051
+ if [ -L "$_a_claim/$_a_name" ] && [ "$(readlink "$_a_claim/$_a_name")" = "$_a_text" ]; then
1052
+ retired_claims="$retired_claims$_a_claim/$_a_name
1053
+ "
1054
+ else
1055
+ if ! unclaim "$_a_claim/$_a_name" "$_a_link"; then
1056
+ retirement_error=1 retirement_restore_error=1
1057
+ break
1058
+ fi
1059
+ err "PRESERVED: $_a_link changed during the setup and is left as it is"
1060
+ fi
1061
+ done
1062
+ if [ -n "$created" ] || [ -n "$to_retire" ] || [ "$wrote" = 1 ] || { [ "$record" = 1 ] && [ "$new_hash" != "$st_hash" ]; }; then
1063
+ # A retired module's link is gone once migrated; then the record drops its name.
1064
+ _k_created=
1065
+ for _k_name in $st_created; do
1066
+ case " $RETIRED " in
1067
+ *" $_k_name "*) owns_link "$skills_dir/$_k_name" "$_k_name" || continue ;;
1068
+ esac
1069
+ _k_created="$_k_created $_k_name"
1070
+ done
1071
+ new_created=$(printf '%s\n' $_k_created $created | LC_ALL=C sort -u | tr '\n' ' ')
977
1072
  new_created=${new_created# }
978
1073
  new_created=${new_created% }
979
1074
  # Of the folders this run meant to create, the ones that exist now, with the ones an
@@ -997,7 +1092,7 @@ install() {
997
1092
  # makes a folder, and its undo keeps the rule it was made under.
998
1093
  want_dirs=1
999
1094
  if [ "$record" = 1 ] && [ -z "$st_has_dirs" ] && [ -z "$new_dirs" ]; then want_dirs=; fi
1000
- if ! write_state; then
1095
+ if [ -n "$retirement_error" ] || ! write_state; then
1001
1096
  for _a_done in $created; do remove_link "$skills_dir/$_a_done"; done
1002
1097
  if [ "$wrote" = 1 ]; then
1003
1098
  # Through a copy beside it and a rename, so a read-only file comes back as well.
@@ -1008,9 +1103,21 @@ install() {
1008
1103
  rm -f "$target"
1009
1104
  fi
1010
1105
  fi
1106
+ if ! printf '%s' "$retired_claims" | while IFS= read -r _a_claim; do
1107
+ unclaim "$_a_claim" "$skills_dir/${_a_claim##*/}" || exit 1
1108
+ done; then
1109
+ die 4 "could not save the setup record $state; rollback incomplete; recovery claims were preserved"
1110
+ fi
1111
+ if [ -n "$retirement_restore_error" ]; then
1112
+ die 4 "retired link restore failed; rollback incomplete; recovery claims were preserved"
1113
+ fi
1011
1114
  die 4 "could not save the setup record $state; the changes of this run were reversed"
1012
1115
  fi
1013
1116
  fi
1117
+ printf '%s' "$retired_claims" | while IFS= read -r _a_claim; do
1118
+ remove_link "$_a_claim" && rmdir "${_a_claim%/*}" 2>/dev/null
1119
+ say "REMOVED_RETIRED: $skills_dir/${_a_claim##*/}"
1120
+ done
1014
1121
 
1015
1122
  # A home made for a record that was not needed goes again.
1016
1123
  if [ ! -e "$state" ] && [ -n "$made_home" ]; then
@@ -1019,7 +1126,7 @@ install() {
1019
1126
 
1020
1127
  # New links and a written instruction file are what a client has to be restarted for.
1021
1128
  changed=0
1022
- if [ "$link_count" -gt 0 ] || [ "$wrote" = 1 ]; then changed=1; fi
1129
+ if [ "$link_count" -gt 0 ] || [ "$wrote" = 1 ] || [ -n "$retired_claims" ]; then changed=1; fi
1023
1130
  if [ "$changed" = 1 ]; then
1024
1131
  say "INSTALLED: $source_line for $label"
1025
1132
  else
@@ -1201,7 +1308,7 @@ undo() {
1201
1308
  for _u_name in $st_created; do
1202
1309
  _u_link=$st_dest/$_u_name
1203
1310
  if [ -L "$_u_link" ]; then
1204
- if ! is_our_link "$_u_link" "$root/skills/$_u_name"; then
1311
+ if ! owns_link "$_u_link" "$_u_name"; then
1205
1312
  say "PRESERVED: changed target $_u_link"
1206
1313
  exit 3
1207
1314
  fi
@@ -1238,7 +1345,7 @@ undo() {
1238
1345
  _u_removed=0
1239
1346
  for _u_name in $st_created; do
1240
1347
  _u_link=$st_dest/$_u_name
1241
- if is_our_link "$_u_link" "$root/skills/$_u_name"; then
1348
+ if owns_link "$_u_link" "$_u_name"; then
1242
1349
  remove_link "$_u_link" || die 4 "could not remove $_u_link"
1243
1350
  _u_removed=$((_u_removed + 1))
1244
1351
  fi
@@ -1283,9 +1390,23 @@ undo() {
1283
1390
  rm -f "$state"
1284
1391
  # The record is gone, so the folder it sat in may be empty now. A folder that was there
1285
1392
  # before the setup is not listed, and a folder that holds anything stays.
1393
+ # Two entries that lead to one folder cannot both be what the setup made, and the record
1394
+ # cannot say which one is, so no entry of such a group is removed. The groups are found
1395
+ # before any folder goes.
1286
1396
  _u_gone=
1287
1397
  if [ -n "$st_dirs" ]; then
1398
+ _u_dupes=$(printf '%s' "$st_dirs" | while IFS= read -r _u_dir; do physical "$_u_dir"; done | LC_ALL=C sort | uniq -d)
1399
+ _u_shared=
1400
+ if [ -n "$_u_dupes" ]; then
1401
+ _u_shared=$(printf '%s' "$st_dirs" | while IFS= read -r _u_dir; do
1402
+ if printf '%s\n' "$_u_dupes" | grep -q -x -F -e "$(physical "$_u_dir")"; then printf '%s\n' "$_u_dir"; fi
1403
+ done)
1404
+ fi
1288
1405
  _u_gone=$(printf '%s' "$st_dirs" | LC_ALL=C sort -r | while IFS= read -r _u_dir; do
1406
+ if [ -n "$_u_shared" ] && printf '%s\n' "$_u_shared" | grep -q -x -F -e "$_u_dir"; then
1407
+ printf 'PRESERVED: %s: another folder in the record leads to the same folder, so it is left as it is\n' "$_u_dir"
1408
+ continue
1409
+ fi
1289
1410
  if [ ! -d "$_u_dir" ] || [ -L "$_u_dir" ]; then continue; fi
1290
1411
  if under_link "$_u_dir"; then
1291
1412
  printf 'PRESERVED: %s: a folder above it is a link now, so it is left as it is\n' "$_u_dir"
@@ -73,7 +73,7 @@ except ValueError:
73
73
 
74
74
  CURATED_SKILLS = frozenset(
75
75
  {
76
- "nobrainer-codex-context",
76
+ "nobrainer-context",
77
77
  "nobrainer-skill-doctor",
78
78
  "nobrainer-autoimprove",
79
79
  "nobrainer-auto-fine-tune",
@@ -104,6 +104,7 @@ LEGACY_TO_CANONICAL = {
104
104
  "agents-restraint": "nobrainer-tech-flow",
105
105
  "codex-in-claude-code": "nobrainer-tech-flow",
106
106
  "code-autoresearch": "nobrainer-autoimprove",
107
+ "nobrainer-codex-context": "nobrainer-context",
107
108
  "deep-audit": "nobrainer-review",
108
109
  "deep-autoreview": "nobrainer-review",
109
110
  "deep-autoresearch": "nobrainer-autoimprove",
@@ -149,9 +150,11 @@ LEGACY_TO_CANONICAL = {
149
150
  "wiki-get": "nobrainer-wiki",
150
151
  "wiki-tidy": "nobrainer-wiki",
151
152
  }
152
- # Reddit integration was removed, not absorbed into a canonical skill. Keep its
153
- # owned-link migration, but a different source may legitimately use this name.
154
- RETIRED_LEGACY = frozenset({"nobrainer-reddit"})
153
+ # Names Flow no longer ships: Reddit integration was removed, and
154
+ # nobrainer-codex-context was renamed to nobrainer-context in 2.5.0 without an
155
+ # alias. Keep their owned-link migration, but another source may legitimately
156
+ # use these names, so a foreign target there is preserved, never removed.
157
+ RETIRED_LEGACY = frozenset({"nobrainer-codex-context", "nobrainer-reddit"})
155
158
  UNMAPPED_LEGACY = frozenset({"nobrainer-fast-audit"})
156
159
 
157
160
 
@@ -455,6 +458,10 @@ def legacy_name_for(target: Path, canonical: Path) -> str | None:
455
458
  return None
456
459
 
457
460
 
461
+ class ClaimRestoreError(RuntimeError):
462
+ """A changed entry was claimed but could not be restored to its original name."""
463
+
464
+
458
465
  def claim_legacy_link(
459
466
  target: Path, legacy_name: str
460
467
  ) -> tuple[Path, EntryFingerprint]:
@@ -484,7 +491,7 @@ def claim_legacy_link(
484
491
  try:
485
492
  restore_claim(target, claim)
486
493
  except RuntimeError as exc:
487
- raise RuntimeError(
494
+ raise ClaimRestoreError(
488
495
  f"legacy link changed after preflight; {exc}"
489
496
  ) from exc
490
497
  raise RuntimeError(
@@ -683,6 +690,17 @@ def parse_args() -> argparse.Namespace:
683
690
  "root-level layout and renamed aliases."
684
691
  ),
685
692
  )
693
+ parser.add_argument(
694
+ "--recorded-retired",
695
+ action="append",
696
+ default=[],
697
+ metavar="NAME",
698
+ help=(
699
+ "A retired name that the calling setup's record lists as created by it: "
700
+ "its exact link into this checkout no longer blocks installation and is "
701
+ "left in place for that setup to retire once its whole run has succeeded."
702
+ ),
703
+ )
686
704
  return parser.parse_args()
687
705
 
688
706
 
@@ -748,6 +766,14 @@ def main() -> int:
748
766
  if legacy_name in RETIRED_LEGACY
749
767
  else "conflict"
750
768
  )
769
+ if (
770
+ state == "legacy"
771
+ and legacy_name in RETIRED_LEGACY
772
+ and legacy_name in args.recorded_retired
773
+ ):
774
+ # The setup record proves the setup made this link. The setup retires it
775
+ # after its own run succeeds; this helper never deletes it.
776
+ state = "recorded"
751
777
  alias_plan.append((legacy_name, canonical_name, target, state))
752
778
  if state == "conflict" or (state == "legacy" and not args.migrate_legacy):
753
779
  conflicts.append(target)
@@ -774,6 +800,9 @@ def main() -> int:
774
800
  print(f"{action}: {name}: {source} -> {target}")
775
801
 
776
802
  for legacy_name, canonical_name, target, state in alias_plan:
803
+ if state == "recorded":
804
+ print(f"RETIRED_RECORDED: {legacy_name}: made by this setup; renamed {canonical_name}: {target}")
805
+ continue
777
806
  if state == "preserved":
778
807
  print(
779
808
  f"PRESERVED_FOREIGN: {legacy_name}: retired from nobrainer-tech-flow; "