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
@@ -0,0 +1,39 @@
1
+ export function classifyStart(input) {
2
+ const expected = input.attempt ?? input.seq; // P1-e: brak attempt → stary seq
3
+ if (!nonempty(input.jobId))
4
+ return { ok: false, reason: "brak jobId" };
5
+ if (!positiveInteger(input.attempts) || input.attempts !== expected)
6
+ return { ok: false, reason: "próba się nie zgadza" };
7
+ const active = input.actives.find(a => a.taskId === input.cardId);
8
+ if (!active)
9
+ return { ok: false, reason: "brak w state.actives" };
10
+ if (active.jobId !== input.jobId || active.attempts !== expected) {
11
+ return { ok: false, reason: "state.actives nie zgadza się z próbą/jobId" };
12
+ }
13
+ const status = typeof input.helperStatus === "string" ? input.helperStatus.trim().toLowerCase() : "";
14
+ if (status !== "queued" && status !== "running" && status !== "completed") {
15
+ return { ok: false, reason: `helper ${status || "brak"}` };
16
+ }
17
+ return { ok: true };
18
+ }
19
+ export function nextAttempt(details) {
20
+ const worker = details?.maszynista;
21
+ if (!record(worker))
22
+ return 1;
23
+ const previous = worker.attempts ?? worker.attempt;
24
+ if (worker.outcome === "MODEL_AT_CAPACITY" && positiveInteger(previous))
25
+ return previous;
26
+ // Karta po blokadzie wrocila do ready reka czlowieka/CR -> nowa seria prob (inaczej 4/3 = blokada po 1 probie).
27
+ if (worker.move === "blocked")
28
+ return 1;
29
+ return positiveInteger(previous) ? previous + 1 : 1;
30
+ }
31
+ function record(value) {
32
+ return value !== null && typeof value === "object" && !Array.isArray(value);
33
+ }
34
+ function positiveInteger(value) {
35
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
36
+ }
37
+ function nonempty(value) {
38
+ return typeof value === "string" && value.trim().length > 0;
39
+ }
@@ -0,0 +1,23 @@
1
+ export type CiJobSteps = {
2
+ name: string;
3
+ diskTrusted?: boolean;
4
+ evidenceMissing?: boolean;
5
+ interruptedTrusted?: "cancelled" | "timed_out";
6
+ steps: Array<{
7
+ name: string;
8
+ conclusion: string | null;
9
+ }>;
10
+ };
11
+ export declare function failedTestFiles(raw: string): string[];
12
+ export declare function normalizeCiLogTail(raw: string, maxLines?: number, maxChars?: number): string;
13
+ export declare function formatCiFailNote(failed: Array<{
14
+ name: string;
15
+ conclusion: string | null;
16
+ }>, jobs: CiJobSteps[], tails: Array<{
17
+ runId: string;
18
+ tail: string;
19
+ }>, maxChars?: number): string;
20
+ export declare const CI_DISK_HOLD_STEP = "Robisie disk HOLD v1";
21
+ export declare const CI_DISK_HOLD_JOBS: string[];
22
+ export declare function hasCiDiskHold(job: CiJobSteps): boolean;
23
+ export declare function classifyCiFailure(failedJobs: CiJobSteps[]): "disk" | "host" | "infra" | "code" | "unknown";
@@ -0,0 +1,90 @@
1
+ export function failedTestFiles(raw) {
2
+ const clean = raw.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
3
+ return [...new Set(Array.from(clean.matchAll(/(?:^|[\t ])FAIL[\t ]+([^\s]+\.test\.(?:ts|tsx|mts|mjs|js))[\t ]+(?:>|\[)/gm), match => match[1]))];
4
+ }
5
+ export function normalizeCiLogTail(raw, maxLines = 40, maxChars = 1000) {
6
+ const lines = raw.split(/\r?\n/).map((line) => line
7
+ .replace(/\x1b\[[0-9;]*[A-Za-z]/g, "")
8
+ .replace(/^[^\t]*\t[^\t]*\t/, "")
9
+ .replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s?/, "")
10
+ .trimEnd()).filter((line) => line.trim().length > 0);
11
+ const tail = lines.slice(-maxLines).join("\n");
12
+ return tail.length > maxChars ? `…${tail.slice(-(maxChars - 1))}` : tail;
13
+ }
14
+ export function formatCiFailNote(failed, jobs, tails, maxChars = 1400) {
15
+ const header = `CI: ${failed.map((run) => `${run.name}: ${run.conclusion}`).join(", ")}`;
16
+ const steps = jobs.flatMap((job) => {
17
+ const failedSteps = job.steps.filter((step) => step.conclusion === "failure");
18
+ return failedSteps.length > 0 ? failedSteps.map((step) => `${job.name} › ${step.name}`) : [job.name];
19
+ });
20
+ const marker = "…(ucięto)";
21
+ if (header.length >= maxChars)
22
+ return header.slice(0, maxChars);
23
+ let note = header;
24
+ const nonEmptyTails = tails.filter(({ tail }) => tail.length > 0);
25
+ const firstTail = nonEmptyTails[0];
26
+ const firstBlock = firstTail ? `\n--- ogon logu run ${firstTail.runId} ---\n${firstTail.tail}` : "";
27
+ const tailReserve = Math.min(firstBlock.length, 500) + (nonEmptyTails.length > 1 ? marker.length : 0);
28
+ if (steps.length > 0) {
29
+ const stepLine = `\nPadłe: ${steps.join(", ")}`;
30
+ const stepBudget = maxChars - note.length - tailReserve;
31
+ const stepMarker = firstTail ? "…(ucięto kroki)" : marker;
32
+ if (stepLine.length <= stepBudget)
33
+ note += stepLine;
34
+ else if (stepBudget >= stepMarker.length)
35
+ note += `${stepLine.slice(0, stepBudget - stepMarker.length)}${stepMarker}`;
36
+ }
37
+ for (const [index, { runId, tail }] of nonEmptyTails.entries()) {
38
+ const blockHeader = `\n--- ogon logu run ${runId} ---\n`;
39
+ const block = `${blockHeader}${tail}`;
40
+ if (note.length + block.length > maxChars) {
41
+ if (index === 0) {
42
+ const omittedMarker = nonEmptyTails.length > 1 ? marker : "";
43
+ const tailRoom = maxChars - note.length - blockHeader.length - omittedMarker.length;
44
+ if (tailRoom > 1) {
45
+ note += `${blockHeader}…${tail.slice(-(tailRoom - 1))}${omittedMarker}`;
46
+ break;
47
+ }
48
+ }
49
+ if (note.length + marker.length <= maxChars)
50
+ note += marker;
51
+ break;
52
+ }
53
+ note += block;
54
+ }
55
+ return note;
56
+ }
57
+ export const CI_DISK_HOLD_STEP = "Robisie disk HOLD v1";
58
+ export const CI_DISK_HOLD_JOBS = ["Verify (lint + build:check)", "Secret scan (gitleaks)", "check-agent-writes (ROB brief narrative discipline)", "check-postmortem-required (ROB_099 — bugfix postmortem gate)", "Deploy (wrangler → Cloudflare Workers)"];
59
+ export function hasCiDiskHold(job) {
60
+ return job.diskTrusted === true && CI_DISK_HOLD_JOBS.includes(job.name)
61
+ && job.steps.some(step => step.name === CI_DISK_HOLD_STEP && step.conclusion === "failure");
62
+ }
63
+ export function classifyCiFailure(failedJobs) {
64
+ if (failedJobs.length === 0)
65
+ return "unknown";
66
+ const hostStep = /^(Set up runner|Complete runner|Set up job|Complete job)$/;
67
+ let sawFailedStep = false;
68
+ let sawNotStartedJob = false;
69
+ let sawDisk = false;
70
+ const diskPresent = failedJobs.some(hasCiDiskHold);
71
+ for (const job of failedJobs) {
72
+ if (job.evidenceMissing && job.interruptedTrusted && !diskPresent)
73
+ continue;
74
+ if (job.steps.length === 0 && !job.evidenceMissing)
75
+ sawNotStartedJob = true;
76
+ const failed = job.steps.filter((st) => st.conclusion === "failure");
77
+ if (failed.length === 0)
78
+ continue;
79
+ sawFailedStep = true;
80
+ const disk = hasCiDiskHold(job);
81
+ if (!failed.every((st) => hostStep.test(st.name) || disk && st.name === CI_DISK_HOLD_STEP))
82
+ return "code";
83
+ sawDisk ||= disk;
84
+ }
85
+ if (sawDisk)
86
+ return failedJobs.some(job => !job.interruptedTrusted && (job.evidenceMissing || job.steps.length > 0 && !job.steps.some(step => step.conclusion === "failure"))) ? "unknown" : "disk";
87
+ if (sawNotStartedJob)
88
+ return "infra";
89
+ return sawFailedStep ? "host" : "unknown";
90
+ }
@@ -0,0 +1,7 @@
1
+ export declare const HEADLESS_SYSTEM_PROMPT = "Jeste\u015B bezstanowym modu\u0142em fabryki Robisie wywo\u0142ywanym przez skrypt. Nie masz narz\u0119dzi ani dost\u0119pu do repozytorium, plik\u00F3w, tablicy ani historii \u2014 opierasz si\u0119 wy\u0142\u0105cznie na tre\u015Bci tej wiadomo\u015Bci. Odpowiadasz dok\u0142adnie w formacie, kt\u00F3rego \u017C\u0105da wiadomo\u015B\u0107.";
2
+ export declare const HEADLESS_CLAUDE_ENV: {
3
+ readonly CLAUDE_CODE_DISABLE_ATTACHMENTS: "1";
4
+ };
5
+ export declare function headlessClaudeArgs(model: string): string[];
6
+ /** Tokeny wejścia z koperty `--output-format json`; null, gdy koperta nie ma liczbowego `usage`. */
7
+ export declare function envelopeInputTokens(envelope: unknown): number | null;
@@ -0,0 +1,28 @@
1
+ // Jedno źródło argumentów `claude -p` dla automatu (selektor, agent zasiewu).
2
+ // Po co (Reksio-364, 2026-09-22, zmierzone): `--allowedTools ""` NIE odbiera narzędzi, a
3
+ // `--exclude-dynamic-system-prompt-sections` PRZENOSI `git status` do pierwszej wiadomości zamiast go
4
+ // usuwać; z cwd = repo model dostawał też personę Reksia (CLAUDE.local.md) i hooki SessionStart —
5
+ // 44 562 tokeny wejścia i 12 narzędzi. Z listą poniżej: 0 narzędzi, 0 instrukcji, 0 hooków, 1 234 tokeny.
6
+ // `--bare` odpada: wymaga ANTHROPIC_API_KEY, a fabryka działa na OAuth (plan Max).
7
+ export const HEADLESS_SYSTEM_PROMPT = "Jesteś bezstanowym modułem fabryki Robisie wywoływanym przez skrypt. Nie masz narzędzi ani dostępu do repozytorium, plików, tablicy ani historii — opierasz się wyłącznie na treści tej wiadomości. Odpowiadasz dokładnie w formacie, którego żąda wiadomość.";
8
+ // --tools "" does not stop CLI-side @file expansion before the model sees the prompt.
9
+ export const HEADLESS_CLAUDE_ENV = { CLAUDE_CODE_DISABLE_ATTACHMENTS: "1" };
10
+ export function headlessClaudeArgs(model) {
11
+ return [
12
+ "-p", "--model", model, "--disable-slash-commands",
13
+ "--tools", "", "--setting-sources", "", "--strict-mcp-config",
14
+ "--system-prompt", HEADLESS_SYSTEM_PROMPT, "--output-format", "json",
15
+ ];
16
+ }
17
+ const TOKEN_FIELDS = ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"];
18
+ /** Tokeny wejścia z koperty `--output-format json`; null, gdy koperta nie ma liczbowego `usage`. */
19
+ export function envelopeInputTokens(envelope) {
20
+ if (typeof envelope !== "object" || envelope === null)
21
+ return null;
22
+ const usage = envelope.usage;
23
+ if (typeof usage !== "object" || usage === null)
24
+ return null;
25
+ const values = TOKEN_FIELDS.map((field) => usage[field])
26
+ .filter((value) => typeof value === "number");
27
+ return values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
28
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { type AgentId } from "./agents.js";
3
+ import { type Ctx } from "./context.js";
4
+ import { type RoleProviders } from "./manifest.js";
5
+ export interface ParsedArgs {
6
+ command: string;
7
+ help: boolean;
8
+ /** Positional argument (`robisie mcp <name>`). */
9
+ target: string | null;
10
+ agents: AgentId[] | null;
11
+ roles: Partial<RoleProviders>;
12
+ shell: boolean;
13
+ keyStdin: boolean;
14
+ engineConfig: string | null;
15
+ }
16
+ export declare function parseArgs(argv: readonly string[]): ParsedArgs;
17
+ export declare function run(argv: readonly string[], ctx: Ctx): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { AGENT_IDS, isAgentId } from "./agents.js";
5
- import { doctor, init, uninstall } from "./commands.js";
5
+ import { doctor, init, recoveryStep, uninstall } from "./commands.js";
6
6
  import { HELPER_SERVERS, PACKAGE_VERSION } from "./constants.js";
7
7
  import { RobisieError, errorMessage, nodeCtx } from "./context.js";
8
- import { isHelperServer, runHelper } from "./helpers.js";
8
+ import { helperPath, isHelperServer, runHelper } from "./helpers.js";
9
9
  import { ROLE_IDS, ROLE_STEP } from "./manifest.js";
10
+ import { doctorEngineConfig } from "./role-registry.js";
10
11
  const HELP = `robisie ${PACKAGE_VERSION} — łączy Twojego agenta z tablicą robisie
11
12
 
12
13
  npx robisie init klucz rob_… (z ${"ROBISIE_KEY"} albo z pytania) + konfiguracja MCP + role klienta
@@ -15,6 +16,10 @@ const HELP = `robisie ${PACKAGE_VERSION} — łączy Twojego agenta z tablicą r
15
16
  npx robisie mcp <${HELPER_SERVERS.join("|")}>
16
17
  pomocnik ról (serwer MCP stdio) — uruchamia go agent, nie Ty
17
18
 
19
+ Opcje doctor:
20
+ --engine-config <path> lokalnie i offline sprawdza K2 i rejestr wykonania czterech ról K7
21
+ --engine-config=<path> ta sama składnia; nie sprawdza uruchomienia dostawców
22
+
18
23
  Opcje init:
19
24
  --agent <${AGENT_IDS.join("|")}> wybrany dostawca (można powtórzyć albo podać po przecinku); razem z dostawcami mapy, bez --agent również wykryci
20
25
  --role <rola=dostawca> wybór ceo, builder, quality → claude, codex, opencode (można powtórzyć)
@@ -29,13 +34,30 @@ export function parseArgs(argv) {
29
34
  const roles = {};
30
35
  let shell = true;
31
36
  let keyStdin = false;
37
+ let engineConfig = null;
32
38
  let target = null;
33
39
  // Help wins over everything else, so `init --help` never starts an install.
34
- if (args.includes("--help") || args.includes("-h"))
35
- return { command, help: true, target, agents: null, roles, shell, keyStdin };
40
+ if (argv.includes("--help") || argv.includes("-h"))
41
+ return { command, help: true, target, agents: null, roles, shell, keyStdin, engineConfig };
36
42
  while (args.length > 0) {
37
43
  const arg = args.shift();
38
- if (arg === "--no-shell")
44
+ if (command !== "init" && (arg === "--no-shell" || arg === "--key-stdin" ||
45
+ arg === "--agent" || arg.startsWith("--agent=") ||
46
+ arg === "--role" || arg.startsWith("--role="))) {
47
+ throw new RobisieError(`Opcja „${arg.split("=")[0]}” jest dostępna tylko dla polecenia init.`, "Zobacz `npx robisie help`.");
48
+ }
49
+ if (arg === "--engine-config" || arg.startsWith("--engine-config=")) {
50
+ const step = "Użyj `npx robisie doctor --engine-config <path>` z jedną niepustą ścieżką pliku.";
51
+ if (command !== "doctor")
52
+ throw new RobisieError("Opcja --engine-config jest dostępna tylko dla polecenia doctor.", step);
53
+ if (engineConfig !== null)
54
+ throw new RobisieError("Opcję --engine-config można podać tylko raz.", step);
55
+ const value = arg === "--engine-config" ? args.shift() ?? "" : arg.slice("--engine-config=".length);
56
+ if (!value.trim() || value.startsWith("-"))
57
+ throw new RobisieError("Opcja --engine-config wymaga ścieżki pliku.", step);
58
+ engineConfig = value;
59
+ }
60
+ else if (arg === "--no-shell")
39
61
  shell = false;
40
62
  else if (arg === "--key-stdin")
41
63
  keyStdin = true;
@@ -45,9 +67,9 @@ export function parseArgs(argv) {
45
67
  throw new RobisieError("Opcja --role wymaga przypisania rola=dostawca.", ROLE_STEP);
46
68
  const [role, provider, extra] = value.split("=");
47
69
  if (!ROLE_IDS.some((id) => id === role))
48
- throw new RobisieError(`Nieznana rola „${role || "(pusta rola --role)"}”.`, ROLE_STEP);
70
+ throw new RobisieError("Nieznana rola (--role).", ROLE_STEP);
49
71
  if (extra !== undefined || !provider || !isAgentId(provider))
50
- throw new RobisieError(`Niepoprawny dostawca „${provider ?? ""}” dla roli „${role}” (--role ${value}).`, ROLE_STEP);
72
+ throw new RobisieError("Niepoprawny dostawca lub przypisanie (--role).", ROLE_STEP);
51
73
  const id = role;
52
74
  if (roles[id] && roles[id] !== provider)
53
75
  throw new RobisieError(`Sprzeczne przypisania roli „${role}”: ${roles[id]} i ${provider}.`, ROLE_STEP);
@@ -58,11 +80,11 @@ export function parseArgs(argv) {
58
80
  const ids = value.split(",").map((v) => v.trim());
59
81
  // An empty value must not silently fall back to "every detected agent".
60
82
  if (ids.some((id) => id === "") || value.startsWith("--")) {
61
- throw new RobisieError("Opcja --agent wymaga nazwy agenta.", `Podaj np. \`--agent codex\` (dostępne: ${AGENT_IDS.join(", ")}) albo pomiń --agent, żeby użyć wykrytych.`);
83
+ throw new RobisieError("Opcja --agent wymaga nazwy agenta.", ROLE_STEP);
62
84
  }
63
85
  for (const id of ids) {
64
86
  if (!isAgentId(id))
65
- throw new RobisieError(`Nieznany agent „${id}”.`, `Użyj jednego z: ${AGENT_IDS.join(", ")}.`);
87
+ throw new RobisieError("Nieznany agent (--agent).", ROLE_STEP);
66
88
  if (!agents.includes(id))
67
89
  agents.push(id);
68
90
  }
@@ -70,9 +92,9 @@ export function parseArgs(argv) {
70
92
  else if (command === "mcp" && target === null && !arg.startsWith("-"))
71
93
  target = arg;
72
94
  else
73
- throw new RobisieError(`Nieznana opcja „${arg}”.`, "Zobacz `npx robisie help`.");
95
+ throw new RobisieError("Nieznana opcja argumentów CLI.", "Zobacz `npx robisie help`.");
74
96
  }
75
- return { command, help: false, target, agents: agents.length > 0 ? agents : null, roles, shell, keyStdin };
97
+ return { command, help: false, target, agents: agents.length > 0 ? agents : null, roles, shell, keyStdin, engineConfig };
76
98
  }
77
99
  export async function run(argv, ctx) {
78
100
  try {
@@ -86,12 +108,30 @@ export async function run(argv, ctx) {
86
108
  await init(ctx, { agents: args.agents, roles: args.roles, shell: args.shell, keyStdin: args.keyStdin });
87
109
  return 0;
88
110
  case "doctor":
111
+ if (args.engineConfig !== null)
112
+ return doctorEngineConfig(ctx, args.engineConfig);
113
+ if (ctx.env.ROBISIE_KEY) {
114
+ try {
115
+ const helper = await import(pathToFileURL(helperPath("client-launch-context")).href);
116
+ if (!helper || typeof helper !== "object" || !("resolveProjectContext" in helper)
117
+ || typeof helper.resolveProjectContext !== "function")
118
+ throw new Error("Invalid project-context export");
119
+ const result = helper.resolveProjectContext(ctx.env, ctx.cwd);
120
+ if (!result || typeof result !== "object" || !("projectContext" in result)
121
+ || typeof result.projectContext !== "string" || !("plannerAccess" in result)
122
+ || result.plannerAccess !== "configured")
123
+ throw new Error("Invalid project-context result");
124
+ }
125
+ catch {
126
+ throw new RobisieError("Nie mogę potwierdzić kontekstu projektu używanego przez pomocnik klienta.", "W katalogu projektu wykonaj `unset ROBISIE_PROJECT_CONTEXT`, potem `npx robisie doctor`. Albo ustaw istniejący absolutny rzeczywisty katalog: `export ROBISIE_PROJECT_CONTEXT='/absolutna/sciezka/do/projektu'` (zastąp ścieżkę katalogiem projektu), potem `npx robisie doctor`.");
127
+ }
128
+ }
89
129
  return await doctor(ctx);
90
130
  case "uninstall":
91
131
  return uninstall(ctx);
92
132
  case "mcp":
93
133
  if (!args.target || !isHelperServer(args.target)) {
94
- throw new RobisieError(`Nieznany pomocnik „${args.target ?? ""}”.`, `Użyj jednego z: ${HELPER_SERVERS.join(", ")}.`);
134
+ throw new RobisieError("Nieznany pomocnik (mcp).", `Użyj jednego z: ${HELPER_SERVERS.join(", ")}.`);
95
135
  }
96
136
  return await runHelper(args.target);
97
137
  case "--version":
@@ -104,13 +144,13 @@ export async function run(argv, ctx) {
104
144
  ctx.out(HELP);
105
145
  return 0;
106
146
  default:
107
- throw new RobisieError(`Nieznane polecenie „${args.command}”.`, "Zobacz `npx robisie help`.");
147
+ throw new RobisieError("Nieznane polecenie CLI.", "Zobacz `npx robisie help`.");
108
148
  }
109
149
  }
110
150
  catch (error) {
111
151
  if (error instanceof RobisieError) {
112
152
  ctx.err(`✗ ${error.message}`);
113
- ctx.err(`Następny krok: ${error.nextStep}`);
153
+ ctx.err(`Następny krok: ${recoveryStep(ctx, error.nextStep)}`);
114
154
  }
115
155
  else {
116
156
  ctx.err(`✗ Coś poszło nie tak: ${errorMessage(error)}`);
@@ -0,0 +1,18 @@
1
+ import { type AgentId } from "./agents.js";
2
+ import { type Ctx } from "./context.js";
3
+ import { type RoleProviders } from "./manifest.js";
4
+ export interface InitOptions {
5
+ agents: AgentId[] | null;
6
+ roles?: Partial<RoleProviders>;
7
+ shell: boolean;
8
+ /** Read the key from piped stdin — only on request, so an open non-TTY stdin can never hang init. */
9
+ keyStdin: boolean;
10
+ rolesDir?: string;
11
+ }
12
+ /** Advice chooses an executable CLI, not merely a leftover configuration directory. */
13
+ export declare function initRecoveryStep(ctx: Ctx, preferred?: AgentId): string;
14
+ export declare function recoveryStep(ctx: Ctx, step: string): string;
15
+ export declare function init(ctx: Ctx, options: InitOptions): Promise<void>;
16
+ /** Returns the process exit code: 0 = all good, 1 = something needs one action. */
17
+ export declare function doctor(ctx: Ctx): Promise<number>;
18
+ export declare function uninstall(ctx: Ctx): number;