-
-
Notifications
You must be signed in to change notification settings - Fork 219
Expand file tree
/
Copy path.facts
More file actions
1087 lines (1006 loc) · 112 KB
/
Copy path.facts
File metadata and controls
1087 lines (1006 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
- app > Harbor App setup gate shows heading 'Almost done' with status refresh-required when harbor doctor --check fails (Docker daemon down or not yet in group), with error text distinguishing the two sub-cases; there is no separate 'Environment issue' heading @spec @setup-gate-docker-down-copy @implemented
- install > webui first boot > compose.webui.yml healthcheck grants a 15m start_period so first-boot model downloads keep the container in 'starting' (not 'unhealthy') and 'up -d --wait' does not fail mid-download @spec @webui-first-boot @implemented
- install > webui first boot > run_up prints a first-start notice about embedding/audio model downloads when webui is being started and services/webui/cache/embedding does not exist @spec @webui-first-boot @implemented
- install > webui first boot > run_open waits (up to 900s) for a container in health status 'starting' to become healthy before opening the browser, warning if it turns unhealthy @spec @webui-first-boot @implemented
- label: services > webui x searxng > config.searxng.json seeds Open WebUI's current web.search.* config keys (enable, engine searxng, searxng_query_url from SEARXNG_QUERY_URL env) instead of the legacy rag.web.search.* prefix that Open WebUI's rename_prefix migration discards when web.search.* rows already exist
command: python3 -c "import json; c=json.load(open('services/webui/configs/config.searxng.json')); w=c['web']['search']; assert w['enable'] is True and w['engine']=='searxng' and w['searxng_query_url']=='$'+'{SEARXNG_QUERY_URL}' and 'web' not in c.get('rag',{}), c" && echo ok
tags: [spec, core-integration, implemented]
- label: services > webui x searxng x ollama > config.x.searxng.ollama.json sets rag.ollama.base_url to HARBOR_OLLAMA_INTERNAL_URL (not Open WebUI's host.docker.internal default) and uses the auto-pulled nomic-embed-text:latest embedding model
command: python3 -c "import json; r=json.load(open('services/webui/configs/config.x.searxng.ollama.json'))['rag']; assert r['ollama']['base_url']=='$'+'{HARBOR_OLLAMA_INTERNAL_URL}' and r['embedding_model']=='nomic-embed-text:latest', r" && echo ok
tags: [spec, core-integration, implemented]
- services > dify > the dify-openai bridge translates OpenAI chat completions to Dify 1.x APIs: Chat via /v1/chat-messages, Workflow via /v1/workflows/run including the blocking-mode single-JSON response shape and OUTPUT_VARIABLE selection from workflow outputs @dify-1x @spec
# project
## domain
- a Harbor is a containerized LLM toolkit: Bash CLI, Docker Compose service orchestration, and optional Tauri desktop app
- a Service is a named Harbor handle with a compose file under services/ and metadata in the app catalog
- a Backend is a Service that exposes an OpenAI-compatible inference API for other services and host tools
- a Satellite is a Service that depends on a Backend or external keys and is wired through cross-compose integrations
- a ComposeIntegration is a compose.x.<consumer>.<provider>.yml overlay applied when both handles are in the active set
- a Launch routes harbor launch to either a host coding-tool adapter or a containerized service CLI
- a Host tool is an installed agent CLI on the host PATH that Harbor configures with backend URL, API key, and model
- a Boost is Harbor's LLM proxy that applies modules and workflows to chat completions and compat API surfaces
- a Workflow is a named ordered list of Boost module steps advertised as workflow-prefixed model IDs
- a Module is a Boost Python plugin with an ID_PREFIX that transforms chat via async apply(chat, llm)
- Harbor merges compose.yml, per-service compose files, capability overlays, and cross-compose files into __harbor.yml
- a Profile is a named snapshot of workspace .env configuration under the Harbor home directory
- Satellite uses Backend through ComposeIntegration depends_on, env vars, and mounted config templates
## distribution
- label: the npm package name is @avcodes/harbor
command: jq -e -r '.name' package.json | grep -q '@avcodes/harbor'
- label: the PyPI package name is llm-harbor
command: grep -q 'name = "llm-harbor"' pyproject.toml
- label: version is synchronized across package.json, pyproject.toml, and app/package.json
command: V=$(jq -r .version package.json) && grep -q "version = \"$V\"" pyproject.toml && jq -e --arg v "$V" ".version == \$v" app/package.json >/dev/null
- label: install.sh and requirements.sh handle platform-specific installation and dependency detection
command: test -f install.sh && test -f requirements.sh
- Harbor is a containerized LLM toolkit distributed as a Docker Compose project with a CLI and Tauri desktop app
- label: the project is licensed under Apache 2.0
command: head -1 LICENSE | grep -q 'Apache'
- label: install.sh verifies the install with harbor doctor --check so non-critical doctor warnings do not fail setup
command: rg -q 'doctor --check' install.sh
tags: [implemented]
## development
- label: local Deno runtime caches under .deno-cache are ignored by Git
command: git check-ignore -q .deno-cache/latest.txt
tags: [spec, implemented]
# cli
- label: harbor.sh is the main CLI entrypoint, a Bash script over 5000 lines
command: test -x harbor.sh && test $(wc -l < harbor.sh) -ge 5000
- label: CLI internals are rewritten in Deno TypeScript under routines/
command: test -f routines/deno.json && test $(find routines -name '*.ts' | wc -l) -ge 10
- label: dev scripts live in .scripts/ and must be run via harbor dev, not directly
command: test -d .scripts && test $(find .scripts -name '*.ts' -o -name '*.sh' | wc -l) -ge 20
- label: harbor config get/set/search/update reads and writes workspace .env; defaults live in profiles/default.env
command: grep -q 'env_manager' harbor.sh && test -f profiles/default.env
- label: harbor config update merges profiles/default.env into the active .env
command: grep -q 'config_update' harbor.sh || grep -q 'config update' harbor.sh
- label: harbor compose up uses docker compose up -d --wait requiring Compose 2.23.1 or newer
command: grep -q 'up -d --wait' harbor.sh && grep -q 'desired_compose_minor="23"' harbor.sh
tags: [implemented]
- label: harbor logs tails container output by default and can hang unattended agents
command: grep -q -- "logs -n \"\$tail_lines\" -f" harbor.sh
tags: [implemented]
- label: harbor.sh avoids Bash 4 case-conversion parameter expansion for macOS Bash 3 compatibility
command: bash -n harbor.sh && ! rg -q '\$\{[^}]+(\^\^|,,)' harbor.sh
tags: [implemented]
- label: harbor doctor --check exits nonzero only for critical environment issues
command: rg -q 'check_mode=true' harbor.sh && rg -q 'has_critical' harbor.sh
tags: [implemented]
- when compose_with_options fails in get_services, harbor ls exits nonzero instead of executing a bare 'config' word @get-services-guard @implemented
- label: harbor doctor SELinux AVC probe reads audit logs instead of blocking on noninteractive stdin
command: timeout 5 bash -lc 'cd /home/everlier/code/harbor && tail -f /dev/null | harbor doctor >/tmp/harbor-doctor-stdin.out 2>/tmp/harbor-doctor-stdin.err'
tags: [spec, app-doctor-stdin, implemented]
- bare harbor open resolves ui.main instead of reporting no service specified @spec @implemented
- bare harbor tunnel resolves ui.main instead of reporting no service specified @spec @implemented
- _resolve_ui_service centralizes default UI target resolution for open, url, and tunnel @spec @implemented
- harbor up --open opens the first started service instead of ui.main when no separate open target is given @spec @implemented
- _resolve_ui_service reads ui.main at call time and validates the resolved service exists @spec @implemented
- harbor qr documents and resolves ui.main like harbor url @spec @implemented
- label: harbor up --open targets the first started service after defaults and --no-defaults are resolved
command: rg -q 'open_target="\$\{display_services\[0\]:-\}"' harbor.sh
tags: [spec, implemented]
- label: harbor doctor --check reports Docker, Compose, Harbor home, git, curl, and git-repository status before returning, while only critical environment failures make it exit nonzero
command: out="$(bash ./harbor.sh doctor --check 2>&1)" && rc=$? || rc=$?; [ "$rc" -eq 0 ] && echo "$out" | grep -q "Docker is installed" && echo "$out" | grep -q "Compose" && echo "$out" | grep -q "Harbor home" && echo "$out" | grep -q "git is installed" && echo "$out" | grep -q "curl is installed" && echo "$out" | grep -q "essential checks passed" && grep -q "not a git repository" harbor.sh
tags: [spec, release-validation, implemented]
- label: host deno invocations set DENO_NO_UPDATE_CHECK=1 to suppress the update-check nag and its dl.deno.land telemetry
command: test $(grep -c 'DENO_NO_UPDATE_CHECK=1 deno run' harbor.sh) -eq 3
tags: [spec, deno-no-update-check, implemented]
- label: when compose_with_options fails, run_pull returns 1 instead of executing a bare 'pull' word
command: grep -q 'compose_cmd=$(compose_with_options) || return 1' harbor.sh && grep -q 'compose_cmd=$(compose_with_options "${flag_args\[@\]}" "${service_args\[@\]}") || return 1' harbor.sh
tags: [spec, implemented, pull-resolver-guard]
- label: when the compose resolver routine fails or returns empty, compose_with_options emits a 'false' sentinel command so bare $(compose_with_options ...) call sites fail cleanly instead of executing a bare docker subcommand
command: grep -A6 'The compose file merge routine produced no output' harbor.sh | grep -q 'echo "false"'
tags: [spec, implemented, pull-resolver-guard]
- label: harbor up with multiple services and -t tails logs for all requested services, not just the first
command: rg -q "run_logs \"\\\$\\{filtered_args\\[@\\]\\}\"" harbor.sh
tags: [spec, implemented]
- label: harbor env <service> accepts --rm and --unset as aliases for unset: removal deletes the KEY line from services/<service>/override.env and never writes a literal __RM= entry
command: bash -c 'echo TESTKEY=1 >> services/webtop/override.env; ./harbor.sh env webtop --rm TESTKEY >/dev/null 2>&1; ! grep -q "__RM\|TESTKEY" services/webtop/override.env'
tags: [spec, env-fix, implemented]
## launch
- label: harbor launch routes host tools codex, claude, opencode, pi, and others to OpenAI-compatible Harbor backends with optional --backend, --model, --config, --web, and --workflow
command: rg -q 'launch_host_tool_command' harbor.sh && rg -q 'launch_detect_backend' harbor.sh && rg -q -- '--web' harbor.sh && rg -q -- '--workflow' harbor.sh
tags: [implemented]
- label: harbor launch --service forces the containerized Harbor service instead of the host tool when names collide
command: rg -q 'force_service_launch' harbor.sh && rg -q 'harbor launch --service opencode' docs/3.-Harbor-CLI-Reference.md
tags: [implemented]
- label: harbor launch starts llamacpp as the default backend when no OpenAI-compatible Harbor backend is running
command: rg -q 'starting llamacpp' harbor.sh
tags: [implemented]
- label: harbor launch --web starts Boost and SearXNG and configures a boost-prefixed workflow model
command: rg -q 'launch_prepare_boost_workflow' harbor.sh && rg -q 'searxng' harbor.sh
tags: [implemented]
- label: harbor launch validates the host tool binary before starting backend or Boost services
command: rg -q 'started compose before validating the host binary' tests/suites/02-cli.sh
tags: [implemented]
- label: harbor launch exposes --web and --workflow as Boost routing modifiers and rejects removed groups such as --time and --notes
command: ./harbor.sh launch --help | rg -q -- '--web' && ./harbor.sh launch --help | rg -q -- '--workflow' && ! ./harbor.sh launch --help | rg -q -- '--time|--notes|--files|--scratch'
tags: [implemented]
- label: harbor launch --workflow accepts Boost module names but does not advertise built-in workflow presets when none ship
command: ./harbor.sh launch --help | rg -q 'Boost module' && ./harbor.sh launch --help | rg -q 'No built-in workflow presets ship by default' && ! ./harbor.sh launch --help | rg -q 'workflow presets:'
tags: [spec, reported-regression, implemented]
- label: harbor launch prefers a reachable backend listed in services.default over other running backends when --backend is not given
command: grep -q 'launch_default_backends' harbor.sh && grep -A4 'launch_detect_backend()' harbor.sh >/dev/null
tags: [spec, implemented]
## models
- label: harbor models supports source-aware list, pull, and rm for ollama, llamacpp, dmr, mlx, and omlx
command: rg -q 'run_models_routine' harbor.sh && rg -q 'dmr' routines/models.ts && rg -q 'mlx' routines/models.ts && rg -q 'omlx' routines/models.ts
tags: [implemented]
## compose-merge
- label: compose environment-list merging is key-aware: a later cross-file's KEY=VALUE entry replaces an earlier same-KEY entry even when byte-identical, so .a.b tie-breaker files take effect
command: grep -q 'mergeEnvironmentArrays' routines/utils.ts
tags: [implemented, bughunt]
- label: compose deepMerge normalizes list-form depends_on to map form when merging, so cross-file depends_on never drops init-sidecar completion conditions
command: deno eval --unstable-sloppy-imports 'import {deepMerge} from "./routines/utils.ts"; const r: any = deepMerge({depends_on:{i:{condition:"service_completed_successfully"}}} as any, {depends_on:["o"]} as any); if (!r.depends_on?.i) Deno.exit(1);'
tags: [implemented, bughunt]
## pull
- label: harbor pull with mixed service and model arguments passes only model arguments to the model pull path
command: rg -q 'model_args\+\=' harbor.sh && rg -q 'Mixed service and model arguments' harbor.sh && rg -q 'run_models_pull "\$\{model_args\[@\]\}"' harbor.sh
tags: [spec, reported-regression, implemented]
## up
- label: service_compose_exists accepts cross-file-only selectors (tokens appearing as parts of compose.x.* filenames, e.g. mcp-server-time), keeping the documented 'harbor up mcpo mcp-server-time' flow working
command: bash -c 'source /dev/null; ./harbor.sh cmd mcpo mcp-server-time >/dev/null'
tags: [services-it, implemented]
# services
- label: compose.yml defines only the shared harbor-network without service definitions
command: grep -q 'harbor-network' compose.yml && ! grep -q '^services:' compose.yml
- label: each Harbor service has a compose file at services/compose.<handle>.yml
command: test $(find services -maxdepth 1 -name 'compose.*.yml' ! -name 'compose.x.*' | wc -l) -ge 100
- label: cross-service integrations use compose.x.<consumer>.<backend>.yml files selected when both handles are active
command: test $(find services -maxdepth 1 -name 'compose.x.*.yml' | wc -l) -ge 300
- label: profiles/default.env contains over 800 HARBOR configuration keys
command: test $(grep -c '^HARBOR_' profiles/default.env) -ge 800
- label: default services on harbor up without handles are webui and llamacpp
command: grep -q 'HARBOR_SERVICES_DEFAULT="webui;llamacpp"' profiles/default.env
tags: [implemented]
- label: dmr mlx and omlx are host-managed backends proxied by Caddy containers on the harbor network
command: test -f services/compose.dmr.yml && test -f services/compose.mlx.yml && test -f services/compose.omlx.yml && test -f services/dmr/Caddyfile
tags: [implemented]
- label: harbor up dmr mlx or omlx starts the host runner before bringing up the proxy container
command: rg -q 'run_dmr_command' harbor.sh && rg -q 'run_mlx_command' harbor.sh && rg -q 'run_omlx_command' harbor.sh
tags: [implemented]
- label: ml-intern is a satellite with cross-compose integrations for ollama llamacpp vllm dmr mlx and omlx
command: test -f services/compose.ml-intern.yml && test -f services/compose.x.ml-intern.omlx.yml
tags: [implemented]
- label: mi is a CLI satellite launched via harbor mi and harbor launch with compose.x integrations for backends
command: test -f services/compose.mi.yml && rg -q 'run_mi_command' harbor.sh
tags: [implemented]
- label: facts is a CLI satellite run via harbor facts with the caller workspace bind-mounted
command: test -f services/compose.facts.yml && rg -q 'run_facts_command' harbor.sh
tags: [implemented]
- label: agent skills and install docs describe defaults as webui plus llamacpp, not ollama
command: grep -q 'llamacpp` | Port: 33831 | Default service' skills/run-llms/SKILL.md && ! grep -q 'Ollama + Open WebUI' docs/1.0.-Installing-Harbor.md && ! grep -rq 'Port: 33821 | Default service' skills/
tags: [implemented]
## hermes
- label: hermes ships a non-empty default API key (sk-hermes) so the gateway API server never runs with auth disabled
command: grep -q 'HARBOR_HERMES_API_KEY="sk-hermes"' profiles/default.env && grep -q 'hermes.api_key' .scripts/migrations/0.5.0.ts
tags: [implemented, bughunt]
- label: harbor hermes help prints usage and returns without proxying to the container
command: grep -A2 'Any other Hermes CLI command' harbor.sh | grep -q 'return 0'
tags: [implemented, bughunt]
- label: hermes docs do not advertise the nonexistent 'harbor hermes server' subcommand; the API server starts automatically with harbor up
command: ! grep -rq 'harbor hermes server' docs/
tags: [implemented, bughunt]
## searxng
- label: shipped settings.yml passes SearXNG engine about-schema validation: every engine about section uses require_api_key (no requires_api_key typo)
command: ! grep -q 'requires_api_key' services/searxng/settings.yml
tags: [spec, implemented]
## langflow
- label: langflow data bind mount is chowned to the host user by a langflow-init sidecar before langflow starts (compose.langflow.yml depends_on langflow-init service_completed_successfully; services/langflow/workspace-init.sh)
command: grep -q 'langflow-init' services/compose.langflow.yml && test -f services/langflow/workspace-init.sh
tags: [spec, implemented]
## librechat
- label: librechat-rag waits for librechat-vector (pgvector) readiness: compose.librechat.yml has a pg_isready healthcheck on librechat-vector and librechat-rag depends_on it with condition service_healthy, preventing first-boot 'connection refused' crash
command: grep -q pg_isready services/compose.librechat.yml && grep -q 'service_healthy' services/compose.librechat.yml
tags: [spec, implemented]
- label: the Ollama custom endpoint baseURL in librechat.yml is the API root (.../v1) without a /chat/completions suffix — LibreChat's client appends the path itself and 404s on a full path
command: grep -q 'INTERNAL_URL}/v1"' services/librechat/librechat.yml && ! grep -q 'INTERNAL_URL}/v1/chat/completions' services/librechat/librechat.yml
tags: [spec, services-it, implemented]
- label: compose.librechat.yml pins getmeili/meilisearch:v1.35.1 with data mounted at services/librechat/meili_data_v1.35 (version-suffixed dir so bumps start a fresh index)
command: grep -q 'meilisearch:v1.35.1' services/compose.librechat.yml && grep -q 'meili_data_v1.35:/meili_data' services/compose.librechat.yml
tags: [implemented]
## fabric
- label: fabric config mounts into the image user's real home (/home/appuser/.config/fabric, image runs as appuser not root) and the entrypoint creates an empty .env if missing, since fabric hard-fails without one even when vendor/model come from environment
command: grep -q '/home/appuser/.config/fabric' services/compose.fabric.yml && grep -q 'touch /home/appuser/.config/fabric/.env' services/compose.fabric.yml
tags: [spec, implemented]
## cmdh
- label: cmdh's ollama adapter passes a literal JSON schema as the structured-output format (no zod-to-json-schema dependency), because zod-to-json-schema v3 with zod v4 silently produces an empty schema that Ollama rejects with 'invalid JSON schema in format'
command: grep -q 'format: CmdhResponseSchema' services/cmdh/ollama.ts && ! grep -q "from 'zod-to-json-schema'" services/cmdh/ollama.ts
tags: [spec, implemented]
- When LLM_HOST is OpenAI and OPENAI_MODEL_NAME is unset, cmdh's openai adapter (services/cmdh/openai.ts, mounted over the upstream source) resolves the model by listing the backend's /v1/models and using the first entry, so OpenAI-mode cross-files (llamacpp, dmr, mlx, omlx, tgi) work without an explicit model name @spec @implemented
## plandex
- label: services/plandex/Dockerfile installs the plandex CLI from the GitHub raw install script (app/cli/install.sh), not the dead plandex.ai domain
command: grep -q 'raw.githubusercontent.com/plandex-ai/plandex/main/app/cli/install.sh' services/plandex/Dockerfile
tags: [spec, implemented]
- label: plandex-db pins postgres:17 — postgres:18+ images refuse the /var/lib/postgresql/data bind-mount layout used by Harbor
command: grep -q 'image: postgres:17' services/compose.plandex.yml
tags: [spec, implemented]
- label: plandex-server uses the official plandexai/plandex-server image (the ghcr.io/wipash rolling image crashes: bundled litellm launch fails on missing uvicorn)
command: grep -q 'plandexai/plandex-server' services/compose.plandex.yml
tags: [spec, implemented]
- label: compose.plandex.yml does not bind-mount /etc/timezone (absent on Fedora-family hosts; docker creates it as a directory and the mount then fails against the image's file)
command: ! grep -q -- '- /etc/timezone' services/compose.plandex.yml
tags: [spec, implemented]
- label: plandex-server port mapping and the CLI's PLANDEX_API_HOST use port 8099 (official image in GOENV=development listens on 8099, not 8080)
command: grep -q 'plandex-server:8099' services/compose.plandex.yml && grep -q ':8099$' services/compose.plandex.yml
tags: [spec, implemented]
- label: H4 plandex health probe allows 300s — first boot does fresh postgres init + LiteLLM bootstrap + migrations before the listener opens
command: grep -qE 'H4 plandex server health.+plandex-server./health.+ 300$' tests/services-integration.sh
tags: [implemented]
## webtop
- label: services/webtop/Dockerfile does not install neofetch (package removed from current Ubuntu base of the webtop image; it broke the build)
command: ! grep -q neofetch services/webtop/Dockerfile
tags: [spec, implemented]
- label: services/webtop/Dockerfile installs node via NodeSource (bundles npm; the legacy npmjs.org/install.sh bootstrap is defunct and distro nodejs/npm conflict) and installs yarn via npm
command: grep -q 'deb.nodesource.com' services/webtop/Dockerfile && ! grep -q 'RUN curl -L https://npmjs.org/install.sh' services/webtop/Dockerfile
tags: [spec, implemented]
## litellm
- label: a litellm.llamacpp config fragment plus compose.x.litellm.llamacpp.yml overlay route llamacpp/* requests through LiteLLM to the llama.cpp router via wildcard routing
command: grep -q 'llamacpp/\*' services/litellm/litellm.llamacpp.yaml && grep -q litellm.llamacpp.yaml services/compose.x.litellm.llamacpp.yml
tags: [services-runner, implemented]
- label: litellm.optillm.yaml routes via provider wildcard (optillm/* -> openai/* at http://optillm:8000/v1) instead of a pinned static model, so any backend model reaches optillm through LiteLLM as optillm/<model>
command: grep -q 'model_name: "optillm/\*"' services/litellm/litellm.optillm.yaml && grep -q 'model: "openai/\*"' services/litellm/litellm.optillm.yaml
tags: [services-it, implemented]
## boost
- label: ChatNode.history() merges consecutive same-role plain messages (no tool calls) so multi-assistant-turn modules like g1 pass llama.cpp's 'cannot have 2+ assistant messages at the end' validation
command: cd services/boost && python -m pytest tests/test_chat_node.py -q -k merge
tags: [services-runner, implemented]
## chatui
- label: envify.js bridges Harbor's legacy MODELS config to chat-ui >= 0.10's single-provider scheme by emitting OPENAI_BASE_URL and OPENAI_API_KEY from the first configured openai endpoint, so per-backend configs (chatui.llamacpp.yml etc.) still take effect
command: grep -q OPENAI_BASE_URL services/chatui/envify.js
tags: [spec, services-it, implemented]
## traefik
- label: traefik.config default is ./services/traefik/traefik.yml so the compose bind mount resolves to the shipped config (repo-root-relative convention)
command: grep -q 'HARBOR_TRAEFIK_CONFIG="./services/traefik/traefik.yml"' profiles/default.env
tags: [services-it, implemented]
## drawio
- label: drawio ollama overlay sets OLLAMA_BASE_URL to the native Ollama API root (${HARBOR_OLLAMA_INTERNAL_URL}/api) because next-ai-draw-io uses ollama-ai-provider (createOllama), which 404s against the OpenAI-compatible /v1 root
command: grep -q 'OLLAMA_INTERNAL_URL}/api' services/compose.x.drawio.ollama.yml && ! grep -q 'OLLAMA_INTERNAL_URL}/v1' services/compose.x.drawio.ollama.yml
tags: [services-it, implemented]
## anythingllm
- label: anythingllm-init sidecar chowns the storage bind mount to the image's fixed uid 1000 before anythingllm starts, fixing the first-boot crash-loop (SQLite 'unable to open database file' on a root-owned storage dir)
command: grep -q 'anythingllm-init' services/compose.anythingllm.yml && test -f services/anythingllm/storage-init.sh
tags: [services-it, implemented]
- label: anythingllm ollama overlay sets EMBEDDING_MODEL_PREF=nomic-embed-text:latest (part of the ollama default pull) — the ollama embedder aborts every chat with 'No embedding model was set' when the pref is missing
command: grep -q 'EMBEDDING_MODEL_PREF=nomic-embed-text' services/compose.x.anythingllm.ollama.yml
tags: [services-it, implemented]
## ldr
- label: compose.x.ldr.searxng.yml sets LDR_SEARCH_ENGINE_WEB_SEARXNG_DEFAULT_PARAMS_INSTANCE_URL (LDR's env mapping for the searxng instance_url setting) instead of the stale SEARXNG_INSTANCE/LDR_SEARCH__TOOL names, so LDR web research actually queries SearXNG
command: grep -q 'LDR_SEARCH_ENGINE_WEB_SEARXNG_DEFAULT_PARAMS_INSTANCE_URL' services/compose.x.ldr.searxng.yml && ! grep -q 'SEARXNG_INSTANCE=' services/compose.x.ldr.searxng.yml
tags: [spec, services-it, implemented]
- label: compose.ldr.yml mounts the ldr workspace data dir at /data (the image's LDR_DATA_DIR) so users and research history persist across container recreation
command: grep -q 'HARBOR_LDR_WORKSPACE}/data:/data' services/compose.ldr.yml
tags: [spec, services-it, implemented]
## presenton
- label: compose.x.presenton.ollama.yml points OLLAMA_URL at HARBOR_OLLAMA_INTERNAL_URL (compose-network ollama), not a hardcoded host port unreachable from inside the container
command: grep -q 'OLLAMA_URL=${HARBOR_OLLAMA_INTERNAL_URL}' services/compose.x.presenton.ollama.yml
tags: [spec, services-it, implemented]
- label: presenton auth is disabled by default via HARBOR_PRESENTON_DISABLE_AUTH -> DISABLE_AUTH so the API works out of the box (upstream 428s 'Login setup is required' otherwise)
command: grep -q 'DISABLE_AUTH=${HARBOR_PRESENTON_DISABLE_AUTH}' services/compose.presenton.yml && grep -q 'HARBOR_PRESENTON_DISABLE_AUTH="true"' profiles/default.env
tags: [spec, services-it, implemented]
## bifrost
- label: bifrost bootstrap-provider.sh verifies provider-key presence via GET /api/providers/<p>/keys (the provider object redacts keys), so re-running the bootstrap against a persisted config.db exits 0 instead of failing with 'record with this name already exists'
command: grep -q 'providers/$BIFROST_PROVIDER/keys' services/bifrost/bootstrap-provider.sh
tags: [services-it, implemented]
## optillm
- label: compose.optillm.yml sets OPTILLM_HOST=0.0.0.0 (list-form environment) because upstream optillm now binds 127.0.0.1 by default, which makes the published port unreachable
command: grep -q 'OPTILLM_HOST=0.0.0.0' services/compose.optillm.yml
tags: [services-it, implemented]
## metamcp
- label: metamcp start-sse.mjs seeds a default project/profile/API key when the api_keys table is empty, so 'harbor up metamcp' succeeds headlessly on a fresh database instead of crash-looping until the UI is opened
command: grep -q 'seeding default project/profile/key' services/metamcp/start-sse.mjs
tags: [services-it, implemented]
- label: metamcp-postgres uses postgres:16-alpine (floating 16.x patch tag, data-compatible within major 16)
command: grep -q 'postgres:16-alpine' services/compose.metamcp.yml
tags: [implemented]
## mcp-inspector
- label: compose.mcp-inspector.yml runs the inspector with HOST=0.0.0.0 and ALLOWED_ORIGINS for the remapped host port, with no socat entrypoint — the old socat forwarder self-connect-looped (inspector binds ::1, socat forwarded 127.0.0.1 into its own wildcard listener), leaving both published ports dead
command: grep -q 'HOST=0.0.0.0' services/compose.mcp-inspector.yml && ! grep -q 'inspector-entrypoint' services/compose.mcp-inspector.yml && [ ! -f services/mcp/inspector-entrypoint.sh ]
tags: [services-it, implemented]
## s2s
- s2s wraps huggingface/speech-to-speech, the modular VAD-STT-LLM-TTS realtime voice pipeline, as a Harbor backend service @spec
- label: s2s has a compose file at services/compose.s2s.yml defining an s2s service on the harbor network
command: test -f services/compose.s2s.yml && grep -q 'harbor-network' services/compose.s2s.yml
tags: [spec]
- label: s2s builds its image from the upstream repo's own Dockerfile via the HARBOR_S2S_GIT_REF build context since no official image is published
command: grep -q 'HARBOR_S2S_GIT_REF' services/compose.s2s.yml && grep -q 'HARBOR_S2S_GIT_REF' profiles/default.env
tags: [spec]
- label: s2s runs in realtime mode exposing the OpenAI Realtime-compatible WebSocket endpoint /v1/realtime on container port 8765, published via HARBOR_S2S_HOST_PORT
command: grep -q 'HARBOR_S2S_HOST_PORT' profiles/default.env && grep -q '8765' services/compose.s2s.yml
tags: [spec]
- label: s2s LLM backend defaults to responses-api pointed at an OpenAI-compatible endpoint, configurable via HARBOR_S2S_OPENAI_URL, HARBOR_S2S_OPENAI_KEY and HARBOR_S2S_MODEL
command: grep -q 'HARBOR_S2S_OPENAI_URL' profiles/default.env && grep -q 'HARBOR_S2S_OPENAI_KEY' profiles/default.env && grep -q 'HARBOR_S2S_MODEL' profiles/default.env
tags: [spec]
- label: s2s STT and TTS components are selectable via HARBOR_S2S_STT and HARBOR_S2S_TTS with upstream defaults (parakeet-tdt STT, qwen3 TTS)
command: grep -q 'HARBOR_S2S_STT' profiles/default.env && grep -q 'HARBOR_S2S_TTS' profiles/default.env
tags: [spec]
- label: s2s system prompt is configurable via HARBOR_S2S_SYSTEM_PROMPT passed as --init_chat_prompt with --init_chat_role system
command: grep -q 'HARBOR_S2S_SYSTEM_PROMPT' profiles/default.env && grep -q 'init_chat_prompt' services/compose.s2s.yml
tags: [spec]
- label: s2s extra pipeline CLI flags can be appended via HARBOR_S2S_EXTRA_ARGS
command: grep -q 'HARBOR_S2S_EXTRA_ARGS' profiles/default.env
tags: [spec]
- label: s2s persists HuggingFace model cache via the shared global cache mount so models are not re-downloaded across restarts
command: grep -q '.cache' services/compose.s2s.yml
tags: [spec]
- label: s2s has a compose.x integration with llamacpp routing the LLM stage to the llamacpp OpenAI-compatible API
command: test -f services/compose.x.s2s.llamacpp.yml
tags: [spec]
- label: s2s supports NVIDIA GPU via the standard compose.x.s2s.nvidia.yml overlay and runs on CPU without it
command: test -f services/compose.x.s2s.nvidia.yml
tags: [spec]
- label: harbor ls includes s2s and harbor up s2s resolves a valid compose configuration
command: ./harbor.sh ls | grep -q '^s2s$'
tags: [spec]
- label: s2s has a documentation page in docs/ covering the realtime client (scripts/listen_and_play_realtime.py --host <host> --port <port>), env vars, and backend integration
command: ls docs/ | grep -qi 'speech-to-speech'
tags: [spec]
- label: s2s has metadata (name, tags, logo) registered in app/src/serviceMetadata.ts
command: grep -q "s2s" app/src/serviceMetadata.ts
tags: [spec]
## morphic
- label: compose.morphic.yml includes a morphic-db PostgreSQL sidecar (postgres:17-alpine, data in services/morphic/postgres) that morphic depends_on with service_healthy, and sets DATABASE_URL/DATABASE_RESTRICTED_URL to it plus DATABASE_SSL_DISABLED, ENABLE_AUTH=false, MORPHIC_CLOUD_DEPLOYMENT=false — required since upstream morphic main runs drizzle migrations at startup
command: grep -q 'morphic-db' services/compose.morphic.yml && grep -q 'DATABASE_URL=postgresql://morphic:morphic@morphic-db:5432/morphic' services/compose.morphic.yml && grep -q 'service_healthy' services/compose.morphic.yml
tags: [spec, morphic-postgres, implemented]
## mindsdb x backends
- label: every mindsdb backend cross-file (llamacpp, vllm, dmr, mlx, omlx, ollama) sets LLM_FUNCTION_MODEL alongside LLM_FUNCTION_BASE_URL so the LLM() SQL function works out of the box (MindsDB errors on missing 'model_name' otherwise)
command: bash -c 'for f in services/compose.x.mindsdb.{llamacpp,vllm,dmr,mlx,omlx,ollama}.yml; do grep -q LLM_FUNCTION_MODEL $f || exit 1; done'
tags: [spec, mindsdb-llm-model, implemented]
## onyx
- label: onyx-index (Vespa) depends on an onyx-init sidecar that chowns workspace bind-mount subdirs (vespa, api_logs, background_logs, minio — not db) to HARBOR_USER_ID:HARBOR_GROUP_ID, since Vespa runs non-root and fatally fails on a root-owned /opt/vespa/var
command: bash -c 'grep -q onyx-init services/compose.onyx.yml && grep -q vespa services/onyx/workspace-init.sh && ! grep -qE "^ chown.*workspace\"?$" services/onyx/workspace-init.sh'
tags: [spec, onyx-startup, implemented]
- label: onyx-api receives USER_AUTH_SECRET from HARBOR_ONYX_USER_AUTH_SECRET (non-empty default in profiles/default.env) because Onyx refuses to start with an empty auth secret
command: bash -c 'grep -q "USER_AUTH_SECRET: ..HARBOR_ONYX_USER_AUTH_SECRET." services/compose.onyx.yml && grep -qE "^HARBOR_ONYX_USER_AUTH_SECRET=.+" profiles/default.env'
tags: [spec, onyx-startup, implemented]
- label: onyx api and background workers run with ENABLE_OPENSEARCH_INDEXING_FOR_ONYX=false and ONYX_DISABLE_VESPA=false so the Vespa-backed Harbor deployment neither probes a nonexistent OpenSearch sidecar nor rejects the Vespa index
command: bash -c '[ $(grep -c "ENABLE_OPENSEARCH_INDEXING_FOR_ONYX: .false." services/compose.onyx.yml) -eq 2 ] && [ $(grep -c "ONYX_DISABLE_VESPA: .false." services/compose.onyx.yml) -eq 2 ]'
tags: [spec, onyx-startup, implemented]
## cognee
- label: cognee and cognee-mcp depend on ollama-init with condition service_healthy (ollama-init is a long-running healthy sidecar, never 'completed')
command: grep -A2 'ollama-init' services/compose.x.cognee.ollama.yml | grep -q 'service_healthy' && ! grep -q 'service_completed_successfully' services/compose.x.cognee.ollama.yml
tags: [services-it, cognee, implemented]
- label: cognee-mcp runs as root so it can write the shared workspace created root-owned by the cognee API container
command: grep -A40 'cognee-mcp:' services/compose.cognee.yml | grep -q 'user: root'
tags: [services-it, cognee, implemented]
## karakeep
- label: karakeep pins Meilisearch v1.41.0 and sets MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE=true so existing indexes upgrade in place on version bumps
command: grep -q 'HARBOR_KARAKEEP_MEILI_VERSION="v1.41.0"' profiles/default.env && grep -q 'MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE=true' services/compose.karakeep.yml
tags: [implemented]
## dify
- label: compose.dify.yml runs Dify 1.16.1 with the 1.x service set: api, worker, worker-beat, web, plugin daemon (own dify_plugin Postgres DB auto-created on first start), agent backend with dify-local-sandbox and dify-agent-ssrf, code sandbox, ssrf proxy, nginx front door, weaviate, certbot, and the dify-openai shim
command: grep -q "dify-plugin-daemon" services/compose.dify.yml && grep -q "dify-agent:" services/compose.dify.yml && grep -q "MODE: beat" services/compose.dify.yml
tags: [spec, dify-1x, implemented]
- label: profiles/default.env pins HARBOR_DIFY_VERSION 1.16.1, HARBOR_DIFY_SANDBOX_VERSION 0.2.15, HARBOR_DIFY_PLUGIN_DAEMON_VERSION 0.6.3-local, HARBOR_DIFY_WEAVIATE_VERSION 1.27.0
command: grep -q "HARBOR_DIFY_VERSION=\"1.16.1\"" profiles/default.env && grep -q "HARBOR_DIFY_PLUGIN_DAEMON_VERSION=\"0.6.3-local\"" profiles/default.env && grep -q "HARBOR_DIFY_SANDBOX_VERSION=\"0.2.15\"" profiles/default.env && grep -q "HARBOR_DIFY_WEAVIATE_VERSION=\"1.27.0\"" profiles/default.env
tags: [spec, dify-1x, implemented]
- label: vendored nginx/ssrf templates under services/dify are the upstream 1.16.1 docker templates with hostnames rewritten to Harbor dify-* service names
command: grep -q "http://dify-plugin-daemon:5002" services/dify/nginx/conf.d/default.conf.template && grep -q "dstdomain dify-agent" services/dify/ssrf_proxy/squid-agent.conf.template
tags: [spec, dify-1x, implemented]
- label: the dify-openai bridge translates OpenAI chat completions to Dify 1.x APIs: Chat via /v1/chat-messages, Workflow via /v1/workflows/run including the blocking-mode single-JSON response shape and OUTPUT_VARIABLE selection from workflow outputs
command: grep -q 'workflow_run_id' services/dify/openai/app.js && grep -q 'extractWorkflowOutput' services/dify/openai/app.js
tags: [dify-1x, spec, implemented]
- label: HARBOR_DIFY_INPUT_VARIABLE and HARBOR_DIFY_OUTPUT_VARIABLE are plumbed from profiles/default.env through compose.dify.yml into the dify-openai container
command: grep -q 'INPUT_VARIABLE=${HARBOR_DIFY_INPUT_VARIABLE}' services/compose.dify.yml && grep -q HARBOR_DIFY_INPUT_VARIABLE profiles/default.env
tags: [dify-1x, spec, implemented]
- label: dify base configuration (upstream .env.example values with Harbor hostnames) lives in tracked services/dify/dify.env loaded via env_file before services/dify/override.env, so override.env stays an empty user-editable overlay like every other service
command: bash -c 'grep -q dify.env services/compose.dify.yml && grep -q "^DB_HOST=dify-db" services/dify/dify.env && ! grep -q "^DB_HOST=" services/dify/override.env'
tags: [spec, env-fix, implemented]
## k6
- label: HARBOR_K6_GRAFANA_VERSION in profiles/default.env pins grafana 11.6.1, verified to provision both shipped dashboards and the influxdb datasource
command: grep -q 'HARBOR_K6_GRAFANA_VERSION="11.6.1"' profiles/default.env
tags: [spec, svc-testing, implemented]
## ol1
- label: services/ol1/Dockerfile builds without cloning the deleted upstream tcsenpai/ol1 repo, copying Harbor's own app.py instead
command: ! grep -q 'git clone' services/ol1/Dockerfile && grep -q 'COPY app.py' services/ol1/Dockerfile
tags: [spec, svc-testing, implemented]
## omnichain
- label: openai.ts /v1/models returns an empty model list (not 500) when data/chains does not exist yet on a fresh install
command: grep -q 'existsSync(chainsDir)' services/omnichain/openai.ts
tags: [spec, svc-testing, implemented]
- label: omnichain image installs socat so entrypoint.sh port forwarding does not error with command-not-found
command: grep -q socat services/omnichain/Dockerfile
tags: [spec, svc-testing, implemented]
# app
- label: Harbor App blocks main UI behind a setup gate until setup detail status is ready
command: rg -q 'detail\?\.status === "ready"' app/src/setup/HarborSetupContext.tsx
tags: [implemented]
- label: Harbor App installs the CLI through install.sh on Linux and macOS and install.ps1 on Windows
command: rg -q 'install.sh' app/src-tauri/src/setup.rs && rg -q 'install.ps1' app/src-tauri/src/setup.rs
tags: [implemented]
- label: Harbor App setup uses a PTY for installer output and supports forwarding input for interactive prompts
command: rg -q 'portable_pty|portable-pty' app/src-tauri/Cargo.toml && rg -q 'write_harbor_setup_input' app/src-tauri/src/setup.rs
tags: [implemented]
- label: Harbor App Windows setup runs Harbor commands through WSL bash with a persisted preferred Ubuntu distro
command: rg -q 'wsl_bash_args|preferred_wsl_distro' app/src-tauri/src/setup.rs
tags: [implemented]
- label: Harbor App home lists services from harbor ls and supports bulk harbor up and harbor down
command: rg -q "useHarbor\\(\\['ls'\\]\\)" app/src/home/useServiceList.tsx && rg -q 'runHarbor' app/src/home/ServiceList.tsx
tags: [implemented]
- label: Harbor App close hides the window on desktop instead of quitting unless the user chooses Quit from the tray
command: rg -q 'prevent_close' app/src-tauri/src/lib.rs
tags: [implemented]
- label: Harbor App setup detection reports ready only when harbor doctor --check passes
command: rg -q 'doctor --check' app/src-tauri/src/setup.rs
tags: [implemented]
- Harbor App setup detection runs installer prerequisite blockers only when the CLI is not already installed @implemented
- label: install.ps1 suppresses the PowerShell progress bar during the Docker Desktop installer download to avoid the PS 5.1 Invoke-WebRequest slowdown
command: rg -q "ProgressPreference = 'SilentlyContinue'" install.ps1
tags: [implemented]
- label: Harbor App build splits vendor code into react terminal markdown and syntax chunks via Vite manualChunks so no JS chunk exceeds 600 kB
command: grep -q 'manualChunks' app/vite.config.ts
tags: [implemented]
# boost
- label: Boost is a FastAPI LLM proxy that exposes OpenAI chat completions and optional Anthropic and Responses compat layers
command: grep -q 'fastapi' services/boost/pyproject.toml && grep -q '/v1/chat/completions' services/boost/src/main.py
- label: Boost modules are Python plugins with ID_PREFIX and async apply loaded from modules and custom_modules directories
command: rg -q 'ID_PREFIX' services/boost/src/modules/ && rg -q 'importlib' services/boost/src/mods.py
- label: Boost auth accepts requests when BOOST_AUTH is empty and otherwise requires Authorization or x-api-key
command: test -f services/boost/src/auth.py && rg -q 'BOOST_AUTH' services/boost/src/config.py
tags: [implemented]
- label: Boost maps Anthropic Messages to OpenAI chat completions internally and streams Anthropic SSE event envelopes
command: rg -q '/v1/messages' services/boost/src/anthropic_compat.py && rg -q 'message_start' services/boost/src/anthropic_compat.py
tags: [implemented]
- label: Boost Responses API converts input to chat messages and emits response.created through response.completed SSE sequences
command: rg -q '/v1/responses' services/boost/src/responses_compat.py && rg -q 'response.created' services/boost/src/responses_compat.py
tags: [implemented]
- label: Boost Anthropic and Responses HTTP errors use SDK-specific envelopes based on route path not request body shape
command: rg -q '_ANTHROPIC_ERROR_TYPE_MAP' services/boost/src/main.py && rg -q 'ERROR_TYPE_MAP' services/boost/src/responses_compat.py
tags: [implemented]
- label: Boost workflows advertise model IDs as workflow prefixes and execute ordered module steps including system and final completion
command: test -f services/boost/src/workflows.py && rg -q 'apply_workflow' services/boost/src/workflows.py
tags: [implemented]
- label: Boost direct-task detection bypasses module serve for known title-generation style prompts when no workflow is set
command: rg -q 'is_direct_task' services/boost/src/mapper.py && rg -q 'is_direct_task' services/boost/src/main.py
tags: [implemented]
- label: Boost SSE streaming uses retry intervals keepalive comments and no-buffer headers for proxy compatibility
command: rg -q 'SSE_KEEPALIVE_INTERVAL' services/boost/src/compat_utils.py && rg -q 'sse_keepalive' services/boost/src/anthropic_compat.py
tags: [implemented]
- label: Boost BackendError forwards rate-limit headers to clients on 429 in both streaming and non-streaming paths
command: rg -q 'RATE_LIMIT_FORWARD_HEADERS' services/boost/src/compat_utils.py && rg -q 'BackendError' services/boost/tests/test_streaming_backend_error.py
tags: [implemented]
- label: autocheck audits coding deliverable drafts with a structured checklist (correctness, completeness, file-path grounding) and revises at most once when the audit verdict is revise; non-deliverable turns pass through unchanged
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAutocheckApply -q --tb=no
tags: [implemented]
- label: diffscope compares cited file paths in coding deliverable drafts against user-stated scope from recent messages, verifies workspace paths when HARBOR_BOOST_WORKSPACE_ROOT is set, and revises once on out-of-scope or missing paths
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestDiffscopeApply -q --tb=no
tags: [implemented]
- label: deliverable heuristics gate agentic modules: coding implementation requests with file paths or code blocks trigger post-processing while purely explanatory questions pass through
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestDeliverableGate -q --tb=no
tags: [implemented]
- label: read_workspace_file is exposed as a portable tool only when HARBOR_BOOST_WORKSPACE_ROOT is set and rejects paths outside the workspace root
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestWorkspaceFileTool -q --tb=no
tags: [implemented]
- label: shared research package enforces per-request search URL-read and character budgets and renders gathered results into a research_brief system block
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestResearchBudget tests/test_agentic_infra.py::TestResearchBrief -q --tb=no
tags: [implemented]
- label: shared research respects HARBOR_BOOST_RESEARCH_NOTES_MAX_CHARS when adding brief notes: trims each search or read failure note to the configured per-note character limit (default 4000); set to 0 to disable truncation
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestResearchFetch::test_trim_note_uses_config_default tests/test_agentic_infra.py::TestResearchBrief::test_add_note_truncates_oversized_notes tests/test_orchestrate.py::TestOrchestrateParallelFetch::test_run_searches_truncates_long_failure_notes -q --tb=no
tags: [implemented]
- label: grep_workspace is an opt-in portable tool when HARBOR_BOOST_WORKSPACE_ROOT is set: searches workspace files with optional glob and max_matches caps and rejects paths outside the workspace root
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestWorkspaceFileTool::test_grep_workspace_finds_pattern tests/test_agentic_infra.py::TestWorkspaceFileTool::test_grep_workspace_respects_max_matches tests/test_agentic_infra.py::TestWorkspaceFileTool::test_grep_workspace_path_jail tests/test_agentic_infra.py::TestWorkspaceFileTool::test_selected_tools_includes_grep_when_configured -q --tb=no
tags: [spec, implemented]
- label: list_workspace_files is an opt-in portable tool when HARBOR_BOOST_WORKSPACE_ROOT is set: lists workspace files with optional glob and max_entries caps and rejects paths outside the workspace root
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestWorkspaceFileTool::test_list_workspace_files_lists_files tests/test_agentic_infra.py::TestWorkspaceFileTool::test_list_workspace_files_respects_max_entries tests/test_agentic_infra.py::TestWorkspaceFileTool::test_list_workspace_files_path_jail tests/test_agentic_infra.py::TestWorkspaceFileTool::test_selected_tools_includes_list_when_configured -q --tb=no
tags: [spec, implemented]
- label: Boost compose bind-mounts the host folder from HARBOR_BOOST_WORKSPACE to /workspace in the container so workspace tools jail paths under HARBOR_BOOST_WORKSPACE_ROOT
command: rg -q '\$\{HARBOR_BOOST_WORKSPACE' services/compose.boost.yml && rg -q '^HARBOR_BOOST_WORKSPACE=' profiles/default.env && rg -q '^HARBOR_BOOST_WORKSPACE_ROOT=' profiles/default.env
tags: [spec, implemented]
- label: write_workspace_file is an opt-in portable tool not in the default tool list when HARBOR_BOOST_WORKSPACE_ROOT is set: writes files inside the workspace root with a size cap and rejects paths outside the root
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestWorkspaceFileTool::test_write_workspace_file_writes_file tests/test_agentic_infra.py::TestWorkspaceFileTool::test_write_workspace_file_enforces_size_cap tests/test_agentic_infra.py::TestWorkspaceFileTool::test_default_tools_omits_workspace_writer -q --tb=no
tags: [spec, implemented]
- label: diffscope grounds scope checks in git repos by collecting changed paths from git diff --name-only and git diff --stat with timeout and nonzero-exit fallback to cited draft paths
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestGitDiffGrounding -q --tb=no
tags: [spec, implemented]
- label: research orchestration parallelizes multi-query web_search calls and multi-URL page reads with concurrency caps while respecting per-request search and read budgets
command: cd services/boost && uv run pytest tests/test_orchestrate.py::TestOrchestrateParallelFetch -q --tb=no
tags: [spec, implemented]
- label: autocheck runs mechanical pre-audit before the LLM audit on deliverable turns: flags code without cited paths or missing workspace paths, includes git diff stat context, and forces revise when blockers are found
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestMechanicalPreaudit -q --tb=no
tags: [spec, implemented]
- label: autocheck mechanical pre-audit emits non-blocking linter hints when eslint or ruff configs appear near cited or changed paths: walks up from anchor paths to discover configs, suggests runnable npx eslint or ruff check commands, and includes warn findings in run_mechanical_preaudit
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestLinterHint -q --tb=no
tags: [implemented]
- label: git_diff_workspace is an opt-in portable tool when HARBOR_BOOST_WORKSPACE_ROOT is set on a git repo: returns git diff --name-only and --stat for workspace paths with optional path scoping jail and 5 second timeout
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestWorkspaceFileTool::test_git_diff_workspace_returns_stat_and_name_only tests/test_agentic_infra.py::TestWorkspaceFileTool::test_git_diff_workspace_scopes_path tests/test_agentic_infra.py::TestWorkspaceFileTool::test_git_diff_workspace_path_jail tests/test_agentic_infra.py::TestWorkspaceFileTool::test_selected_tools_includes_git_diff_when_git_workspace -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_SHOW_AUDIT and per-request boost show_audit param: when enabled appends an audit footer to deliverable answers and emits an HTML findings summary artifact; show_audit overrides config
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestWorkspacePaths::test_show_audit_footer_reads_boost_params tests/test_autocheck.py::TestWorkspacePaths::test_show_audit_footer_boost_param_overrides_config tests/test_autocheck.py::TestAutocheckApply::test_apply_appends_audit_footer_when_show_audit_enabled tests/test_autocheck.py::TestAutocheckApply::test_apply_skips_audit_artifact_when_show_audit_disabled -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_STRICT: when true prepends a warning banner to deliverable answers when critical or major findings remain after all revise passes instead of silently shipping
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestWorkspacePaths::test_should_prepend_strict_warning_when_enabled_with_blockers tests/test_autocheck.py::TestWorkspacePaths::test_should_not_prepend_strict_warning_for_warn_only_findings tests/test_autocheck.py::TestAutocheckApply::test_apply_prepends_strict_warning_when_blockers_remain tests/test_autocheck.py::TestAutocheckApply::test_apply_skips_strict_warning_when_disabled -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_STRICT workspace_unconfigured skip: when true and HARBOR_BOOST_WORKSPACE_ROOT is unset, autocheck skips deliverable audit with workspace_unconfigured gate reason, logs a config error naming both keys, emits Autocheck: skipped (workspace_unconfigured) status, and streams final completion without audit; non-strict mode still triggers on deliverable turns without workspace root
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAutocheckGate::test_strict_mode_requires_workspace_root_gate_reason tests/test_autocheck.py::TestAutocheckGate::test_non_strict_mode_allows_missing_workspace_root tests/test_autocheck.py::TestWorkspacePaths::test_format_skipped_status_includes_gate_reason tests/test_autocheck.py::TestAutocheckApply::test_apply_strict_skips_audit_when_workspace_unconfigured -q --tb=no
tags: [implemented]
- label: diffscope revise_with_correction prompt includes explicit allowed_paths forbidden_paths out_of_scope_paths and git_evidence sections built from user scope violations and workspace git diff
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestWorkspaceAndNotes::test_build_revise_scope_sections_lists_allowed_forbidden_and_git_evidence -q --tb=no
tags: [implemented]
- label: agentic modules record optional per-module debug payloads on request.state via research.debug_metrics: triggered or skipped status, reason, duration_ms, and module-specific extras under keys like quickhop_debug
command: cd services/boost && uv run pytest tests/test_agentic_debug_metrics.py -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_MAX_REVISE_PASSES: configurable revise passes after audit verdict revise clamped to 0-2; HARBOR_BOOST_AUTOCHECK_STRICT true adds one extra pass still capped at 2
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAutocheckGate::test_clamp_max_revise_passes_caps_at_two tests/test_autocheck.py::TestAutocheckGate::test_effective_max_revise_passes_defaults_to_one tests/test_autocheck.py::TestAutocheckGate::test_effective_max_revise_passes_adds_one_in_strict_mode tests/test_autocheck.py::TestAutocheckGate::test_effective_max_revise_passes_stays_capped_when_strict_and_config_two -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_AUDIT_MODEL: configurable model for the structured audit sub-call; when empty autocheck uses the incoming request model, when set audit_llm and run_audit override the cheap LLM client model after stripping whitespace
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAuditAndRevise::test_audit_llm_keeps_request_model_when_unset tests/test_autocheck.py::TestAuditAndRevise::test_audit_llm_overrides_model_when_configured tests/test_autocheck.py::TestAuditAndRevise::test_run_audit_uses_audit_model_override -q --tb=no
tags: [implemented]
- label: diffscope supports HARBOR_BOOST_DIFFSCOPE_ALLOW_COLLATERAL: when true out-of-scope files against hinted scope emit collateral warnings unless the user said only X; when false any out-of-scope path triggers a scope revision
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestCollateralViolations::test_partition_treats_hinted_out_of_scope_as_collateral_by_default tests/test_diffscope.py::TestCollateralViolations::test_partition_blocks_hinted_out_of_scope_when_collateral_disabled tests/test_diffscope.py::TestCollateralViolations::test_apply_warns_on_collateral_without_revision tests/test_diffscope.py::TestCollateralViolations::test_apply_revises_hinted_out_of_scope_when_collateral_disabled -q --tb=no
tags: [implemented]
- label: diffscope treats only-edit and edit-only user scope as allowed-only mode where HARBOR_BOOST_DIFFSCOPE_ALLOW_COLLATERAL is ignored, extracts multi-path only phrases, and excludes allowed paths from hinted scope
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestUserScope::test_extract_allowed_paths_from_only_edit_phrase tests/test_diffscope.py::TestUserScope::test_extract_allowed_paths_from_edit_only_phrase tests/test_diffscope.py::TestUserScope::test_extract_multiple_allowed_paths_from_only_edit_phrase tests/test_diffscope.py::TestUserScope::test_allowed_paths_are_excluded_from_hinted_scope tests/test_diffscope.py::TestCollateralViolations::test_partition_only_edit_ignores_allow_collateral tests/test_diffscope.py::TestCollateralViolations::test_apply_revises_only_edit_even_when_allow_collateral_true -q --tb=no
tags: [implemented]
- label: deliverable has_research_signals requires a research keyword plus question mark, a version pattern, a package or product mention, or a URL; bare questions such as What does this code do? do not trigger research modules
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestDeliverableBorderlineCases::test_bare_questions_without_research_context_are_not_research_signals tests/test_agentic_infra.py::TestDeliverableBorderlineCases::test_research_signal_keywords_detected tests/test_agentic_infra.py::TestDeliverableBorderlineCases::test_research_signals_require_keyword_question_version_or_package -q --tb=no
tags: [implemented]
- label: shared research fetch retries web_search and read_url once after transient HTTP failures (timeout or connect error) with a one second backoff and does not retry non-transient errors
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestResearchFetch::test_web_search_retries_once_on_transient_failure tests/test_agentic_infra.py::TestResearchFetch::test_web_search_does_not_retry_non_transient_failures tests/test_agentic_infra.py::TestResearchFetch::test_read_url_retries_jina_once_on_transient_failure tests/test_agentic_infra.py::TestResearchFetch::test_read_url_retries_direct_once_on_transient_failure -q --tb=no
tags: [implemented]
- label: diffscope extracts forbidden paths from user scope hints including dont touch, leave X alone, except and except for Z, multi-path lists, and apostrophe-less dont-touch variants
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestUserScope::test_extract_forbidden_paths_from_dont_touch tests/test_diffscope.py::TestUserScope::test_extract_forbidden_paths_from_leave_alone tests/test_diffscope.py::TestUserScope::test_extract_forbidden_paths_from_except tests/test_diffscope.py::TestUserScope::test_extract_forbidden_paths_from_except_for tests/test_diffscope.py::TestUserScope::test_extract_multiple_forbidden_paths_from_dont_touch tests/test_diffscope.py::TestUserScope::test_extract_multiple_forbidden_paths_from_leave_alone tests/test_diffscope.py::TestUserScope::test_extract_forbidden_without_apostrophe -q --tb=no
tags: [implemented]
- label: research.workflow anchor_deferred_draft records deferred drafts in chat when defer_final is set, replacing an existing assistant tail instead of appending so downstream diffscope and autocheck in chained workflows audit scoped or revised answers not pre-revision drafts (DIF-003)
command: cd services/boost && uv run pytest tests/test_agentic_debug_metrics.py::TestAnchorDeferredDraft tests/test_diffscope.py::TestDiffscopeApply::test_apply_anchors_scoped_draft_when_defer_final tests/test_diffscope.py::TestDiffscopeApply::test_apply_replaces_prior_draft_when_defer_final tests/test_autocheck.py::TestAutocheckApply::test_apply_anchors_revised_draft_when_defer_final tests/test_agentic_workflow_chains.py::TestDiffscopeDeliverableChain::test_diffscope_scope_violation_triggers_revise -q --tb=no
tags: [implemented, bughunt]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_DRAFT_MODEL: configurable model for the draft sub-call; when empty autocheck uses the incoming request model, when set draft_llm and generate_draft override the cheap LLM client model after stripping whitespace
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAuditAndRevise::test_draft_llm_keeps_request_model_when_unset tests/test_autocheck.py::TestAuditAndRevise::test_draft_llm_overrides_model_when_configured tests/test_autocheck.py::TestAuditAndRevise::test_generate_draft_uses_draft_model_override -q --tb=no
tags: [implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_REVISE_MODEL: configurable model for the revise sub-call; when empty autocheck uses the incoming request model, when set revise_llm and revise_draft override the cheap LLM client model after stripping whitespace
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAuditAndRevise::test_revise_llm_keeps_request_model_when_unset tests/test_autocheck.py::TestAuditAndRevise::test_revise_llm_overrides_model_when_configured tests/test_autocheck.py::TestAuditAndRevise::test_revise_draft_uses_revise_model_override -q --tb=no
tags: [implemented]
- label: shared research fetch respects HARBOR_BOOST_RESEARCH_FETCH_TIMEOUT_SECONDS for web_search and read_url httpx clients: configurable HTTP timeout in seconds (default 30) applied to Tavily, Jina, and direct read paths
command: cd services/boost && uv run pytest tests/test_agentic_infra.py::TestResearchFetch::test_research_fetch_timeout_defaults_to_30_seconds tests/test_agentic_infra.py::TestResearchFetch::test_search_tavily_uses_configured_fetch_timeout tests/test_agentic_infra.py::TestResearchFetch::test_read_with_jina_uses_configured_fetch_timeout tests/test_agentic_infra.py::TestResearchFetch::test_read_direct_uses_configured_fetch_timeout -q --tb=no
tags: [implemented]
- label: autocheck treats HARBOR_BOOST_AUTOCHECK_MAX_PASSES as a backward-compatible alias for HARBOR_BOOST_AUTOCHECK_MAX_REVISE_PASSES: both clamp to 0-2 and effective_max_revise_passes uses the higher value when both are set
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAutocheckGate::test_effective_max_revise_passes_honors_legacy_max_passes_alias -q --tb=no
tags: [spec, implemented]
- label: HARBOR_BOOST_DEBUG and per-request boost debug param gate a compact Debug: status summary at final completion: when enabled complete_or_defer emits per-module triggered or skipped lines with duration_ms and extras; debug param overrides config
command: cd services/boost && uv run pytest tests/test_agentic_debug_metrics.py::TestDebugSummaryHelpers::test_debug_enabled_respects_boost_param tests/test_agentic_debug_metrics.py::TestDebugSummaryHelpers::test_debug_enabled_falls_back_to_config tests/test_agentic_debug_metrics.py::TestCompleteOrDeferDebugStatus::test_emits_compact_summary_when_debug_enabled tests/test_agentic_debug_metrics.py::TestCompleteOrDeferDebugStatus::test_skips_summary_when_debug_disabled -q --tb=no
tags: [spec, implemented]
- label: autocheck supports HARBOR_BOOST_AUTOCHECK_ENABLED: when false needs_autocheck is false and autocheck_gate_reason returns disabled so deliverable turns pass through without audit
command: cd services/boost && uv run pytest tests/test_autocheck.py::TestAutocheckGate::test_skips_when_disabled -q --tb=no
tags: [spec, implemented]
- label: diffscope supports HARBOR_BOOST_DIFFSCOPE_ENABLED: when false needs_diffscope is false and diffscope_gate_reason returns disabled so scoped deliverables pass through without scope revision
command: cd services/boost && uv run pytest tests/test_diffscope.py::TestDiffscopeGate::test_skips_when_disabled -q --tb=no
tags: [spec, implemented]
- label: research.workflow anchor_and_emit_final anchors a deferred draft via anchor_deferred_draft then emits the text with llm.emit_message unless defer_final is set; autocheck uses it on audit_failed so chained workflows anchor the draft without double-emitting before the explicit final step
command: cd services/boost && uv run pytest tests/test_agentic_debug_metrics.py::TestAnchorAndEmitFinal tests/test_autocheck.py::TestAutocheckApply::test_apply_audit_failed_defers_emit_when_configured -q --tb=no
tags: [implemented]
- label: Boost ChatNode preserves non-text multimodal content parts instead of flattening them to text
command: cd services/boost && uv run pytest tests/test_chat_node.py::TestChatNodeContent -q --tb=short
tags: [spec, reported-regression, implemented]
- label: caveman injects one level-tagged caveman_style block into the leading system message before completion unless the user or workflow disables the style, preserving single-system-message backend compatibility
command: cd services/boost && uv run --with pytest --with pytest-asyncio pytest -q tests/test_caveman.py
tags: [spec, release-validation, implemented]
- label: Boost declares a dev dependency group with pytest pytest-asyncio pytest-xdist and anthropic so bare uv run pytest collects the SDK integration tests and can parallelize with -n auto
command: cd services/boost && python3 -c "import tomllib;g=tomllib.load(open('pyproject.toml','rb'))['dependency-groups']['dev'];assert {'pytest','pytest-asyncio','pytest-xdist','anthropic'} <= set(g), g"
tags: [implemented]
- label: style level commands are module scoped: /caveman <level> and stop caveman only affect the caveman module and /ponytail forms only affect ponytail, while normal mode disables both; resolve_style_level receives the calling module id
command: cd services/boost && uv run pytest tests/test_caveman.py tests/test_ponytail.py -q
tags: [implemented]
- label: read_url direct fetch follows redirects manually (max 5 hops) and re-validates every hop with require_http_url so a public URL cannot 3xx into private, loopback, link-local, or reserved addresses
command: cd services/boost && uv run pytest tests/test_agentic_infra.py -q -k redirect
tags: [implemented]
- label: autocheck gather_workspace_context sanitizes error attributes (quotes and newlines) and neutralizes file-tag markers inside file content so failed or spoofed reads never count as successful workspace evidence
command: cd services/boost && uv run pytest tests/test_autocheck.py -q
tags: [implemented]
- label: research orchestrate read_urls schedules at most the remaining URL-read budget, so the 'reading N sources' status line never reports more pages than the budget allows
command: grep -q 'scheduled_urls = urls\[:remaining_reads\]' services/boost/src/research/orchestrate.py
tags: [spec, release-validation, implemented]
- label: deephop passes phase labels 'hop 1'/'hop 2' (without the module name) to run_searches and read_urls so emitted status lines read 'Deephop research: hop 2: ...' instead of duplicating 'Deephop'
command: ! grep -q 'phase="Deephop hop' services/boost/src/modules/deephop.py && grep -q 'phase="hop 2"' services/boost/src/modules/deephop.py
tags: [spec, release-validation, implemented]
- label: Boost module discovery resolves relative BOOST_FOLDERS entries against the mods.py directory instead of the process cwd so tests and tools can import mods from any working directory
command: cd /tmp && python3 -c "import sys;sys.path.insert(0,'/home/everlier/code/harbor/services/boost/src');import types;m=types.ModuleType('mapper');m.__stub__=True;sys.modules['mapper']=m;import mods;assert len(mods.registry)>0"
tags: [implemented]
- label: Boost CI runs the pytest suite in parallel with pytest-xdist -n auto
command: grep -q 'pytest -q -n auto' .github/workflows/boost-tests.yml
tags: [implemented]
- label: token_counter encodes texts longer than 16k chars in fixed-size slices so pathological inputs like a 1MB single-character run cannot stall the server for minutes during token counting
command: cd services/boost && timeout 120 uv run pytest -q tests/test_anthropic_compat.py -k TestSecurityTokenCounting
tags: [implemented]
- label: the boost test isolation fixture snapshots and restores the plain BOOST_AUTH list so a test that rebinds it cannot leak 401s into unrelated tests
command: grep -q 'saved_auth' services/boost/tests/conftest.py
tags: [implemented]
- label: When the backend rejects a chat completion, Boost propagates the backend's HTTP status and an OpenAI-style error JSON to the client instead of 200 with empty content; mid-stream failures emit a final SSE error chunk before DONE
command: cd services/boost && uv run pytest -q tests/test_backend_error_propagation.py
tags: [spec, boost-error-propagation, implemented]
- label: Handled BackendErrors log one concise 'Backend error <status>: <message>' line without stack-trace frames (raise sites warn once; _on_task_done and the chat handler stay quiet for them), while unexpected exceptions still log full tracebacks via exc_info
command: cd services/boost && uv run pytest -q tests/test_backend_error_logging.py
tags: [spec, implemented, boost-error-logging]
# testing
- label: tests use a Deno orchestrator running sequential bash suites across containerized distros
command: test -f tests/run.ts && test -f tests/suites/01-install.sh && test -f tests/suites/05-launch-smoke.sh
- label: harbor dev test runs the container test matrix documented in tests/README.md
command: test -f tests/README.md && rg -q 'harbor dev test' tests/README.md
- label: harbor dev test --suite boost-agentic-smoke runs the Boost agentic pytest battery via tests/suites/06-boost-agentic-smoke.sh with container or host mode through tests/lib/boost-agentic.sh
command: test -f tests/suites/06-boost-agentic-smoke.sh && test -f tests/lib/boost-agentic.sh && rg -q 'boost-agentic-smoke' tests/README.md
tags: [implemented]
- pending launch CLI, Hugging Face model discovery, and Boost module changes have an agent-executable integration release checklist covering behavior, lint, facts, and user documentation @spec @release-validation @implemented
- the test-boost-module skill live-tests current Boost modules through harbor launch with pi and does not recommend removed modules or obsolete module roles @spec @release-validation @implemented
# lint
- label: harbor dev lint runs a three-pass Deno orchestrator with HARBOR-prefixed bash rules under .scripts/lint
command: test -f .scripts/lint/run.ts && test -f .scripts/lint/rules.yaml && grep -q 'HARBOR0' .scripts/lint/rules.yaml
tags: [implemented]
- label: harbor dev lint --strict exits non-zero when any finding is reported, warnings included, so CI can gate on a fully clean lint
command: rg -q 'case "strict"' .scripts/lint/run.ts && rg -q 'args.strict && reported.length > 0' .scripts/lint/run.ts
tags: [spec, implemented]
# docs
- label: documentation lives in docs/ with hierarchical numbering and covers 100+ services
command: test $(find docs -name '*.md' | wc -l) -ge 100
- label: docs are regenerated via harbor dev docs from .scripts/docs.ts
command: test -f .scripts/docs.ts
- label: README introduces harbor launch with backend model --web --config and --service examples
command: rg -q 'harbor launch --backend ollama' README.md && rg -q 'harbor launch --service opencode' README.md
tags: [implemented]
- label: BionicGPT docs explain how to configure Ollama from inside Harbor by using the internal Ollama URL with /v1, a non-empty API key, and an available Ollama model name
command: grep -q 'http://ollama:11434/v1' 'docs/2.1.8-Frontend&colon-BionicGPT.md' && grep -q 'sk-ollama' 'docs/2.1.8-Frontend&colon-BionicGPT.md' && grep -q 'harbor url -i ollama' 'docs/2.1.8-Frontend&colon-BionicGPT.md' && grep -Eq 'harbor ollama (list|ls)' 'docs/2.1.8-Frontend&colon-BionicGPT.md'
tags: [implemented]
- label: BionicGPT compose exposes upstream default DNS names for its bundled LLM and embeddings APIs
command: grep -A10 '^ bionicgpt-llmapi:' services/compose.bionicgpt.yml | grep -q 'llm-api' && grep -A10 '^ bionicgpt-embeddingsapi:' services/compose.bionicgpt.yml | grep -q 'embeddings-api'
tags: [implemented]
# ci
- label: CI includes GitHub Actions workflows for app-release bench-docker boost-docker lint and test
command: test -f .github/workflows/test.yml && test -f .github/workflows/lint.yml && test -f .github/workflows/boost-docker.yml
# seo
- label: scripts/seo/plan.md documents Harbor SEO keyword priorities for local LLM and Docker Compose intent
command: test -f scripts/seo/plan.md && rg -q 'local LLM stack' scripts/seo/plan.md
tags: [seo-plan, implemented]
## guides
- label: all seven Harbor SEO guides 8.1 through 8.7 exist under docs/
command: for f in docs/8.{1..7}-*.md; do test -f "$f" || exit 1; done
tags: [seo-guides, implemented]
- label: docs/8.-Guides.md indexes and links every 8.x guide
command: test -f docs/8.-Guides.md && for n in 1 2 3 4 5 6 7; do rg -q "8.${n}-" docs/8.-Guides.md || exit 1; done
tags: [seo-guides, implemented]
- label: docs/README.md and README.md link Harbor Guides and the 8.x guide pages
command: rg -q 'Harbor Guides' docs/README.md README.md && rg -q '8.1-Local-LLM' docs/README.md README.md && rg -q '8.7-Run-Hermes' docs/README.md README.md
tags: [seo-guides, implemented]
- label: each 8.x SEO guide links back to the Harbor Guides index
command: for f in docs/8.[1-7]-*.md; do rg -q '\[Harbor Guides\]\(\./8\.-Guides\.md\)' "$f" || exit 1; done
tags: [seo-guides, implemented]
# dev
## lint
- label: Harbor lint implementation lives under .scripts/lint not scripts/lint
command: test -f .scripts/lint/run.ts && test ! -e scripts/lint
tags: [implemented]
- label: harbor dev lint and harbor dev lint-self-test dispatch to .scripts/lint implementations
command: rg -q './lint/run.ts' .scripts/lint.ts && rg -q './lint/self-test.ts' .scripts/lint-self-test.ts
tags: [implemented]
# tests
- label: tests/services-integration.sh --list prints the 8 test groups A-H with their services
command: ./tests/services-integration.sh --list | grep -c '^[A-H] ' | grep -qx 8
tags: [services-runner, implemented]
- label: tests/services-integration.sh --list includes Group I depth checks (webui chat, searxng categories, litellm proxy, boost modules, jupyter kernel, promptfoo eval, comfyui workflow)
command: ./tests/services-integration.sh --list | grep -q '^I '
tags: [services-runner, implemented]
- label: tests/services-integration.sh --list includes Group J ROCm paths (llamacpp/ollama/lemonade/localai/voicebox/vllm), gated behind ROCm-host detection and excluded from the default group list
command: ./tests/services-integration.sh --list | grep -q '^J .*ROCm' && grep -q 'has_rocm_host' tests/services-integration.sh && ! grep -q 'DEFAULT_GROUPS=.*J' tests/services-integration.sh
tags: [services-runner, implemented]
- label: the lint file collector excludes services/netdata/cache and services/netdata/lib — netdata's workspace is services/netdata itself and its runtime dirs are root-owned, crashing the walk
command: grep -q 'services/netdata/cache' .scripts/lint/util.ts && grep -q 'services/netdata/lib' .scripts/lint/util.ts
tags: [spec, implemented]
- label: tests: 02-cli asserts default services are webui and llamacpp only, with no ollama-as-default assertions
command: bash -c '! grep -q "default services include ollama" tests/suites/02-cli.sh && grep -q "default services include llamacpp" tests/suites/02-cli.sh'
tags: [implemented, defaults]
## app-install
- label: the integration install suite exercises install.sh and first-run llamacpp webui wiring
command: rg -q 'install.sh' tests/suites/01-install.sh && rg -q 'harbor up --no-defaults llamacpp webui' tests/suites/01-install.sh
tags: [implemented]
- label: tests/app-native-setup.md documents verifiable native app first-run setup expectations
command: test -f tests/app-native-setup.md && rg -q 'harbor doctor --check' tests/app-native-setup.md && ! rg -q 'HARBOR_APP_SETUP_SMOKE' tests/app-native-setup.md
tags: [implemented]
## fix-tests
- label: scripts/specs/fix-tests.md documents the test-run disk exhaustion incident as problem analysis only
command: test -f scripts/specs/fix-tests.md && grep -q '## Root Cause' scripts/specs/fix-tests.md && grep -q 'No remediation is specified here' scripts/specs/fix-tests.md
tags: [spec, implemented]
## runner
- label: the test orchestrator auto-prepends 01-install when a selected suite requires an installed harbor (all except install and boost-agentic-smoke) and install was not selected
command: grep -q 'SELF_SUFFICIENT_SUITES' tests/run.ts && grep -q 'auto-prepending' tests/run.ts
tags: [spec, implemented]
- label: the orchestrator forwards host GITHUB_TOKEN or GH_TOKEN into each test row's suite environment so GitHub API calls during --install-source github are authenticated when a token is present
command: grep -q 'GITHUB_TOKEN' tests/run.ts
tags: [spec, gh-rate-limit, implemented]
## defaults-up
- label: tests/suites/07-defaults-up.sh asserts a bare 'harbor up' on a fresh install starts webui+llamacpp only (no ollama), prints both first-boot notices, webui reaches healthy, and llamacpp /v1/models answers with an empty HF cache
command: test -x tests/suites/07-defaults-up.sh && grep -q 'llama.cpp has no local models yet' tests/suites/07-defaults-up.sh && grep -q 'v1/models' tests/suites/07-defaults-up.sh
tags: [spec, defaults-up, implemented]
- label: 07-defaults-up.sh guards the --flatten merger: merged webui config and the webui config DB table contain flat dot-path per-key rows (openai.enable) and no nested legacy top-level blobs
command: grep -q 'openai.enable' tests/suites/07-defaults-up.sh && grep -q 'flatten' tests/suites/07-defaults-up.sh
tags: [spec, defaults-up, implemented]
- label: defaults-up is registered in HEAVY_SUITE_DEFAULTS pinning it to ubuntu-2404 with jobs=1
command: grep -q 'defaults-up' tests/stage-repo.ts && grep -A1 '"defaults-up"' tests/stage-repo.ts | grep -q ubuntu-2404 || grep '"defaults-up"' tests/stage-repo.ts | grep -q ubuntu-2404
tags: [spec, defaults-up, implemented]
## install
- label: install.sh resolve_harbor_version sends an Authorization Bearer header on the GitHub releases API call when GITHUB_TOKEN or GH_TOKEN is set
command: grep -q 'Authorization: Bearer' install.sh
tags: [spec, gh-rate-limit, implemented]
- label: when --install-source github fails inside a row, 01-install falls back to the staged local install with a clear warning instead of failing the row
command: grep -q 'falling back to source=local' tests/suites/01-install.sh
tags: [spec, gh-rate-limit, implemented]
# project > distribution
- label: requirements.sh apt package availability checks verify real package metadata: every apt-cache show probe greps for ^Package: so empty-output false positives (e.g. docker-ce on Ubuntu 24.04 without the Docker repo) fall through to the docker.io / distro fallback
command: test $(grep -cE "apt-cache show (docker-ce|docker-compose-v2|docker-compose-plugin)" requirements.sh) -gt 0 && ! grep -E "apt-cache show (docker-ce|docker-compose-v2|docker-compose-plugin)" requirements.sh | grep -v "Package:" | grep -q .
tags: [spec, implemented]
- On a clean Ubuntu 24.04 without the Docker APT repository, the Harbor App guided install completes end-to-end by installing docker.io, with no manual repo configuration required @spec @implemented
## daytona
- tests/app-daytona-install.md exists and covers in-app Harbor install verification on Ubuntu 24.04 and Fedora 42 Daytona sandboxes @implemented @app-daytona-install-spec
# development
- label: release.sh refreshes app/src-tauri/Cargo.lock after the version seed via cargo update --workspace, warning on stderr when cargo is unavailable
command: grep -q 'cargo update --workspace' .scripts/release.sh && grep -q 'command -v cargo' .scripts/release.sh && grep -qi 'WARNING' .scripts/release.sh
tags: [spec, release, implemented]
## lint
- label: harbor dev lint --boost flags zero-byte .py module files under services/boost/src/modules and custom_modules with HARBOR011 so truncated writes fail at lint time instead of runtime import
command: test -f .scripts/lint/passes/boost.ts && rg -q 'HARBOR011' .scripts/lint/passes/boost.ts .github/workflows/lint.yml && test ! -s .scripts/lint/fixtures/boost/zero-byte/fail.py
tags: [implemented]
- harbor dev lint ignores nested .claude worktrees so their copied source and intentional failure fixtures do not create duplicate findings @spec @release-validation @implemented
- label: A compose service may declare x-harbor-lint-ignore: [rule, ...] to waive specific compose lint rules for that service; the env-file-main pass fixture includes a waived service proving zero findings
command: grep -q 'x-harbor-lint-ignore' .scripts/lint/passes/compose.ts && grep -q 'x-harbor-lint-ignore' .scripts/lint/fixtures/compose/env-file-main/pass.yml
tags: [implemented]
# launch
- label: harbor launch grok writes or updates a harbor-<backend> model entry in ~/.grok/config.toml with the selected base_url, model, and env_key, then invokes grok -m harbor-<backend>
command: rg -q 'launch_grok_config_path' harbor.sh && rg -q 'launch_write_grok_config' harbor.sh && ./harbor.sh launch --help | rg -q grok
tags: [implemented]
- label: harbor launch grok removes the harbor-<backend> model entry from ~/.grok/config.toml after the grok process exits
command: rg -q 'launch_remove_grok_config' harbor.sh && rg -q 'trap .*launch_remove_grok_config' harbor.sh && ./harbor.sh launch --help | rg -q grok
tags: [spec, implemented]
# models
- label: harbor models ls and rm start Ollama automatically when no explicit source is provided
command: rg -q 'requested_source.*ollama' harbor.sh && rg -q 'run_up --no-defaults ollama' harbor.sh
tags: [spec, reported-regression, implemented]
- label: listHfModels does not wipe the model list when the hub library scan throws: it proceeds with zero known repos so the rescue directory walk still lists readable repos
command: rg -q "cacheInfo = \{ repos: \[\] \}" routines/models/hf.ts
tags: [implemented]
# demos
- label: launch-grid demo supports --duration and exits early when all agent panes finish
command: ./scripts/demos/launch-grid.sh --help | grep -q -- '--duration' && grep -q 'start_completion_watcher' scripts/demos/launch-grid.sh
tags: [spec, autonomous-demo, implemented]
- label: launch-grid demo defaults to llamacpp backend, unsloth/Qwen3.6-35B-A3B-GGUF:Q4_K_XL model, caveman workflow, and codex/opencode/hermes/title toolset
command: grep -q 'HARBOR_DEMO_BACKEND:-llamacpp' scripts/demos/launch-grid.sh && grep -q 'HARBOR_DEMO_MODEL:-unsloth/Qwen3.6-35B-A3B-GGUF:Q4_K_XL' scripts/demos/launch-grid.sh && grep -q 'WORKFLOW="${HARBOR_DEMO_WORKFLOW:-caveman}"' scripts/demos/launch-grid.sh && grep -q 'TOOLS="codex,opencode,hermes,title"' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid demo uses viewer-readable per-tool CLI syntax for codex exec, hermes chat -Q -q, and pi --session-dir -p
command: grep -q 'TOOL_PROMPT_DISPLAY\[codex\]="exec' scripts/demos/launch-grid.sh && grep -q 'TOOL_PROMPT\[hermes\]="chat -Q -q' scripts/demos/launch-grid.sh && grep -q 'TOOL_PROMPT\[pi\]="--session-dir' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid demo applies the same Boost workflow to every assistant pane and uses a title pane rendered with glow
command: grep -q title scripts/demos/launch-grid.sh && grep -q find_glow scripts/demos/launch-grid.sh && grep -q workflow scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid demo dry-run prints a compact Pane/Command table with human-readable quoted prompts and --workflow on every assistant launch command
command: out=$(./scripts/demos/launch-grid.sh --dry-run --yes); grep -q "==> Dry run" <<<"$out" && grep -q "Pane Command" <<<"$out" && grep -q -- "--workflow caveman codex exec" <<<"$out" && grep -q -- "--workflow caveman opencode run" <<<"$out" && grep -q -- "--workflow caveman hermes chat -Q -q" <<<"$out" && grep -q "Tool route. Boost shape. Local model answer." <<<"$out" && ! grep -q "bash -lc" <<<"$out"
tags: [spec, implemented]
- label: launch-grid help examples use valid grids with at least two assistant tools and keep Boost workflow examples visible
command: ./scripts/demos/launch-grid.sh --help | grep -q -- "--workflow deephop --tools codex,opencode,hermes,title" && ./scripts/demos/launch-grid.sh --help | grep -q -- "--workflow quickhop --tools codex,opencode,hermes,title" && ./scripts/demos/launch-grid.sh --help | grep -q -- "--panes 3 --tools grok,pi,title" && ! ./scripts/demos/launch-grid.sh --help | grep -q -- "codex,title"
tags: [spec, implemented]
- label: launch-grid duration mode tracks assistant completion marker files so the intro pane does not prevent early completion
command: grep -q 'DEMO_DONE_DIR' scripts/demos/launch-grid.sh && grep -q \\*.done scripts/demos/launch-grid.sh && grep -q 'DEMO_ASSISTANT_COUNT' scripts/demos/launch-grid.sh && ! grep -q 'pane_dead' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid assistant panes hide adapter stderr unless the command fails, while grok uses --no-wait-for-background for scale demos
command: grep -q 'TOOL_QUIET_STDERR' scripts/demos/launch-grid.sh && grep -q 'cat .*stderr_log.*>&2' scripts/demos/launch-grid.sh && grep -q -- '--no-wait-for-background' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid demo chooses a concise default prompt from the selected Boost workflow when HARBOR_DEMO_TASK is unset
command: grep -q 'default_task_for_workflow' scripts/demos/launch-grid.sh && grep -q 'HARBOR_DEMO_TASK+x' scripts/demos/launch-grid.sh && grep -q 'Quickhop search brief first' scripts/demos/launch-grid.sh && grep -q 'Deephop checks first pass' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-grid title pane explains harbor launch routing, the selected Boost workflow behavior, and what viewers should compare across tools
command: grep -q 'What is happening' scripts/demos/launch-grid.sh && grep -q 'harbor launch starts or reuses' scripts/demos/launch-grid.sh && grep -q 'Workflow behavior' scripts/demos/launch-grid.sh && grep -q 'Compare panes' scripts/demos/launch-grid.sh
tags: [spec, implemented]
- label: launch-walkthrough demo presents harbor launch in tmux as a paced story: status, configuration preview, direct host-tool launch, and web-enabled Boost routing without pre-starting Boost or SearXNG outside the visible web step
command: out=$(bash scripts/demos/launch-walkthrough.sh --dry-run); grep -q -- '--web' <<<"$out" && grep -q -- '--config' <<<"$out" && bash -n scripts/demos/launch-walkthrough.sh && ! rg -q '^[[:space:]]*ensure_service[[:space:]]+boost$' scripts/demos/launch-walkthrough.sh && ! rg -q '^[[:space:]]*ensure_service[[:space:]]+searxng$' scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough demo rejects unsupported tools and Claude before building the web step
command: ! ./scripts/demos/launch-walkthrough.sh --dry-run --tool claude >/tmp/harbor-launch-walkthrough-claude.out 2>&1 && grep -q "does not support --web" /tmp/harbor-launch-walkthrough-claude.out && ! ./scripts/demos/launch-walkthrough.sh --dry-run --tool unknown >/tmp/harbor-launch-walkthrough-unknown.out 2>&1 && grep -q "Unsupported demo tool" /tmp/harbor-launch-walkthrough-unknown.out
tags: [spec, implemented]
- label: launch-walkthrough demo writes narrative text through a temp file and shell-quotes --allow-missing-tool fallback messages
command: grep -q "NARRATIVE_FILE=" scripts/demos/launch-walkthrough.sh && grep -q "narrative_render_cmd" scripts/demos/launch-walkthrough.sh && grep -q "missing_tool_cmd" scripts/demos/launch-walkthrough.sh && grep -q -- "--allow-missing-tool" scripts/demos/launch-walkthrough.sh && ! grep -q "cat <<'NARRATIVE'" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough demo uses a tmux client-attached hook so the step driver starts only after viewers can see panes update live
command: grep -q "client-attached" scripts/demos/launch-walkthrough.sh && grep -q "run-shell -b" scripts/demos/launch-walkthrough.sh && grep -q "tmux attach-session" scripts/demos/launch-walkthrough.sh && ! grep -q "run_demo &" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough dry-run validates the selected web-compatible tool and prints the four planned steps without requiring harbor or tmux on PATH
command: PATH=/usr/bin:/bin bash scripts/demos/launch-walkthrough.sh --dry-run --tool mi --backend test-backend --model test-model --task test | grep -q "Step 4: Web tools" && ! PATH=/usr/bin:/bin bash scripts/demos/launch-walkthrough.sh --dry-run --tool claude >/tmp/launch-walkthrough-claude.out 2>&1 && grep -q "does not support --web" /tmp/launch-walkthrough-claude.out
tags: [spec, implemented]
- label: launch-walkthrough live mode creates narrative and action panes, installs a tmux client-attached hook, and starts the step driver only after attach
command: grep -q "client-attached" scripts/demos/launch-walkthrough.sh && grep -q "run-shell -b" scripts/demos/launch-walkthrough.sh && grep -q "tmux attach-session" scripts/demos/launch-walkthrough.sh && grep -q "NARRATIVE_PANE" scripts/demos/launch-walkthrough.sh && grep -q "ACTION_PANE" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough workflow uses a product-focused launch-route opening, explicit action headings, and a web proof step that shows harbor ps before and after harbor launch --web
command: bash scripts/demos/launch-walkthrough.sh --dry-run | grep -q "Opening: Launch route" && ! bash scripts/demos/launch-walkthrough.sh --dry-run | grep -qi "what to watch\|good TUI\|readable, paced\|Charm glow narrative" && grep -q "run_action_command" scripts/demos/launch-walkthrough.sh && grep -q "Before --web" scripts/demos/launch-walkthrough.sh && grep -q "After --web" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough live mode hides completion markers with PROMPT_COMMAND, records command status, and requires host tools unless --allow-missing-tool is passed
command: grep -q "PROMPT_COMMAND" scripts/demos/launch-walkthrough.sh && grep -q "STATUS_FILE" scripts/demos/launch-walkthrough.sh && grep -q -- "--allow-missing-tool" scripts/demos/launch-walkthrough.sh && grep -q "Host tool.*is not installed.*--allow-missing-tool" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough renders narrative cards with Charm glow when available and falls back to plain cat when glow is missing
command: grep -q "find_glow" scripts/demos/launch-walkthrough.sh && grep -q -- "--style dark" scripts/demos/launch-walkthrough.sh && grep -q "printf.*cat %s" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough defaults to slower capture-friendly pacing with a hold delay after visible commands
command: bash scripts/demos/launch-walkthrough.sh --dry-run | grep -q "Hold delay" && grep -q "HOLD_DELAY" scripts/demos/launch-walkthrough.sh && grep -q "hold_capture" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough reduces action-pane noise by using a minimal prompt, pane titles for action labels, and no printed marker strings
command: grep -q "PS1=\\$ " scripts/demos/launch-walkthrough.sh && grep -q "select-pane.*-T" scripts/demos/launch-walkthrough.sh && ! grep -q "printf .*HARBOR_WALKTHROUGH_DONE" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough visible titles and explainers describe Harbor behavior, not demo-production advice or meta-commentary
command: ! bash scripts/demos/launch-walkthrough.sh --dry-run | grep -qi "what to watch\|good TUI\|readable, paced\|honest\|Charm glow narrative\|stable panes\|live output\|capture" && grep -q "Opening: Launch route" scripts/demos/launch-walkthrough.sh && grep -q "harbor launch connects host coding tools" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough narrative pane renders cards by respawning the pane instead of typing shell commands into it
command: grep -q "respawn-pane" scripts/demos/launch-walkthrough.sh && grep -q "narrative_pane_cmd" scripts/demos/launch-walkthrough.sh && ! grep -q "send-keys -t \"\$NARRATIVE_PANE\" \"clear" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-walkthrough opening and completion cards do not render as numbered steps
command: bash scripts/demos/launch-walkthrough.sh --dry-run | grep -q "Opening: Launch route" && grep -q "show_card" scripts/demos/launch-walkthrough.sh && ! grep -q "show_narrative 0" scripts/demos/launch-walkthrough.sh && ! grep -q "show_narrative \"\$TOTAL_STEPS\" \"\$TOTAL_STEPS\" \"Demo complete\"" scripts/demos/launch-walkthrough.sh
tags: [spec, implemented]
- label: launch-tui demo dry-run prints a sequential harbor launch story with status, config, direct, web-before, web, web-after, and recap steps without requiring Textual or harbor on PATH
command: PATH=/usr/bin:/bin python3 scripts/demos/launch-tui.py --dry-run --tool mi --backend test-backend --model test-model | grep -q 'Step 6: Recap' && PATH=/usr/bin:/bin python3 scripts/demos/launch-tui.py --dry-run --tool mi --backend test-backend --model test-model | grep -q -- 'harbor launch --web --backend test-backend --model test-model mi -p'
tags: [spec, implemented]
- label: launch-tui demo rejects Claude and unsupported tools before building the web-enabled step
command: ! python3 scripts/demos/launch-tui.py --dry-run --tool claude >/tmp/harbor-launch-tui-claude.out 2>&1 && grep -q 'does not support --web' /tmp/harbor-launch-tui-claude.out && ! python3 scripts/demos/launch-tui.py --dry-run --tool unknown >/tmp/harbor-launch-tui-unknown.out 2>&1 && grep -q 'Unsupported demo tool' /tmp/harbor-launch-tui-unknown.out
tags: [spec, implemented]
- label: launch-tui --tui mode is a Textual dashboard with a step sidebar, top route bar, command output panel, and explanation panel, while dry-run avoids importing Textual
command: python3 -m py_compile scripts/demos/launch-tui.py && grep -q 'class HarborLaunchDemoApp' scripts/demos/launch-tui.py && grep -q 'import_textual' scripts/demos/launch-tui.py && grep -q 'StepList' scripts/demos/launch-tui.py && grep -q 'output-log' scripts/demos/launch-tui.py && grep -q 'explain-panel' scripts/demos/launch-tui.py
tags: [spec, implemented]
- label: launch-tui demo supports capture-friendly run controls: --allow-missing-tool, --duration, --record, and --capture-mode asciinema
command: python3 scripts/demos/launch-tui.py --help | grep -q -- '--allow-missing-tool' && python3 scripts/demos/launch-tui.py --help | grep -q -- '--duration' && python3 scripts/demos/launch-tui.py --help | grep -q -- '--record' && python3 scripts/demos/launch-tui.py --help | grep -q -- '--capture-mode' && grep -q 'asciinema rec' scripts/demos/launch-tui.py
tags: [spec, implemented]
- label: launch-tui demo can be run directly with uv because the script declares its Textual dependency in a PEP 723 metadata block and help shows the uv run command
command: head -n 20 scripts/demos/launch-tui.py | grep -q 'dependencies = \["textual' && python3 scripts/demos/launch-tui.py --help | grep -q 'uv run scripts/demos/launch-tui.py'
tags: [spec, implemented]
- label: launch-tui demo opens with a visible product explanation that says it will run real harbor launch commands and shows the config direct and web route before execution
command: grep -q 'What this demo does' scripts/demos/launch-tui.py && grep -q 'This demo runs real Harbor commands' scripts/demos/launch-tui.py && grep -q 'Config -> Direct -> Web route' scripts/demos/launch-tui.py
tags: [spec, implemented]
- label: launch-tui demo names the Textual app Harbor Launch Demo and the prelaunch confirmation tells users to run --dry-run first if they only want the command plan
command: grep -q 'TITLE = "Harbor Launch Demo"' scripts/demos/launch-tui.py && grep -q -- '--dry-run first' scripts/demos/launch-tui.py
tags: [spec, implemented]
- label: launch-tui demo defaults to a narrated plain-terminal story and reserves the full-screen Textual dashboard for --tui
command: python3 scripts/demos/launch-tui.py --help | grep -q -- '--tui' && grep -q 'run_story' scripts/demos/launch-tui.py && grep -q 'run_live' scripts/demos/launch-tui.py && python3 scripts/demos/launch-tui.py --dry-run | grep -q 'What this proves'
tags: [spec, implemented]
- label: launch-tui story mode explains harbor launch as a route builder before commands run and labels every step with what it proves, why it matters, and what to look for
command: grep -q 'route builder' scripts/demos/launch-tui.py && grep -q 'What this proves' scripts/demos/launch-tui.py && grep -q 'Why it matters' scripts/demos/launch-tui.py && grep -q 'What to look for' scripts/demos/launch-tui.py
tags: [spec, implemented]
- label: launch-tui story mode displays canonical harbor commands while executing ./harbor.sh as a repo-local fallback when harbor is not on PATH
command: grep -q 'resolve_harbor_bin' scripts/demos/launch-tui.py && grep -q './harbor.sh' scripts/demos/launch-tui.py && grep -q 'display_command' scripts/demos/launch-tui.py
tags: [spec, implemented]
# pull
- label: harbor pull passes leading flags like --no-defaults to the compose resolver instead of misrouting them as model specs
command: bash -c 'source /dev/null; grep -A4 "flag_args=()" harbor.sh | grep -q "service_args=()"' && grep -q 'compose_with_options "${flag_args\[@\]}" "${service_args\[@\]}"' harbor.sh
tags: [spec, implemented]
- label: harbor pull rejects a bare unknown token (no slash or colon, not a service) with an unknown-service error and starts nothing
command: timeout 60 ./harbor.sh pull definitely-not-a-real-service-xyz 2>&1 | grep -qi 'unknown service' && ! timeout 60 ./harbor.sh pull definitely-not-a-real-service-xyz >/dev/null 2>&1
tags: [spec, pull-unknown-reject, implemented]
# tools
## lint
- label: the lint file collector never descends into service runtime dirs (services/*/data, workspace, vectordb, meili_data*) — gitignored container-generated trees that contain root-owned dirs and third-party scripts which crash or fail the scan
command: grep -q 'services/\*/workspace' .scripts/lint/util.ts && grep -q 'services/\*/vectordb' .scripts/lint/util.ts
tags: [spec, implemented]
# libretranslate
- label: compose.libretranslate.yml runs the container as the host user (user: HARBOR_USER_ID:HARBOR_GROUP_ID with HOME=/home/libretranslate) — upstream image uid 1032 cannot write workspace mounts chowned to the host user by the init sidecar
command: grep -q 'user: "${HARBOR_USER_ID}:${HARBOR_GROUP_ID}"' services/compose.libretranslate.yml && grep -q 'HOME=/home/libretranslate' services/compose.libretranslate.yml && grep -q 'XDG_DATA_HOME=/home/libretranslate/.local/share' services/compose.libretranslate.yml
tags: [spec, implemented]
# install
## macos
- requirements.sh brew_install treats a bare docker CLI without any engine provider (Docker.app/OrbStack.app present, colima installed, or reachable daemon) as missing Docker and installs the Docker Desktop cask instead of proceeding with a broken setup @spec @macos-docker-provider @implemented
- label: Stubbed unit tests in .scripts/test-requirements-macos.sh (harbor dev test-requirements-macos) source requirements.sh with PATH shims for uname/brew/docker/open and assert brew_install branch behavior: no docker -> cask install; bare CLI without provider -> warning + cask install; CLI with provider (OrbStack.app) -> no cask; reachable daemon -> proceed with no warnings; plus all macos_has_docker_provider detection variants
command: bash .scripts/test-requirements-macos.sh --no-bash32
tags: [spec, macos-docker-provider, implemented]
- label: requirements.sh executes main only when HARBOR_REQUIREMENTS_SOURCE_ONLY is unset, so test harnesses can source its functions without triggering the install flow
command: grep -q 'HARBOR_REQUIREMENTS_SOURCE_ONLY' requirements.sh && bash -c 'HARBOR_REQUIREMENTS_SOURCE_ONLY=1 . ./requirements.sh && type brew_install >/dev/null'
tags: [spec, macos-docker-provider, implemented]
## defaults
- label: The distributed default profile (profiles/default.env) sets HARBOR_SERVICES_DEFAULT to webui;llamacpp — ollama is not a default service, so fresh installs avoid large Ollama downloads
command: grep -q "HARBOR_SERVICES_DEFAULT=\"webui;llamacpp\"" profiles/default.env
tags: [spec, default-services-no-ollama, implemented]
- label: Cognee's dependency on ollama-init lives in the cross-file compose.x.cognee.ollama.yml, not the base compose.cognee.yml, so 'harbor up cognee' resolves without ollama in the default services
command: grep -A2 "depends_on" services/compose.cognee.yml | grep -q ollama-init && exit 1 || grep -q "ollama-init" services/compose.x.cognee.ollama.yml
tags: [spec, default-services-no-ollama, implemented]
## webui speaches
- label: start_webui.sh runs json_config_merger.py with --flatten so the merged Open WebUI config lands as dot-path keys (audio.tts.engine, openai.api_base_urls) matching Open WebUI's per-key config import
command: grep -q -- --flatten services/webui/start_webui.sh
tags: [spec, speaches-webui, implemented]
- label: json_config_merger.py flatten mode stops at Open WebUI's dict-valued registered keys (models.default_params, openai.api_configs, ...) and rewrites legacy openai.enabled/ollama.enabled to openai.enable/ollama.enable
command: grep -q DICT_VALUE_KEYS shared/json_config_merger.py && grep -q 'openai.enable' shared/json_config_merger.py
tags: [spec, speaches-webui, implemented]
- On a fresh boot of 'harbor up webui speaches', Open WebUI's config rows point STT and TTS at http://speaches:8000/v1 and TTS/STT requests through Open WebUI's audio API succeed against speaches @spec @speaches-webui @implemented
## webui config fragments
- label: Every services/webui/configs/config.*.json fragment flattens cleanly via json_config_merger flatten_config, and each resulting dot-path key (after the startup rag.web -> web rename) matches a registered Open WebUI per-key config key
command: python3 -c "import json,sys; sys.path.insert(0,'shared'); from json_config_merger import flatten_config; import os; d='services/webui/configs'; [flatten_config(json.load(open(os.path.join(d,f)))) for f in os.listdir(d) if f.endswith('.json')]"
tags: [webui-config-fragments, spec, implemented]
- On a fresh webui DB with searxng, openterminal, and comfyui fragments mounted, terminal_server.connections, web.search.*, and image_generation.* config rows land correctly; web search returns real results and the terminal server verify endpoint reports status true @webui-config-fragments @spec @implemented
## env
- label: install.sh defaults its install path to HARBOR_HOME when set (explicit HARBOR_INSTALL_PATH still wins), so a curl-install on a machine with HARBOR_HOME exported lands where harbor.sh resolves its home instead of ~/.harbor
command: grep -q 'HARBOR_INSTALL_PATH:-${HARBOR_HOME:-' install.sh
tags: [spec, install-harbor-home, implemented]
## first-run
- label: When 'harbor up' includes llamacpp with no model specifier and no *.gguf in the HF cache, the CLI prints a notice with a concrete 'harbor pull' example instead of leaving frontends with an empty model list
command: grep -q 'llama.cpp has no local models yet' harbor.sh
tags: [spec, llamacpp-empty-cache-notice, implemented]
- 'harbor pull <org/repo:quant>' on a default config auto-detects a GGUF HuggingFace repo and downloads it via an ephemeral llamacpp container into HARBOR_HF_CACHE, creating the hub cache layout; verified end-to-end with ggml-org/tiny-llamas:stories15M-q4_0 on an isolated install @spec @pull-first-run @implemented
- label: run_llamacpp_pull runs the ephemeral llamacpp container as the host user (--user id -u:id -g) with HOME remapped to /tmp and the HF and llama.cpp caches mounted at the remapped location, so pulled model blobs land owned by the host user on the host
command: grep -A12 'llamacpp_cache_path=$(env_manager get llamacpp.cache)' harbor.sh | grep -q -- '--user "$(id -u):$(id -g)"' && grep -A14 'llamacpp_cache_path=$(env_manager get llamacpp.cache)' harbor.sh | grep -q 'HOME=/tmp'
tags: [spec, pull-ownership, implemented]
- After 'harbor pull ggml-org/tiny-llamas:stories15M-q4_0' into a fresh HARBOR_HF_CACHE, every file under the cache is owned by the host user and a subsequent 'harbor up llamacpp' discovers the model via /v1/models with need_download false @spec @pull-ownership @implemented
## bash32
- label: harbor.sh contains no bash-4-only builtins (mapfile/readarray/associative arrays); run_restart collects active services via word-splitting so 'harbor restart' works on stock macOS bash 3.2
command: ! grep -nE '\bmapfile\b|\breadarray\b|declare -A' harbor.sh
tags: [spec, bash32-compat, implemented]
- label: Lint rule HARBOR012 (error severity) flags mapfile/readarray/declare -A in host-run scripts (harbor.sh, install.sh, requirements.sh, shared, tests, .scripts) with fixtures validated by lint-self-test
command: grep -q 'id: HARBOR012' .scripts/lint/rules.yaml && test -f .scripts/lint/fixtures/HARBOR012/fail.sh && test -f .scripts/lint/fixtures/HARBOR012/pass.sh
tags: [spec, bash32-compat, implemented]
- label: run_restart word-splits get_active_services output (a single space-separated line), so restarting with multiple active services restarts each service instead of failing on a bogus combined name like 'llamacpp webui'
command: grep -A3 'emits one space-separated line' harbor.sh | grep -q 'for word in $active_services'
tags: [spec, restart-multi-service, implemented]
- label: The macOS requirements test battery re-executes itself inside a bash:3.2 container (stock macOS bash), asserting the sourced requirements.sh path and the test harness itself contain no bash-4-only constructs
command: bash .scripts/test-requirements-macos.sh
tags: [spec, bash32-compat, implemented]