robisie 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +76 -12
  3. package/dist/active-recovery.d.ts +58 -0
  4. package/dist/active-recovery.js +143 -0
  5. package/dist/agents.d.ts +34 -0
  6. package/dist/agents.js +77 -16
  7. package/dist/attempt-identity.d.ts +21 -0
  8. package/dist/attempt-identity.js +39 -0
  9. package/dist/ci-failure-result.d.ts +23 -0
  10. package/dist/ci-failure-result.js +90 -0
  11. package/dist/claude-headless.d.ts +7 -0
  12. package/dist/claude-headless.js +28 -0
  13. package/dist/cli.d.ts +17 -0
  14. package/dist/cli.js +55 -15
  15. package/dist/commands.d.ts +18 -0
  16. package/dist/commands.js +190 -85
  17. package/dist/constants.d.ts +32 -0
  18. package/dist/constants.js +3 -3
  19. package/dist/context.d.ts +24 -0
  20. package/dist/context.js +22 -6
  21. package/dist/dependency-readiness.d.ts +13 -0
  22. package/dist/dependency-readiness.js +80 -0
  23. package/dist/dispatch-scope.d.ts +18 -0
  24. package/dist/dispatch-scope.js +46 -0
  25. package/dist/done-evidence.d.ts +31 -0
  26. package/dist/done-evidence.js +216 -0
  27. package/dist/engine-config.d.ts +65 -0
  28. package/dist/engine-config.js +139 -0
  29. package/dist/fsops.d.ts +87 -0
  30. package/dist/fsops.js +117 -24
  31. package/dist/helpers.d.ts +15 -0
  32. package/dist/helpers.js +262 -45
  33. package/dist/intent-rules/admission.d.ts +10 -0
  34. package/dist/intent-rules/admission.js +35 -0
  35. package/dist/intent-rules/done-when-roles.d.ts +21 -0
  36. package/dist/intent-rules/done-when-roles.js +101 -0
  37. package/dist/intent-rules/hooks.d.ts +29 -0
  38. package/dist/intent-rules/hooks.js +23 -0
  39. package/dist/k3-codex-role.d.ts +20 -0
  40. package/dist/k3-codex-role.js +68 -0
  41. package/dist/manifest.d.ts +41 -0
  42. package/dist/manifest.js +59 -14
  43. package/dist/mcp.d.ts +18 -0
  44. package/dist/mcp.js +89 -39
  45. package/dist/opencode-result.d.ts +19 -0
  46. package/dist/opencode-result.js +136 -0
  47. package/dist/opencode-review-guard.d.ts +1 -0
  48. package/dist/opencode-review-guard.js +19 -0
  49. package/dist/opencode-session-export.d.ts +5 -0
  50. package/dist/opencode-session-export.js +28 -0
  51. package/dist/outcome-moves.d.ts +15 -0
  52. package/dist/outcome-moves.js +39 -0
  53. package/dist/planer-client.d.ts +214 -0
  54. package/dist/planer-client.js +351 -0
  55. package/dist/restart-recovery.d.ts +47 -0
  56. package/dist/restart-recovery.js +70 -0
  57. package/dist/review-source-packet.d.ts +17 -0
  58. package/dist/review-source-packet.js +44 -0
  59. package/dist/role-registry.d.ts +19 -0
  60. package/dist/role-registry.js +83 -0
  61. package/dist/role-results.d.ts +15 -0
  62. package/dist/role-results.js +42 -0
  63. package/dist/roles.d.ts +30 -0
  64. package/dist/roles.js +20 -16
  65. package/dist/seed-validation.d.ts +28 -0
  66. package/dist/seed-validation.js +294 -0
  67. package/dist/selection-result.d.ts +44 -0
  68. package/dist/selection-result.js +144 -0
  69. package/dist/sparse-policy.d.ts +8 -0
  70. package/dist/sparse-policy.js +49 -0
  71. package/dist/tester-identity.d.ts +11 -0
  72. package/dist/tester-identity.js +53 -0
  73. package/dist/worker-env.d.ts +5 -0
  74. package/dist/worker-env.js +34 -0
  75. package/docs/ENGINE-API.md +78 -0
  76. package/docs/ENGINE-CONFIG.md +115 -0
  77. package/docs/README.en.md +24 -15
  78. package/docs/README.md +26 -15
  79. package/docs/RELEASE-0.1.0.md +81 -8
  80. package/docs/RELEASE-0.1.1.md +110 -0
  81. package/helpers/ceo-client-launcher-mcp.mjs +36 -8
  82. package/helpers/client-spawn-session.sh +181 -0
  83. package/helpers/client-spawn.sh +825 -0
  84. package/package.json +9 -4
  85. package/roles/ceo-client.md +3 -2
  86. package/roles/manager-builder-client.md +18 -4
  87. package/roles/quality-officer-client.md +20 -6
package/CHANGELOG.md CHANGED
@@ -1,11 +1,80 @@
1
1
  # Historia zmian klienta / Client changelog
2
2
 
3
+ ## 0.1.1 (2026-10-30)
4
+
5
+ Wydanie utrzymaniowe: bezpieczniejsza instalacja, odzyskiwanie i diagnostyka oraz biblioteki klienta. Poniższe 45 kart ma odbiór **TAK lokalnie**; sprawdzenie wersji z rejestru npm pozostaje osobną bramką. / Maintenance release: safer installation, recovery and diagnostics, plus client libraries. The following 45 cards have **local acceptance**; verification of the npm registry release remains a separate gate.
6
+
7
+ ### E3
8
+
9
+ - `10393507` — init i doctor uwzględniają wersję kontraktu każdej odpowiedzi.
10
+ - `13bafb12` — odbiorca klienta ustala dokładny kandydat do sprawdzenia.
11
+ - `18235645` — paczka wybiera rozłączne zakresy równoległych kart.
12
+ - `2e9242da` — klient rozróżnia awarię CI i błąd kodu z dostarczonych wyników.
13
+ - `4316e68e` — recenzent klienta czyta raport związany z kartą i kandydatem.
14
+ - `5084ba5e` — uninstall zachowuje rolę, gdy brakuje jej oryginału w kopii.
15
+ - `59255cbe` — doctor wykrywa brak instrukcji uruchomienia zainstalowanych ról.
16
+ - `695e6eed` — klient sprawdza komplet lokalnego pakietu bez skryptu hosta.
17
+ - `737bbf62` — reguły intencji klienta używają ról jego instancji.
18
+ - `78225362` — uninstall zachowuje manifest do zakończenia sprzątania.
19
+ - `79f7e41f` — doctor sprawdza kontekst projektu używany przez pomocnik klienta.
20
+ - `80931e36` — init zachowuje działający profil logowania Bash.
21
+ - `8d297c6f` — niepełny ostatni werdykt nie odziedzicza wcześniejszego PASS.
22
+ - `945c44b9` — init sprawdza odczyt wszystkich docelowych plików przed pierwszym zapisem.
23
+ - `9d10bca2` — paczka odtwarza aktywne zadania z zachowanej tożsamości.
24
+ - `a6e291b6` — instalacja odmawia względnych katalogów konfiguracji.
25
+ - `af3a5bc3` — init odrzuca znane konflikty konfiguracji przed zapisaniem instalacji.
26
+ - `b9402172` — klient rozpoznaje rzeczywiście odebrane zależności karty.
27
+ - `bb29afd9` — klient API tworzy pierwszy tor i szkic karty.
28
+ - `da4ddc8e` — konsument TypeScript korzysta z typów zainstalowanej paczki.
29
+ - `dcf3218b` — doctor potwierdza pełny start pomocnika przed uznaniem go za sprawnego.
30
+ - `dd86b397` — paczka wiąże dowód startu z właściwą próbą zadania.
31
+ - `e65b667c` — uszkodzona ewidencja instalacji odmawia przed zmianą plików.
32
+ - `fb07c02f` — klient zachowuje decyzję odzyskania pracy po restarcie.
33
+ - `ff361096` — odczyt karty z API paczki zachowuje indeks jej pełnych danych.
34
+ - `ff998946` — klient odczytuje pełną odpowiedź selektora bez importu hosta.
35
+
36
+ ### E4
37
+
38
+ - `b231a265` — obcy agent uruchamia przykład konfiguracji silnika z paczki.
39
+ - `c781f9f0` — agent korzysta z API paczki bez zgadywania odczytu i ponowienia.
40
+ - `deb1e7d2` — doctor odróżnia niedostępny plik instalacji od braku klucza.
41
+ - `805aad70` — Operator B podłącza paczkę bez klucza w argumentach i bez kasowania cudzego MCP.
42
+
43
+ ### E5
44
+
45
+ - `0b0940cc` — powrót klienta przechodzi przez dwa rzeczywiste archiwa paczki.
46
+ - `5e579f94` — ponowne pakowanie usuwa wycofane artefakty klienta.
47
+ - `6964272e` — notatka wydania odpowiada faktycznej zawartości klienta.
48
+
49
+ ### E6
50
+
51
+ - `1bd3a86d` — niepełny zagnieżdżony blok nie usuwa cudzych instrukcji.
52
+ - `31a377ef` — init przywraca prywatność własnego katalogu instalacji.
53
+ - `3b250acb` — kopie konfiguracji klienta pozostają w prywatnym katalogu.
54
+ - `58a45de0` — eksport sesji klienta nie interpretuje identyfikatora jako polecenia.
55
+ - `5b8dd7f5` — ponowny init przywraca ochronę prywatnej konfiguracji bez zmiany jej treści.
56
+ - `6fddbaec` — init nie przekazuje poświadczenia pod adres z przekierowania.
57
+ - `8b9882ba` — błąd połączenia klienta nie kopiuje sekretu do diagnostyki.
58
+ - `ac688abb` — role planowania i recenzji OpenCode nie dostają powłoki ani edycji.
59
+ - `ad251014` — nieudany proces Codex nie przenosi stderr ani cause do wyjątku klienta.
60
+ - `af5c973c` — odmowa pomocnika nie przenosi jego sekretów do doctor.
61
+ - `ccf5a385` — parser konfiguracji paczki nie powiela obcej wartości w błędzie.
62
+ - `e5413393` — instalator nie zamienia cudzych bajtów konfiguracji na znaki zastępcze.
63
+
64
+ Ograniczenia odbioru: `805aad70` — krok 5 z rolami i pomocnikami w prawdziwej sesji Claude Code pozostaje niesprawdzony; `80931e36` — profil Bash sprawdzono z symulacją macOS na Linuksie, rzeczywisty przebieg na Macu pozostaje otwarty. `9d10bca2` i `13bafb12` potwierdzają konsumenta paczki; integracja z usługą klienta wymaga osobnego odbioru. / Acceptance limits: the real Claude Code session for step 5 of `805aad70` and the real Mac Bash run for `80931e36` remain unverified. `9d10bca2` and `13bafb12` establish package consumer behavior; customer service integration needs separate acceptance.
65
+
66
+ Pełny opis, wymagania i granice: [notatka PL/EN 0.1.1](docs/RELEASE-0.1.1.md). / Requirements, behavior and limits: [0.1.1 PL/EN release note](docs/RELEASE-0.1.1.md).
67
+
3
68
  ## 0.1.0
4
69
 
5
70
  Kandydat pierwszego wydania, bez daty publikacji. / First release candidate, no publication date.
6
71
 
7
72
  - `init`, `doctor`, `uninstall` dla Claude Code, Codex CLI i OpenCode; role CEO, Builder i Quality Officer.
8
73
  - Pomocniki `client-launch-context` i `ceo-client-launcher` dostarczane w paczce, polecenia przypięte do wersji klienta.
74
+ - Po zgodzie na kartę `ready`: CEO → `ceo-client-launcher` → `spawn_builder`; ręczny start wyłącznie z wyboru operatora. / After approval of a `ready` card, the CEO starts the Builder through the launcher; manual startup is the operator's explicit choice.
75
+ - Biblioteki ESM: `engine-config`, `role-results`, `planer-client`, rejestr ról, walidacja zasiewu/reguły intencji, wybór/zakres, ruchy/odzyskiwanie, tożsamość prób/testera, środowisko Workera, źródła recenzji, wyniki CI i adaptery Claude/Codex/OpenCode z eksportem sesji. Konfiguracja instancji w `robisie.config.json`; wykaz i przykład w [notatce PL/EN](docs/RELEASE-0.1.0.md), SDK w [API silnika](docs/ENGINE-API.md). / ESM configuration, role outcomes, board SDK and engine support libraries are shipped; imports and helpers do not prove a running engine or an autonomous loop.
76
+ - Instalator nie startuje tła/Buildera; doctor nie dowodzi dostępności narzędzi u dostawcy. Claude Code wymaga niezależnej recenzji, verified receipt i odbioru; OpenCode zostaje w `in_review`, Codex wymaga integracji Agent. / Installation and doctor do not establish provider capabilities or acceptance: independent review, verified receipt and operator acceptance remain required; OpenCode stops at `in_review`, Codex needs Agent integration.
77
+ - Wymagania 0.1: macOS/Linux, Node >=20; wsparcie Windows/WSL niezweryfikowane. / Requirements: macOS/Linux, Node >=20; Windows/WSL support is unverified.
9
78
  - Kontrakt serwera 1.x (obecnie 1.0.0); odmowa innego major przed zapisem konfiguracji.
10
79
  - Ochrona obcych ustawień i instrukcji, pierwsze kopie zapasowe oraz przywracanie własnych plików po uninstall; zmienione role pozostają z ostrzeżeniem.
11
80
  - Zapisywanie dostawcy dla każdej roli i diagnoza ograniczeń; bieżąca pętla wymaga jednego dostawcy dla wszystkich trzech ról.
package/README.md CHANGED
@@ -1,28 +1,92 @@
1
1
  # robisie 0.1.0
2
2
 
3
- Przewodnik dla otrzymanego `robisie-0.1.0.tgz`: [PL — konto → odbiór → odinstalowanie](docs/README.md) · [EN — account → acceptance → uninstall](docs/README.en.md). Zacznij od wymagań, konta i klucza w przewodniku. Przygotuj Node.js 20+, npm, program jednego dostawcy AI oraz pusty katalog z tgz i README; strona i package.json nie są potrzebne. Projekt w panelu i katalog na dysku to osobne rzeczy; CEO wymaga dokładnie jednego projektu z discovery.
3
+ Przewodniki: [PL — konto → odbiór → odinstalowanie](https://unpkg.com/robisie/docs/README.md) · [EN — account → acceptance → uninstall](https://unpkg.com/robisie/docs/README.en.md). Przygotuj Node.js 20+, npm, program jednego dostawcy AI oraz pusty katalog; strona i package.json nie są potrzebne. Zacznij od konta i projektu w panelu. Projekt w panelu i katalog na dysku to osobne rzeczy; CEO wymaga dokładnie jednego projektu z discovery.
4
4
 
5
- Człowiek generuje klucz worker z etykietą w Connect i podaje go poza rozmową do ukrytego pytania init. W zsh/bash usuń stary ENV, a po udanym init wczytaj zapisany klucz do bieżącej powłoki bez wyświetlania go — init nie ustawia jej środowiska. Dla pierwszej próby Claude:
5
+ ## Przed init — konto i pierwszy projekt
6
+
7
+ Załóż konto i zaloguj się w https://robisie.app. Ekran „Zakładamy Twój projekt…” oznacza przygotowywanie projektu; poczekaj na „Twój projekt jest gotowy”. Ten ekran nie dowodzi dokładnie jednego projektu. Nie ma gwarantowanego czasu oczekiwania. Jeśli widzisz „To się przeciąga…”, wybierz „Sprawdź tablicę”: https://robisie.app/dashboard/plugins/planer.
8
+
9
+ Na liście projektów, tylko gdy nie masz przygotowanego projektu, wpisz nazwę w „Nazwa projektu (np. robisie)” i wybierz „Załóż projekt”. Nie twórz drugiego projektu, jeśli pierwszy już jest przygotowany.
10
+
11
+ Jeśli trasa przekierowała na /dashboard bez Planera, znajdź „Zacznij od pierwszego projektu”, wpisz „Nazwa projektu” i wybierz „Stwórz projekt”. Również tutaj twórz projekt tylko wtedy, gdy nie masz jeszcze przygotowanego projektu. Potem przejdź do Connect.
12
+
13
+ ## Init — połącz agenta
14
+
15
+ Człowiek generuje klucz worker z etykietą w https://robisie.app/dashboard/connect i podaje go poza rozmową do ukrytego pytania init, nigdy do czatu, argumentu polecenia ani historii powłoki. W zsh/bash usuń stary ENV, a po udanym init wczytaj zapisany klucz do bieżącej powłoki bez wyświetlania go — init nie ustawia jej środowiska. Dla nowej instalacji z Claude:
6
16
 
7
17
  ```sh
8
- npm i ./robisie-0.1.0.tgz
18
+ npm i robisie
9
19
  unset ROBISIE_KEY
10
- npx robisie init --agent claude --role ceo=claude --role builder=claude --role quality=claude
20
+ npx robisie init --agent claude
11
21
  [ -r "${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key" ] && export ROBISIE_KEY="$(cat "${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key")"
12
22
  npx robisie doctor
13
23
  ```
14
24
 
15
- Dla Codex/OpenCode zastąp wszystkie cztery nazwy dostawcy. Sam --agent nie usuwa wcześniejszej mapy. Nowy terminal wymaga obsługi profilu zsh/bash bez --no-shell; w innych sesjach wczytaj środowisko zgodnie z przewodnikiem. Agenta uruchom z katalogu instalacji i sprawdź narzędzia obu pomocników: doctor startuje je przez node, agent przez npx; ROBISIE_PROJECT_CONTEXT nie zmienia cwd szukania paczki.
25
+ Dla Codex lub OpenCode zastąp `claude` przez `codex` lub `opencode`. Sam `--agent` nie usuwa wcześniejszej mapy ról: przy wcześniejszej konfiguracji użyj pełnej mapy z sekcji init w przewodniku. Nowy terminal wymaga obsługi profilu zsh/bash bez `--no-shell`; w innych sesjach wczytaj środowisko zgodnie z przewodnikiem. Agenta uruchom z katalogu instalacji; szczegóły startu ról, pomocników i diagnozy są w przewodnikach PL/EN powyżej.
16
26
 
17
- Człowiek startuje CEO i ręcznie Buildera. Niezależną recenzję uruchamia CEO przez Agent; bez tego narzędzia w Codex/OpenCode praca pozostaje in_review i wymaga naprawy integracji przez CEO/CR. Worker nie ustawia done; człowiek odbiera wynik w panelu po dowodach i recenzji. Pełne polecenia, wynik pierwszej próby, naprawy, aktualizacja z tgz i uninstall są w PL/EN powyżej.
27
+ ## Po init — sprawdź gotowość CEO
18
28
 
19
- Wybór i zmiana dostawcy ról:
29
+ Po Connect, init i wczytaniu klucza według bloku powyżej uruchom `npx robisie doctor`. Discovery to odczyt aktywnych projektów przez `list_projects`. Nie wymaga wpisywania UUID. Sam `init` z exit 0 ani „połączono” nie wystarczają do potwierdzenia gotowości CEO.
20
30
 
21
- ```sh
22
- npx robisie init --role ceo=claude --role builder=codex --role quality=opencode
23
- npx robisie init --role=builder=claude
31
+ - **0 projektów, exit 1:** `CEO wymaga dokładnie 1 projektu (projektów: 0).` Następny krok: `Sprawdź gotowość projektu na https://robisie.app/dashboard/plugins/planer.` Wróć do przygotowania projektu opisanego przed init.
32
+ - **Więcej niż 1 projekt, exit 1** (np. `CEO wymaga dokładnie 1 projektu (projektów: 2).`): `Uporządkuj widoczność projektów w discovery do 1; sam wybór projektu w panelu nie zmienia discovery.` Człowiek sam decyduje, który projekt zachować aktywny; przy pozostałych na liście wybiera „Archiwizuj”. „Archiwum” → „Przywróć” cofa archiwizację. Nie usuwaj projektów. Kliknięcie projektu nie zawęża discovery.
33
+ - **1 projekt:** oczekuj dokładnie `✓ Połączenie działa: klucz otwiera tablicę (projektów: 1).` oraz końcowego `Wszystko działa. Uruchom swojego agenta i poproś o rolę robisie CEO.` i exit 0.
34
+
35
+ Po zmianie ponów `npx robisie doctor`. Rozwiąż też inne błędy doctor: sam licznik 1 nie wystarcza bez końcowego komunikatu sukcesu i exit 0.
36
+
37
+ Po sukcesie człowiek potwierdza nazwę i cel projektu oraz katalog instalacji, bez klucza i UUID. Przejdź do startu CEO poniżej; to nie jest odbiór karty.
38
+
39
+ ## Start CEO
40
+
41
+ Człowiek uruchamia CEO w katalogu instalacji.
42
+
43
+ Po zgodzie człowieka CEO domyślnie wywołuje `spawn_builder` z ID karty potwierdzonej w `ready`.
44
+
45
+ Przy błędzie `spawn_builder` CEO zgłasza błąd i naprawia przyczynę przed ponowieniem; nie przełącza automatycznie na ręczny start.
46
+
47
+ Ręczny start to wyłącznie jawny wybór operatora: wtedy CEO podaje polecenie do uruchomienia w nowym terminalu w katalogu instalacji.
48
+
49
+ W Claude Code CEO uruchamia niezależnego Quality Officera przez `Agent(quality-officer-client)`. Quality Officer sprawdza wynik i zapisuje recenzję oraz dowody; Builder nie recenzuje własnej pracy. CEO weryfikuje potwierdzenie recenzji (verified receipt) i wykonuje świeże odczyty karty przed przejściem do `delivered`; następnie jawny odbiór operatora w panelu pozwala przejść do `done`. Worker nie ustawia `done`. Sam `init`/`doctor` nie daje `done`.
50
+
51
+ OpenCode: karta pozostaje w `in_review` niezależnie od dostępności narzędzia Agent; brak wspieranego przejścia do `delivered`. Pełny odbiór wymaga opisanej w przewodnikach drogi Claude Code.
52
+
53
+ Codex: przejście do delivered wymaga niezależnej recenzji oraz zweryfikowanego potwierdzenia (verified receipt). Jeśli w sesji CEO brakuje narzędzia Agent, karta pozostaje w `in_review` i wymaga naprawy integracji przez CEO/CR.
54
+
55
+ Wszystkie trzy role muszą używać jednego dostawcy; mieszana mapa daje kod 1 doctor. Paczka nie uruchamia usług w tle ani nie zapewnia konta lub dostępu do AI.
56
+
57
+ Aktualizuj w katalogu instalacji: `npm i robisie@latest` → `npx robisie init` → `npx robisie doctor`. Doctor sprawdza zgodność MAJOR kontraktu serwera; przy niezgodności wybierz zgodne wydanie. Awaryjnie otrzymany plik zainstaluj przez `npm i ./robisie-0.1.0.tgz`; lokalny powrót wymaga poprzedniego zgodnego tgz i kopii konfiguracji. Pełne instrukcje aktualizacji, powrotu i odinstalowania są w przewodnikach.
58
+
59
+ ## Lokalna walidacja rejestru wykonania K7
60
+
61
+ Zapisz pełną konfigurację K2 i osobny rejestr wykonania w `robisie.config.json`:
62
+
63
+ ```json
64
+ {
65
+ "project": "uuid-projektu",
66
+ "tracks": [{"id": "uuid-toru", "name": "Tor główny"}],
67
+ "stateDir": "./stan",
68
+ "engine": {
69
+ "repo": "./repo",
70
+ "branch": "main",
71
+ "commands": {
72
+ "test": ["npm", "test"],
73
+ "lint": ["npm", "run", "lint"],
74
+ "build": ["npm", "run", "build:check"]
75
+ },
76
+ "requiredChecks": ["test", "lint"],
77
+ "delivery": "pr"
78
+ },
79
+ "roleRegistry": {
80
+ "worker-client": {"name":"Ola","provider":"codex","model":"gpt-6.1-sol","effort":"high"},
81
+ "lead-client": {"name":"Leon","provider":"claude","model":"opus","effort":"medium"},
82
+ "reviewer-client": {"name":"Iga","provider":"opencode","model":"opencode-go/gpt-6-luna","effort":"low"},
83
+ "tester-client": {"name":"Jan","provider":"grok","model":"grok-cli","effort":"none"}
84
+ }
85
+ }
24
86
  ```
25
87
 
26
- Dostawcy: `claude`, `codex`, `opencode`; role: `ceo`, `builder`, `quality`. Mapa pozostaje po ponownym init; zmiana jednej roli zachowuje pozostałe. Każdy wskazany dostawca oraz `--agent` otrzymuje komplet ról, MCP i pomocników; poprzedni pozostaje do pełnego uninstall. Bez `--agent` init uwzględnia także wszystkich wykrytych dostawców, również nowo zainstalowanych przy ponownym uruchomieniu. Stary manifest z jednym dostawcą przypisuje mu trzy role; z wieloma zapisuje null. Init bez wyboru nadal działa; jawny wybór wymaga pełnej wynikowej mapy. Wadliwa mapa blokuje init, ale nie uninstall po entries.
88
+ Uruchom `npx robisie doctor --engine-config ./robisie.config.json` (także `--engine-config=./robisie.config.json`). Ścieżka pliku jest względem bieżącego katalogu, a `repo` i `stateDir` względem pliku konfiguracji. Role K2 `coordinator/owner/planner/advisor` zachowują domyślne nazwy; opcjonalne pole `roles` służy ich nazwom i odmianie, osobno od `roleRegistry`.
89
+
90
+ Kod 0 oznacza poprawną konfigurację K2 i dokładnie cztery kompletne przypisania K7; raport pokazuje rolę → imię / dostawcę / model / wysiłek. Kod 1 wskazuje plik, błędne pole (jeśli dotyczy) i następny krok. Ten tryb działa offline, bez klucza, uruchamiania dostawców, wykonywania komend silnika ani zapisu plików. Raport jawnie stwierdza: „sprawdzono konfigurację; nie sprawdzono uruchomienia dostawców”. Zwykły `doctor` zachowuje dotychczasową diagnozę instalacji.
27
91
 
28
- Doctor pokazuje mapę i sprawdza programy w PATH. Mieszane role oznaczają kod 1: Quality Officer działa jako podagent u dostawcy CEO, a polecenie Buildera ma format dostawcy CEO. Wykonanie mieszanych ról to następny zakres. Szczegóły i diagnoza są w przewodnikach PL/EN powyżej.
92
+ Modele i wysiłki to przykładowe, dosłowne etykiety, bez gwarancji dostępności lub obsługi przez CLI. `grok-cli`/`none` opisują obecny review-job; nie dowodzą sterowania modelem ani wysiłkiem Groka. Imię jest metadanymi rejestru; Execution v1 zachowuje `{contractVersion:1,provider,model,effort}`. Walidacja nie jest odbiorem wykonania karty, dostawców ani całego P-18/K3.
@@ -0,0 +1,58 @@
1
+ export type CardLike = {
2
+ detailsReadError?: string | null;
3
+ id: string;
4
+ title: string;
5
+ trackId?: string | null;
6
+ status?: string | null;
7
+ assignee?: string | null;
8
+ details?: Record<string, unknown> | null;
9
+ };
10
+ export declare const ACTIVE_RECOVERY_DETAILS_KEYS: readonly ["maszynista", "review", "reksio_withheld"];
11
+ export type Active = {
12
+ taskId: string;
13
+ title: string;
14
+ trackId: string | null;
15
+ jobId: string;
16
+ worktree: string;
17
+ branch: string;
18
+ startedAt: string;
19
+ attempts: number;
20
+ backend?: "claude" | "codex" | "grok-cli" | "opencode";
21
+ model?: string;
22
+ opencode?: Readonly<{
23
+ pid: number;
24
+ pidPath: string;
25
+ promptPath: string;
26
+ stdoutPath: string;
27
+ stderrPath: string;
28
+ exitPath: string;
29
+ startedAt: string;
30
+ jobId: string;
31
+ model: string;
32
+ provider: "opencode-go";
33
+ sessionId: string | null;
34
+ messageId: string | null;
35
+ }>;
36
+ };
37
+ export declare function reconcileActive(input: {
38
+ stateActives: Active[];
39
+ boardCards: CardLike[];
40
+ assignee: string;
41
+ nowIso: string;
42
+ }): {
43
+ actives: Active[];
44
+ releases: Array<{
45
+ taskId: string;
46
+ status: "ready";
47
+ assignee: null;
48
+ }>;
49
+ adopted: Array<{
50
+ active: Active;
51
+ stamp: Record<string, unknown>;
52
+ orphan?: CardLike;
53
+ }>;
54
+ skipped?: Array<{
55
+ card: CardLike;
56
+ reason: string;
57
+ }>;
58
+ };
@@ -0,0 +1,143 @@
1
+ export const ACTIVE_RECOVERY_DETAILS_KEYS = ["maszynista", "review", "reksio_withheld"];
2
+ function record(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ function asRecord(value) {
6
+ return record(value) ? value : {};
7
+ }
8
+ function positiveInteger(value) {
9
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
10
+ }
11
+ function nonempty(value) {
12
+ return typeof value === "string" && value.trim().length > 0;
13
+ }
14
+ function timestamp(value) {
15
+ return nonempty(value) && Number.isFinite(Date.parse(value));
16
+ }
17
+ export function reconcileActive(input) {
18
+ const previous = new Map(input.stateActives.map((active) => [active.taskId, active]));
19
+ const actives = [];
20
+ const releases = [];
21
+ const adopted = [];
22
+ const skipped = [];
23
+ const takeState = (card, fromState, worker) => {
24
+ const active = {
25
+ ...fromState,
26
+ title: card.title,
27
+ trackId: nonempty(card.trackId) ? card.trackId : null,
28
+ };
29
+ const meta = record(worker) ? worker : {};
30
+ const stamp = {
31
+ branchLease: meta.branchLease ?? null,
32
+ at: fromState.startedAt,
33
+ jobId: fromState.jobId,
34
+ worktree: fromState.worktree,
35
+ branch: fromState.branch,
36
+ attempts: fromState.attempts,
37
+ outcome: "dispatched",
38
+ };
39
+ if ("engine" in meta)
40
+ stamp.engine = meta.engine;
41
+ if (fromState.backend)
42
+ stamp.backend = fromState.backend;
43
+ if (fromState.model)
44
+ stamp.model = fromState.model;
45
+ if (fromState.opencode)
46
+ stamp.opencode = fromState.opencode;
47
+ actives.push(active);
48
+ adopted.push({ active, stamp });
49
+ };
50
+ for (const card of input.boardCards) {
51
+ const orphan = card.status === "in_review" && card.assignee == null && !previous.has(card.id);
52
+ if (card.assignee !== input.assignee && !orphan)
53
+ continue;
54
+ if (orphan) {
55
+ const worker = asRecord(card.details?.maszynista);
56
+ if (card.detailsReadError || card.details?.reksio_withheld === true
57
+ || worker.processStart != null)
58
+ continue;
59
+ if (worker.outcome === "ACTIONABLE_BLOCKER") {
60
+ skipped.push({ card, reason: "outcome=ACTIONABLE_BLOCKER — powrót przez blocked/ready" });
61
+ continue;
62
+ }
63
+ if (!["review", "landing"].includes(String(worker.phase))
64
+ || (worker.outcome !== "WORKER_FINISHED" && asRecord(card.details?.review).verdict !== "PASS"))
65
+ continue;
66
+ if (![worker.jobId, worker.worktree, worker.branch].every(nonempty) || !positiveInteger(worker.attempts)) {
67
+ skipped.push({ card, reason: "niepełne dane Workera (jobId, worktree, branch, attempts)" });
68
+ continue;
69
+ }
70
+ }
71
+ if (card.detailsReadError) {
72
+ const known = previous.get(card.id);
73
+ if (known)
74
+ actives.push(known);
75
+ continue; // No release/restamp can be justified without readable process identity.
76
+ }
77
+ const details = card.details ?? {};
78
+ const worker = details.maszynista;
79
+ const activeStatus = card.status === "in_progress"
80
+ || (card.status === "in_review" && record(worker) && ["review", "landing"].includes(String(worker.phase)));
81
+ if (!activeStatus)
82
+ continue;
83
+ const fromState = previous.get(card.id);
84
+ const cardJobId = record(worker) && nonempty(worker.jobId) ? worker.jobId : null;
85
+ // State wins: no jobId; different jobId without valid `at`; different jobId with older/equal `at`.
86
+ // Same jobId → card wins. Different jobId with strictly newer valid `at` → card wins. Attempts unused.
87
+ if (fromState && !cardJobId) {
88
+ takeState(card, fromState, worker);
89
+ continue;
90
+ }
91
+ if (fromState && cardJobId && cardJobId !== fromState.jobId) {
92
+ const cardAt = record(worker) ? worker.at : undefined;
93
+ const cardNewer = timestamp(cardAt) && Date.parse(cardAt) > Date.parse(fromState.startedAt);
94
+ if (!cardNewer) {
95
+ takeState(card, fromState, worker);
96
+ continue;
97
+ }
98
+ }
99
+ if (!record(worker) || !cardJobId) {
100
+ releases.push({ taskId: card.id, status: "ready", assignee: null });
101
+ continue;
102
+ }
103
+ const attempts = worker.attempts ?? worker.attempt;
104
+ // A known job must not be released and dispatched twice just because metadata is incomplete.
105
+ if (!nonempty(worker.worktree) || !nonempty(worker.branch) || !positiveInteger(attempts)) {
106
+ throw new Error(`Niepełne dane workera na karcie ${card.id}`);
107
+ }
108
+ if (worker.backend !== undefined && worker.backend !== "codex" && worker.backend !== "opencode") {
109
+ if (orphan) {
110
+ skipped.push({ card, reason: "nieobsługiwany backend Workera" });
111
+ continue;
112
+ }
113
+ throw new Error(`Nieobsługiwany backend workera na karcie ${card.id}`);
114
+ }
115
+ if ((worker.opencode !== undefined && worker.backend !== "opencode")
116
+ || (worker.backend === "opencode" && (!record(worker.opencode)
117
+ || !positiveInteger(worker.opencode.pid) || !nonempty(worker.model)
118
+ || ![worker.opencode.pidPath, worker.opencode.promptPath, worker.opencode.stdoutPath,
119
+ worker.opencode.stderrPath, worker.opencode.exitPath,
120
+ worker.opencode.jobId, worker.opencode.model].every(nonempty)
121
+ || worker.opencode.jobId !== cardJobId || worker.opencode.model !== worker.model
122
+ || worker.opencode.provider !== "opencode-go"))) {
123
+ if (orphan) {
124
+ skipped.push({ card, reason: "niepełna tożsamość OpenCode" });
125
+ continue;
126
+ }
127
+ throw new Error(`Niepełna tożsamość OpenCode na karcie ${card.id}`);
128
+ }
129
+ const old = previous.get(card.id);
130
+ const active = {
131
+ taskId: card.id, title: card.title, trackId: nonempty(card.trackId) ? card.trackId : null,
132
+ jobId: cardJobId, worktree: worker.worktree, branch: worker.branch, attempts,
133
+ startedAt: timestamp(worker.at) ? worker.at : old?.startedAt ?? input.nowIso,
134
+ ...(worker.backend === "opencode" || worker.backend === "codex" ? { backend: worker.backend } : {}),
135
+ ...(nonempty(worker.model) ? { model: worker.model } : {}),
136
+ ...(record(worker.opencode) ? { opencode: worker.opencode } : {}),
137
+ };
138
+ actives.push(active);
139
+ if (orphan)
140
+ adopted.push({ active, stamp: worker, orphan: card });
141
+ }
142
+ return { actives, releases, adopted, ...(skipped.length ? { skipped } : {}) };
143
+ }
@@ -0,0 +1,34 @@
1
+ import { type Ctx } from "./context.js";
2
+ import { type Op } from "./fsops.js";
3
+ import { type Role } from "./roles.js";
4
+ export declare const AGENT_IDS: readonly ["claude", "codex", "opencode"];
5
+ export type AgentId = (typeof AGENT_IDS)[number];
6
+ export declare const AGENT_LABELS: Record<AgentId, string>;
7
+ export declare function isAgentId(value: string): value is AgentId;
8
+ /** Where each agent keeps its user-level configuration (the same places the agents themselves read). */
9
+ export declare function agentPaths(ctx: Ctx): {
10
+ clientDir: string;
11
+ claudeDir: string;
12
+ claudeJson: string;
13
+ codexHome: string;
14
+ codexConfig: string;
15
+ codexAgentsMd: string;
16
+ opencodeDir: string;
17
+ opencodeJson: string;
18
+ opencodeJsonc: string;
19
+ };
20
+ export declare function agentOnPath(ctx: Ctx, id: AgentId): boolean;
21
+ /** Detection reads only the filesystem (config dir present, or binary on PATH) — nothing is executed. */
22
+ export declare function detectAgents(ctx: Ctx): AgentId[];
23
+ /** Keep a multiline string or array in its assignment, so its contents cannot look like server headers. */
24
+ export declare function tomlStatements(text: string): string[];
25
+ /**
26
+ * Take over an `[mcp_servers.robisie]` table written earlier by hand or by `codex mcp add` (otherwise
27
+ * Codex would see the table twice and refuse the whole config). Backups retain the old configuration.
28
+ */
29
+ export declare function removeCodexTables(text: string, removeInline?: boolean, codexHome?: string): string;
30
+ /** Every file change `init` makes for one agent. */
31
+ export declare function planAgent(ctx: Ctx, id: AgentId, roles: readonly Role[], key: string): Op[];
32
+ /** Shell profile line that exports ROBISIE_KEY from the package's key file (roles and Codex/OpenCode read it). */
33
+ export declare function shellProfile(ctx: Ctx): string | null;
34
+ export declare const SHELL_EXPORT = "[ -r \"${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key\" ] && export ROBISIE_KEY=\"$(cat \"${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key\")\"";
package/dist/agents.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { accessSync, constants, existsSync, statSync } from "node:fs";
2
- import { delimiter, join } from "node:path";
2
+ import { delimiter, isAbsolute, join } from "node:path";
3
3
  import { HELPER_ENV, HELPER_SERVERS, KEY_ENV, MCP_SERVER_NAME, helperCommand, mcpUrl } from "./constants.js";
4
- import { RobisieError } from "./context.js";
4
+ import { RobisieError, shellQuote } from "./context.js";
5
+ import { freeCopyPath } from "./fsops.js";
6
+ import { OPENCODE_REVIEW_GUARD } from "./opencode-review-guard.js";
5
7
  import { renderClaudeAgent, renderCodexRole, renderOpenCodeAgent } from "./roles.js";
6
8
  export const AGENT_IDS = ["claude", "codex", "opencode"];
7
9
  export const AGENT_LABELS = { claude: "Claude Code", codex: "Codex CLI", opencode: "OpenCode" };
@@ -9,10 +11,17 @@ const BINARIES = { claude: "claude", codex: "codex", opencode: "opencode" };
9
11
  export function isAgentId(value) {
10
12
  return AGENT_IDS.includes(value);
11
13
  }
14
+ function configRoot(ctx, name, fallback) {
15
+ const value = ctx.env[name];
16
+ if (value && !isAbsolute(value)) {
17
+ throw new RobisieError(`${name} musi wskazywać katalog ścieżką absolutną.`, `Ustaw ${name} na ścieżkę absolutną albo wykonaj \`unset ${name}\` i ponów polecenie.`);
18
+ }
19
+ return value || fallback;
20
+ }
12
21
  /** Where each agent keeps its user-level configuration (the same places the agents themselves read). */
13
22
  export function agentPaths(ctx) {
14
- const codexHome = ctx.env.CODEX_HOME || join(ctx.home, ".codex");
15
- const configHome = ctx.env.XDG_CONFIG_HOME || join(ctx.home, ".config");
23
+ const codexHome = configRoot(ctx, "CODEX_HOME", join(ctx.home, ".codex"));
24
+ const configHome = configRoot(ctx, "XDG_CONFIG_HOME", join(ctx.home, ".config"));
16
25
  const opencodeDir = join(configHome, "opencode");
17
26
  return {
18
27
  // The package's own directory. Never ~/.robisie: on the owner's machine that belongs to the factory.
@@ -54,15 +63,27 @@ export function detectAgents(ctx) {
54
63
  const dirs = { claude: paths.claudeDir, codex: paths.codexHome, opencode: paths.opencodeDir };
55
64
  return AGENT_IDS.filter((id) => existsSync(dirs[id]) || agentOnPath(ctx, id));
56
65
  }
66
+ const CODEX_TRUSTED_TOOLS = [
67
+ "append_detail",
68
+ "create_task",
69
+ "create_track",
70
+ "get_board",
71
+ "get_task",
72
+ "list_projects",
73
+ "list_tasks",
74
+ "move_task",
75
+ "reserve_task",
76
+ "update_task",
77
+ ];
57
78
  /** Every server name init writes into `[mcp_servers]`. */
58
79
  const CODEX_SERVERS = [MCP_SERVER_NAME, ...HELPER_SERVERS].map((name) => name.replace(/[-]/g, "\\-")).join("|");
59
- const CODEX_SERVER_KEY = `(?:"(?:${CODEX_SERVERS})"|'(?:${CODEX_SERVERS})'|(?:${CODEX_SERVERS}))`;
80
+ const CODEX_SERVER_KEY = `(?:"(${CODEX_SERVERS})"|'(${CODEX_SERVERS})'|(${CODEX_SERVERS}))`;
60
81
  const TOML_ROBISIE_HEADER = new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*${CODEX_SERVER_KEY}\\s*(?:\\.[^\\]]*)?\\]\\s*(?:#.*)?$`, "m");
61
82
  const TOML_ANY_HEADER = /^\s*\[/;
62
83
  const INLINE_TOP = new RegExp(`^\\s*mcp_servers\\s*\\.\\s*${CODEX_SERVER_KEY}\\s*[.=]`);
63
84
  const INLINE_IN_TABLE = new RegExp(`^\\s*${CODEX_SERVER_KEY}\\s*[.=]`);
64
85
  /** Keep a multiline string or array in its assignment, so its contents cannot look like server headers. */
65
- function tomlStatements(text) {
86
+ export function tomlStatements(text) {
66
87
  const lines = [];
67
88
  let start = 0;
68
89
  let depth = 0;
@@ -85,25 +106,43 @@ function tomlStatements(text) {
85
106
  * Take over an `[mcp_servers.robisie]` table written earlier by hand or by `codex mcp add` (otherwise
86
107
  * Codex would see the table twice and refuse the whole config). Backups retain the old configuration.
87
108
  */
88
- export function removeCodexTables(text, removeInline = false) {
109
+ export function removeCodexTables(text, removeInline = false, codexHome) {
89
110
  const lines = tomlStatements(text);
90
111
  const kept = [];
91
112
  let inRobisie = false;
113
+ let trailingComments = "";
92
114
  let table = "";
115
+ const conflicts = new Set();
93
116
  for (const line of lines) {
117
+ if (inRobisie && /^\s*(?:#[^\r\n]*)?\s*$/.test(line)) {
118
+ trailingComments += line;
119
+ continue;
120
+ }
94
121
  const header = /^\s*\[\s*([^\]]*?)\s*\]/.exec(line);
95
122
  if (header)
96
123
  table = (header[1] ?? "").replace(/\s+/g, "");
97
- if (TOML_ANY_HEADER.test(line))
124
+ if (TOML_ANY_HEADER.test(line)) {
125
+ // Comments immediately before a foreign header belong to that preserved fragment.
126
+ if (inRobisie && !TOML_ROBISIE_HEADER.test(line))
127
+ kept.push(trailingComments);
98
128
  inRobisie = TOML_ROBISIE_HEADER.test(line);
129
+ }
130
+ trailingComments = "";
99
131
  if (inRobisie)
100
132
  continue;
101
- const inline = (table === "" && INLINE_TOP.test(line)) || (table === "mcp_servers" && INLINE_IN_TABLE.test(line));
133
+ const inline = table === "" ? INLINE_TOP.exec(line) : table === "mcp_servers" ? INLINE_IN_TABLE.exec(line) : null;
102
134
  if (!inline)
103
135
  kept.push(line);
104
- else if (!removeInline) {
105
- throw new RobisieError("W ~/.codex/config.toml jest już serwer robisie zapisany w formie, której nie umiem bezpiecznie przejąć.", "Uruchom `codex mcp remove robisie`, potem ponownie `npx robisie init`.");
106
- }
136
+ else
137
+ conflicts.add(inline[1] ?? inline[2] ?? inline[3]);
138
+ }
139
+ if (conflicts.size && !removeInline) {
140
+ const config = codexHome ? join(codexHome, "config.toml") : "config.toml";
141
+ const profile = codexHome ? `CODEX_HOME=${shellQuote(codexHome)} ` : "";
142
+ const copy = codexHome
143
+ ? `Zachowaj oryginał bez nadpisania wcześniejszej kopii: \`cp -p -n -- ${shellQuote(config)} ${shellQuote(freeCopyPath(config))}\`. Sprawdź, że kopia powstała i zawiera oryginalne bajty; dopiero wtedy kontynuuj. `
144
+ : "Zachowaj własną kopię config.toml. ";
145
+ throw new RobisieError(`W ${config} są już serwery ${[...conflicts].join(", ")} zapisane w formie, której nie umiem bezpiecznie przejąć.`, `${copy}Uruchom kolejno ${[...conflicts].map((name) => `\`${profile}codex mcp remove ${name}\``).join(", ")}, potem \`${profile}npx robisie init --agent codex --no-shell\` i \`${profile}npx robisie doctor\`.`);
107
146
  }
108
147
  return kept.join("");
109
148
  }
@@ -116,6 +155,7 @@ function codexHelperTable(name) {
116
155
  // Codex passes only a small default environment to stdio servers; the helpers need these three.
117
156
  `env_vars = [${HELPER_ENV.map((name) => JSON.stringify(name)).join(", ")}]`,
118
157
  "startup_timeout_sec = 60",
158
+ 'default_tools_approval_mode = "approve"',
119
159
  ].join("\n");
120
160
  }
121
161
  /** Every file change `init` makes for one agent. */
@@ -136,8 +176,16 @@ export function planAgent(ctx, id, roles, key) {
136
176
  kind: "block",
137
177
  path: paths.codexConfig,
138
178
  comment: "hash",
139
- body: [`[mcp_servers.${MCP_SERVER_NAME}]\nurl = "${url}"\nbearer_token_env_var = "${KEY_ENV}"`, ...HELPER_SERVERS.map(codexHelperTable)].join("\n\n"),
140
- clean: removeCodexTables,
179
+ body: [
180
+ [
181
+ `[mcp_servers.${MCP_SERVER_NAME}]`,
182
+ `url = "${url}"`,
183
+ `bearer_token_env_var = "${KEY_ENV}"`,
184
+ ...CODEX_TRUSTED_TOOLS.map((name) => `tools.${name}.approval_mode = "approve"`),
185
+ ].join("\n"),
186
+ ...HELPER_SERVERS.map(codexHelperTable),
187
+ ].join("\n\n"),
188
+ clean: (text) => removeCodexTables(text, false, paths.codexHome),
141
189
  },
142
190
  ...roleFiles.map(({ role, path }) => ({ kind: "file", path, content: renderCodexRole(role, roleFiles.find((r) => r.role.kind === "builder")?.path) })),
143
191
  {
@@ -157,7 +205,7 @@ export function planAgent(ctx, id, roles, key) {
157
205
  ];
158
206
  }
159
207
  if (existsSync(paths.opencodeJsonc) && !existsSync(paths.opencodeJson)) {
160
- throw new RobisieError(`OpenCode używa pliku ${paths.opencodeJsonc} (z komentarzami), którego nie zmieniam automatycznie.`, `Dodaj w nim pod "mcp" wpis "robisie": { "type": "remote", "url": "${url}", "headers": { "Authorization": "Bearer {env:${KEY_ENV}}" } } albo uruchom init z --agent bez opencode.`);
208
+ throw new RobisieError(`OpenCode używa pliku ${paths.opencodeJsonc} (z komentarzami), którego nie zmieniam automatycznie.`, "Sam wpis zdalnego MCP w JSONC nie instaluje ról ani pomocników. Zachowaj konfigurację; człowiek przygotowuje wspierany opencode.json bez komentarzy zgodnie z ustawieniami OpenCode albo wybiera innego zainstalowanego dostawcę dla wszystkich trzech ról. Po przygotowaniu JSON ponów init dla OpenCode. Dla zainstalowanego Claude Code uruchom `npx robisie init --agent claude --role ceo=claude --role builder=claude --role quality=claude`. Potem uruchom `npx robisie doctor`. Przewodnik: [sekcja init](node_modules/robisie/docs/README.md#połączenie-agenta).");
161
209
  }
162
210
  return [
163
211
  { kind: "json", path: paths.opencodeJson, keyPath: ["mcp", MCP_SERVER_NAME], value: { type: "remote", url, enabled: true, headers: { Authorization: `Bearer {env:${KEY_ENV}}` } }, private: false },
@@ -168,6 +216,7 @@ export function planAgent(ctx, id, roles, key) {
168
216
  return { kind: "json", path: paths.opencodeJson, keyPath: ["mcp", name], value: { type: "local", command: [command, ...args], enabled: true, environment, timeout: 60_000 }, private: false };
169
217
  }),
170
218
  ...roles.map((role) => ({ kind: "file", path: join(paths.opencodeDir, "agents", role.file), content: renderOpenCodeAgent(role) })),
219
+ { kind: "file", path: join(paths.opencodeDir, "plugins", "robisie-review-guard.js"), content: OPENCODE_REVIEW_GUARD },
171
220
  ];
172
221
  }
173
222
  /** Shell profile line that exports ROBISIE_KEY from the package's key file (roles and Codex/OpenCode read it). */
@@ -176,7 +225,19 @@ export function shellProfile(ctx) {
176
225
  return null;
177
226
  const shell = (ctx.env.SHELL ?? "").split("/").pop();
178
227
  if (shell === "zsh")
179
- return join(ctx.env.ZDOTDIR || ctx.home, ".zshrc");
228
+ return join(configRoot(ctx, "ZDOTDIR", ctx.home), ".zshrc");
229
+ if (shell === "bash" && ctx.platform === "darwin") {
230
+ for (const name of [".bash_profile", ".bash_login", ".profile"]) {
231
+ const path = join(ctx.home, name);
232
+ try {
233
+ accessSync(path, constants.R_OK);
234
+ return path;
235
+ }
236
+ catch {
237
+ // Bash skips profiles it cannot read; keep looking in startup order.
238
+ }
239
+ }
240
+ }
180
241
  if (shell === "bash")
181
242
  return join(ctx.home, ctx.platform === "darwin" ? ".bash_profile" : ".bashrc");
182
243
  return null;
@@ -0,0 +1,21 @@
1
+ type AttemptActive = {
2
+ taskId: string;
3
+ jobId: string;
4
+ attempts: number;
5
+ };
6
+ export declare function classifyStart(input: {
7
+ cardId: string;
8
+ seq: number;
9
+ attempt?: number;
10
+ jobId: unknown;
11
+ attempts: unknown;
12
+ actives: readonly AttemptActive[];
13
+ helperStatus: unknown;
14
+ }): {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ reason: string;
19
+ };
20
+ export declare function nextAttempt(details: Record<string, unknown> | null | undefined): number;
21
+ export {};