@kestrel-agents/ruhroh 0.5.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/assets/ruhroh-badge.png +0 -0
  4. package/assets/ruhroh-logo.png +0 -0
  5. package/dist/adapters.d.ts +97 -0
  6. package/dist/adapters.d.ts.map +1 -0
  7. package/dist/adapters.js +21 -0
  8. package/dist/adapters.js.map +1 -0
  9. package/dist/builtin-scenarios.d.ts +8 -0
  10. package/dist/builtin-scenarios.d.ts.map +1 -0
  11. package/dist/builtin-scenarios.js +22 -0
  12. package/dist/builtin-scenarios.js.map +1 -0
  13. package/dist/cli.d.ts +30 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +313 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/env.d.ts +6 -0
  18. package/dist/env.d.ts.map +1 -0
  19. package/dist/env.js +66 -0
  20. package/dist/env.js.map +1 -0
  21. package/dist/generate.d.ts +32 -0
  22. package/dist/generate.d.ts.map +1 -0
  23. package/dist/generate.js +231 -0
  24. package/dist/generate.js.map +1 -0
  25. package/dist/harbor.d.ts +28 -0
  26. package/dist/harbor.d.ts.map +1 -0
  27. package/dist/harbor.js +47 -0
  28. package/dist/harbor.js.map +1 -0
  29. package/dist/index.d.ts +8 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +8 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/results.d.ts +66 -0
  34. package/dist/results.d.ts.map +1 -0
  35. package/dist/results.js +31 -0
  36. package/dist/results.js.map +1 -0
  37. package/dist/scenarios.d.ts +61 -0
  38. package/dist/scenarios.d.ts.map +1 -0
  39. package/dist/scenarios.js +69 -0
  40. package/dist/scenarios.js.map +1 -0
  41. package/package.json +66 -0
  42. package/python/ruhroh/__init__.py +5 -0
  43. package/python/ruhroh/harbor_agent.py +345 -0
  44. package/python/ruhroh/loop_controller.py +783 -0
  45. package/python/ruhroh/setup.sh +12 -0
  46. package/scenarios/grocery-budget-planner/instruction.md +1 -0
  47. package/scenarios/grocery-budget-planner/scenario.json +44 -0
  48. package/scenarios/nextjs-task-board/instruction.md +1 -0
  49. package/scenarios/nextjs-task-board/scenario.json +45 -0
  50. package/scenarios/shift-coverage-planner/assets/prompt-assets/shift-coverage/coverage-rules.json +29 -0
  51. package/scenarios/shift-coverage-planner/assets/prompt-assets/shift-coverage/employees.csv +8 -0
  52. package/scenarios/shift-coverage-planner/assets/prompt-assets/shift-coverage/existing-schedule.csv +9 -0
  53. package/scenarios/shift-coverage-planner/assets/prompt-assets/shift-coverage/shift-requirements.csv +8 -0
  54. package/scenarios/shift-coverage-planner/assets/prompt-assets/shift-coverage/time-off-requests.csv +5 -0
  55. package/scenarios/shift-coverage-planner/instruction.md +1 -0
  56. package/scenarios/shift-coverage-planner/scenario.json +47 -0
  57. package/scenarios/simple-newsletter/instruction.md +1 -0
  58. package/scenarios/simple-newsletter/scenario.json +40 -0
  59. package/scenarios/vite-csv-reconciliation/assets/prompt-assets/csv-reconciliation-people/source-a.csv +9 -0
  60. package/scenarios/vite-csv-reconciliation/assets/prompt-assets/csv-reconciliation-people/source-b.csv +9 -0
  61. package/scenarios/vite-csv-reconciliation/instruction.md +1 -0
  62. package/scenarios/vite-csv-reconciliation/scenario.json +48 -0
  63. package/scenarios/vite-sprint-planner/instruction.md +1 -0
  64. package/scenarios/vite-sprint-planner/scenario.json +45 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kestrel-agents
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ <p align="center">
2
+ <img src="assets/ruhroh-logo.png" alt="Ruhroh logo" width="220">
3
+ </p>
4
+
5
+ # <img src="assets/ruhroh-badge.png" alt="" width="28" align="absmiddle"> Ruhroh
6
+
7
+ Ruhroh is the **Real-User Harness for Repair-Oriented Harbor**.
8
+
9
+ Ruhroh runs real-user task scenarios against coding agents through adapters,
10
+ preserves the full implementation journey, and runs a terminal evaluator over
11
+ the final delivered workspace.
12
+
13
+ Ruhroh is agent-agnostic. Kestrel is one reference run-agent adapter, not the
14
+ benchmark itself. Harbor is the execution substrate.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pnpm add -D @kestrel-agents/ruhroh
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ Create scenarios under `ruhroh/scenarios/<id>/`, or use the bundled scenarios
25
+ under `node_modules/@kestrel-agents/ruhroh/scenarios`, then run:
26
+
27
+ ```bash
28
+ pnpm ruhroh --list
29
+ pnpm ruhroh --scenario simple-newsletter --generate-only
30
+ pnpm ruhroh --scenario simple-newsletter --adapter ./path/to/agent-adapter --dry-run
31
+ ```
32
+
33
+ In this repo, the same package CLI is available with:
34
+
35
+ ```bash
36
+ pnpm ruhroh --scenario-dir examples/scenarios --list
37
+ pnpm ruhroh --scenario-dir examples/scenarios --scenario simple-newsletter --generate-only
38
+ ```
39
+
40
+ This package currently contains the portable TypeScript surfaces:
41
+
42
+ - scenario schema and validation;
43
+ - run-agent adapter interfaces and capability compatibility helpers;
44
+ - eval and final result types;
45
+ - verdict mapping helpers;
46
+ - env forwarding and redaction helpers;
47
+ - Harbor command construction helpers;
48
+ - JSON scenario discovery and Harbor task generation helpers.
49
+
50
+ ## Scenario Generation
51
+
52
+ Ruhroh can load JSON scenarios from:
53
+
54
+ ```text
55
+ ruhroh/scenarios/<id>/
56
+ scenario.json
57
+ instruction.md
58
+ assets/
59
+ ```
60
+
61
+ and generate local Harbor task directories under:
62
+
63
+ ```text
64
+ .generated/ruhroh/harbor/tasks/<scenario-id>/
65
+ ```
66
+
67
+ The generated Harbor verifier is app-agnostic. It only validates that the
68
+ structured Ruhroh result exists and maps to a passing score/reward; it does not
69
+ perform required-file, route, command, or source-text checks.
70
+
71
+ The public API exports:
72
+
73
+ - `discoverRuhrohScenarios()`
74
+ - `loadRuhrohScenario()`
75
+ - `generateHarborTask()`
76
+ - `generateHarborDataset()`
77
+
78
+ The package CLI exposes this through:
79
+
80
+ ```bash
81
+ pnpm ruhroh --scenario-dir ruhroh/scenarios --generate-only
82
+ pnpm ruhroh --scenario-dir ruhroh/scenarios --dry-run
83
+ ```
84
+
85
+ This package ships the reusable scenario source under `scenarios/` and the
86
+ package-owned Python Harbor runtime under `python/ruhroh`. Run-agents are wired
87
+ into this runtime as command adapters through
88
+ `RUHROH_RUN_AGENT_COMMAND`; terminal evaluation can be supplied through
89
+ `RUHROH_EVAL_COMMAND` or fixture eval variables.
90
+
91
+ Kestrel is a consumer adapter, not a Ruhroh package dependency. The Harbor
92
+ harness is package-owned for generated Ruhroh tasks.
93
+
94
+ ## Docs
95
+
96
+ - Architecture: `docs/architecture.md`
97
+ - Scenario format: `docs/scenario-format.md`
98
+ - Adapter protocol: `docs/adapter-protocol.md`
99
+ - Custom-shell adapter: `docs/custom-shell.md`
100
+ - Harbor: `docs/harbor.md`
101
+ - Eval-agent: `docs/eval-agent.md`
102
+ - Artifacts: `docs/artifacts.md`
103
+ - CI: `docs/ci.md`
104
+ - Security: `docs/security.md`
105
+ - Limitations: `docs/limitations.md`
106
+ - Public repo layout: `docs/public-repo-layout.md`
107
+
108
+ ## Security
109
+
110
+ Scenario prompts and assets are untrusted input. Run-agents should mutate only
111
+ benchmark workspaces. Eval-agent inspection should happen against a copied
112
+ workspace. Secrets must pass through allowlisted environment variables, and
113
+ dry-run output must never print secret values. Generated Harbor verifiers do
114
+ not perform app-goal checks.
Binary file
Binary file
@@ -0,0 +1,97 @@
1
+ export type RuhrohContinuityLevel = "native_session" | "workspace_plus_transcript" | "workspace_only";
2
+ export type RuhrohCompletionConfidence = "explicit" | "adapter_inferred";
3
+ export type RuhrohCompletionDoneReason = "goal_satisfied";
4
+ export type RuhrohCompletionNotDoneReason = "agent_requested_continue" | "missing_completion_signal" | "context_checkpoint" | "partial_progress";
5
+ export type RuhrohCompletionTerminalFailureReason = "cannot_satisfy" | "policy_blocked" | "out_of_scope" | "runtime_failure" | "infra_failure";
6
+ export interface RuhrohEvidenceRef {
7
+ kind: string;
8
+ ref: string;
9
+ summary: string;
10
+ }
11
+ export type RuhrohCompletionStatus = {
12
+ state: "done";
13
+ reason: RuhrohCompletionDoneReason;
14
+ confidence: RuhrohCompletionConfidence;
15
+ evidenceRefs: RuhrohEvidenceRef[];
16
+ } | {
17
+ state: "not_done";
18
+ reason: RuhrohCompletionNotDoneReason;
19
+ evidenceRefs: RuhrohEvidenceRef[];
20
+ } | {
21
+ state: "terminal_failure";
22
+ reason: RuhrohCompletionTerminalFailureReason;
23
+ evidenceRefs: RuhrohEvidenceRef[];
24
+ };
25
+ export interface PrepareRunAgentInput {
26
+ scenarioId: string;
27
+ workspacePath: string;
28
+ runRootPath: string;
29
+ }
30
+ export interface PrepareRunAgentResult {
31
+ artifactPaths: Record<string, string>;
32
+ }
33
+ export interface StartSessionInput {
34
+ scenarioId: string;
35
+ workspacePath: string;
36
+ }
37
+ export interface StartSessionResult {
38
+ sessionHandle: string;
39
+ artifactPaths: Record<string, string>;
40
+ }
41
+ export interface RunTurnInput {
42
+ iteration: number;
43
+ message: string;
44
+ workspacePath: string;
45
+ }
46
+ export interface RunTurnResult {
47
+ iteration: number;
48
+ status: "completed" | "failed" | "timeout";
49
+ failureKind?: string | undefined;
50
+ runId?: string | undefined;
51
+ threadId?: string | undefined;
52
+ sessionHandle: string;
53
+ transcriptPath?: string | undefined;
54
+ eventLogPath?: string | undefined;
55
+ artifactPaths: Record<string, string>;
56
+ raw: unknown;
57
+ }
58
+ export interface DetectCompletionInput {
59
+ turn: RunTurnResult;
60
+ }
61
+ export interface CollectArtifactsInput {
62
+ sessionHandle: string;
63
+ }
64
+ export interface RunAgentArtifactManifest {
65
+ adapterId: string;
66
+ continuityLevel: RuhrohContinuityLevel;
67
+ sessionHandle: string;
68
+ runIds: string[];
69
+ transcriptPaths: string[];
70
+ eventLogPaths: string[];
71
+ artifactPaths: Record<string, string>;
72
+ }
73
+ export interface CleanupInput {
74
+ sessionHandle: string;
75
+ }
76
+ export interface RuhrohRunAgentAdapter {
77
+ readonly id: string;
78
+ readonly continuityLevel: RuhrohContinuityLevel;
79
+ prepare(input: PrepareRunAgentInput): Promise<PrepareRunAgentResult>;
80
+ startSession(input: StartSessionInput): Promise<StartSessionResult>;
81
+ runTurn(input: RunTurnInput): Promise<RunTurnResult>;
82
+ detectCompletion(input: DetectCompletionInput): Promise<RuhrohCompletionStatus>;
83
+ collectArtifacts(input: CollectArtifactsInput): Promise<RunAgentArtifactManifest>;
84
+ cleanup?(input: CleanupInput): Promise<void>;
85
+ }
86
+ export interface RuhrohRunAgentAdapterCapabilities {
87
+ adapter: string;
88
+ continuity: RuhrohContinuityLevel;
89
+ tools: string[];
90
+ network: boolean;
91
+ }
92
+ export declare function adapterSatisfiesRequirements(adapter: RuhrohRunAgentAdapterCapabilities, requirements: {
93
+ continuity: RuhrohContinuityLevel;
94
+ tools: string[];
95
+ network: boolean;
96
+ }): string[];
97
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ const CONTINUITY_RANK = {
2
+ workspace_only: 1,
3
+ workspace_plus_transcript: 2,
4
+ native_session: 3,
5
+ };
6
+ export function adapterSatisfiesRequirements(adapter, requirements) {
7
+ const errors = [];
8
+ if (CONTINUITY_RANK[adapter.continuity] < CONTINUITY_RANK[requirements.continuity]) {
9
+ errors.push(`adapter ${adapter.adapter} does not satisfy required continuity ${requirements.continuity}`);
10
+ }
11
+ for (const tool of requirements.tools) {
12
+ if (!adapter.tools.includes(tool)) {
13
+ errors.push(`adapter ${adapter.adapter} does not provide required tool ${tool}`);
14
+ }
15
+ }
16
+ if (requirements.network && !adapter.network) {
17
+ errors.push(`adapter ${adapter.adapter} does not provide required network access`);
18
+ }
19
+ return errors;
20
+ }
21
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAuHA,MAAM,eAAe,GAA0C;IAC7D,cAAc,EAAE,CAAC;IACjB,yBAAyB,EAAE,CAAC;IAC5B,cAAc,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,UAAU,4BAA4B,CAC1C,OAA0C,EAC1C,YAIC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,eAAe,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,eAAe,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;QACnF,MAAM,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,OAAO,yCAAyC,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,OAAO,mCAAmC,IAAI,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IACD,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,OAAO,2CAA2C,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type LoadedRuhrohScenario } from "./generate.js";
2
+ import { type RuhrohScenario, type RuhrohScenarioTier } from "./scenarios.js";
3
+ export declare function resolveRuhrohPackageRootFromModule(importMetaUrl?: string): string;
4
+ export declare function resolveRuhrohBuiltinScenarioDir(packageRoot?: string): string;
5
+ export declare function loadBuiltinRuhrohScenarios(scenarioDir?: string): LoadedRuhrohScenario[];
6
+ export declare function getBuiltinRuhrohScenarioById(id: string, scenarioDir?: string): RuhrohScenario | undefined;
7
+ export declare function getBuiltinRuhrohScenariosByTier(tier: RuhrohScenarioTier, scenarioDir?: string): RuhrohScenario[];
8
+ //# sourceMappingURL=builtin-scenarios.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-scenarios.d.ts","sourceRoot":"","sources":["../src/builtin-scenarios.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,wBAAgB,kCAAkC,CAAC,aAAa,GAAE,MAAwB,GAAG,MAAM,CAIlG;AAED,wBAAgB,+BAA+B,CAAC,WAAW,GAAE,MAA6C,GAAG,MAAM,CAElH;AAED,wBAAgB,0BAA0B,CACxC,WAAW,GAAE,MAA0C,GACtD,oBAAoB,EAAE,CAExB;AAED,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,MAAM,EACV,WAAW,GAAE,MAA0C,GACtD,cAAc,GAAG,SAAS,CAE5B;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,kBAAkB,EACxB,WAAW,GAAE,MAA0C,GACtD,cAAc,EAAE,CAElB"}
@@ -0,0 +1,22 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { discoverRuhrohScenarios, loadRuhrohScenario } from "./generate.js";
4
+ import { getRuhrohScenarioById, getRuhrohScenariosByTier, } from "./scenarios.js";
5
+ export function resolveRuhrohPackageRootFromModule(importMetaUrl = import.meta.url) {
6
+ const moduleDir = path.dirname(fileURLToPath(importMetaUrl));
7
+ const basename = path.basename(moduleDir);
8
+ return basename === "dist" || basename === "src" ? path.dirname(moduleDir) : moduleDir;
9
+ }
10
+ export function resolveRuhrohBuiltinScenarioDir(packageRoot = resolveRuhrohPackageRootFromModule()) {
11
+ return path.join(packageRoot, "scenarios");
12
+ }
13
+ export function loadBuiltinRuhrohScenarios(scenarioDir = resolveRuhrohBuiltinScenarioDir()) {
14
+ return discoverRuhrohScenarios(scenarioDir).map((source) => loadRuhrohScenario(source));
15
+ }
16
+ export function getBuiltinRuhrohScenarioById(id, scenarioDir = resolveRuhrohBuiltinScenarioDir()) {
17
+ return getRuhrohScenarioById(loadBuiltinRuhrohScenarios(scenarioDir).map((loaded) => loaded.scenario), id);
18
+ }
19
+ export function getBuiltinRuhrohScenariosByTier(tier, scenarioDir = resolveRuhrohBuiltinScenarioDir()) {
20
+ return getRuhrohScenariosByTier(loadBuiltinRuhrohScenarios(scenarioDir).map((loaded) => loaded.scenario), tier);
21
+ }
22
+ //# sourceMappingURL=builtin-scenarios.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-scenarios.js","sourceRoot":"","sources":["../src/builtin-scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAA6B,MAAM,eAAe,CAAC;AACvG,OAAO,EACL,qBAAqB,EACrB,wBAAwB,GAGzB,MAAM,gBAAgB,CAAC;AAExB,MAAM,UAAU,kCAAkC,CAAC,gBAAwB,MAAM,CAAC,IAAI,CAAC,GAAG;IACxF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC1C,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,cAAsB,kCAAkC,EAAE;IACxG,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,cAAsB,+BAA+B,EAAE;IAEvD,OAAO,uBAAuB,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,EAAU,EACV,cAAsB,+BAA+B,EAAE;IAEvD,OAAO,qBAAqB,CAAC,0BAA0B,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7G,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,IAAwB,EACxB,cAAsB,+BAA+B,EAAE;IAEvD,OAAO,wBAAwB,CAAC,0BAA0B,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;AAClH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import type { RuhrohScenarioTier } from "./scenarios.js";
4
+ interface RuntimeDeps {
5
+ spawn: typeof spawnSync;
6
+ env: NodeJS.ProcessEnv;
7
+ cwd: string;
8
+ stdout: Pick<NodeJS.WriteStream, "write">;
9
+ stderr: Pick<NodeJS.WriteStream, "write">;
10
+ }
11
+ export interface RuhrohCliOptions {
12
+ command: "run" | "generate";
13
+ list: boolean;
14
+ dryRun: boolean;
15
+ generateOnly: boolean;
16
+ harborBin: string;
17
+ scenarioDir: string;
18
+ generatedDir: string;
19
+ scenarioId?: string | undefined;
20
+ tier?: RuhrohScenarioTier | undefined;
21
+ iterations?: number | undefined;
22
+ adapter?: string | undefined;
23
+ }
24
+ export declare function parseRuhrohCliArgs(argv: string[], cwd?: string): RuhrohCliOptions;
25
+ export declare function runRuhrohCli(argv: string[], deps: RuntimeDeps): Promise<number>;
26
+ export declare function resolveRuhrohPackageRoot(): string;
27
+ export declare function resolveRuhrohPythonPath(): string;
28
+ export declare function buildHarborSpawnEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
29
+ export {};
30
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAyB,MAAM,oBAAoB,CAAC;AAOtE,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,UAAU,WAAW;IACnB,KAAK,EAAE,OAAO,SAAS,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,GAAG,UAAU,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAcD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAsB,GAAG,gBAAgB,CAgFhG;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAyFrF;AAgED,wBAAgB,wBAAwB,IAAI,MAAM,CAIjD;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAQ7E"}
package/dist/cli.js ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { realpathSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { RUHROH_ARTIFACTS, RUHROH_HARBOR_AGENT_IMPORT_PATH, buildRuhrohHarborCommands } from "./harbor.js";
7
+ import { discoverRuhrohScenarios, generateHarborDataset, loadRuhrohScenario } from "./generate.js";
8
+ class HelpRequested extends Error {
9
+ }
10
+ export function parseRuhrohCliArgs(argv, cwd = process.cwd()) {
11
+ const options = {
12
+ command: "run",
13
+ list: false,
14
+ dryRun: false,
15
+ generateOnly: false,
16
+ harborBin: "harbor",
17
+ scenarioDir: path.join(resolveRuhrohPackageRoot(), "scenarios"),
18
+ generatedDir: path.resolve(cwd, ".generated", "ruhroh"),
19
+ tier: "smoke",
20
+ };
21
+ for (let index = 0; index < argv.length; index += 1) {
22
+ const arg = argv[index];
23
+ if (arg === undefined || arg === "--") {
24
+ continue;
25
+ }
26
+ if (index === 0 && (arg === "run" || arg === "generate")) {
27
+ options.command = arg;
28
+ if (arg === "generate") {
29
+ options.generateOnly = true;
30
+ }
31
+ continue;
32
+ }
33
+ if (arg === "--help" || arg === "-h") {
34
+ throw new HelpRequested();
35
+ }
36
+ if (arg === "--list") {
37
+ options.list = true;
38
+ continue;
39
+ }
40
+ if (arg === "--dry-run") {
41
+ options.dryRun = true;
42
+ continue;
43
+ }
44
+ if (arg === "--generate-only") {
45
+ options.generateOnly = true;
46
+ continue;
47
+ }
48
+ if (arg === "--scenario") {
49
+ options.scenarioId = assertSafeScenarioId(readValue(argv, index, arg));
50
+ options.tier = undefined;
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (arg === "--tier") {
55
+ options.tier = parseTier(readValue(argv, index, arg));
56
+ options.scenarioId = undefined;
57
+ index += 1;
58
+ continue;
59
+ }
60
+ if (arg === "--scenario-dir") {
61
+ options.scenarioDir = path.resolve(cwd, readValue(argv, index, arg));
62
+ index += 1;
63
+ continue;
64
+ }
65
+ if (arg === "--generated-dir") {
66
+ options.generatedDir = path.resolve(cwd, readValue(argv, index, arg));
67
+ index += 1;
68
+ continue;
69
+ }
70
+ if (arg === "--iterations") {
71
+ options.iterations = parsePositiveInteger(readValue(argv, index, arg), arg);
72
+ index += 1;
73
+ continue;
74
+ }
75
+ if (arg === "--adapter") {
76
+ options.adapter = readValue(argv, index, arg);
77
+ index += 1;
78
+ continue;
79
+ }
80
+ if (arg === "--harbor-bin") {
81
+ options.harborBin = readValue(argv, index, arg);
82
+ index += 1;
83
+ continue;
84
+ }
85
+ throw new Error(`Unknown argument: ${arg}`);
86
+ }
87
+ return options;
88
+ }
89
+ export async function runRuhrohCli(argv, deps) {
90
+ let options;
91
+ try {
92
+ options = parseRuhrohCliArgs(argv, deps.cwd);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof HelpRequested) {
96
+ deps.stdout.write(helpText());
97
+ return 0;
98
+ }
99
+ deps.stderr.write(`ruhroh failed: ${error instanceof Error ? error.message : String(error)}\n\n`);
100
+ deps.stderr.write(helpText());
101
+ return 1;
102
+ }
103
+ let loaded;
104
+ try {
105
+ loaded = discoverRuhrohScenarios(options.scenarioDir).map((source) => loadRuhrohScenario(source));
106
+ if (loaded.length === 0) {
107
+ throw new Error(`No Ruhroh scenarios found in ${options.scenarioDir}`);
108
+ }
109
+ }
110
+ catch (error) {
111
+ deps.stderr.write(`ruhroh failed: ${error instanceof Error ? error.message : String(error)}\n`);
112
+ return 1;
113
+ }
114
+ if (options.list) {
115
+ for (const { scenario } of loaded) {
116
+ deps.stdout.write(`${scenario.id}\t${scenario.tier}\t${scenario.title}\n`);
117
+ }
118
+ return 0;
119
+ }
120
+ let selected;
121
+ try {
122
+ selected = selectScenarios(loaded, options);
123
+ }
124
+ catch (error) {
125
+ deps.stderr.write(`ruhroh failed: ${error instanceof Error ? error.message : String(error)}\n`);
126
+ return 1;
127
+ }
128
+ let datasetPath = path.join(options.generatedDir, "harbor");
129
+ if (!options.dryRun || options.generateOnly) {
130
+ const generated = generateHarborDataset({
131
+ scenarios: selected,
132
+ outputRoot: options.generatedDir,
133
+ agentImportPath: RUHROH_HARBOR_AGENT_IMPORT_PATH,
134
+ artifacts: RUHROH_ARTIFACTS,
135
+ });
136
+ datasetPath = generated.datasetPath;
137
+ for (const task of generated.tasks) {
138
+ deps.stdout.write(`[ruhroh] generated ${task.scenarioId}: ${path.relative(deps.cwd, task.taskDir)}\n`);
139
+ }
140
+ }
141
+ if (options.generateOnly) {
142
+ deps.stdout.write("[ruhroh] generate-only complete. No Harbor tasks were started.\n");
143
+ return 0;
144
+ }
145
+ let adapter;
146
+ try {
147
+ adapter = resolveAdapterSelection(options, deps.cwd, deps.env);
148
+ }
149
+ catch (error) {
150
+ deps.stderr.write(`ruhroh failed: ${error instanceof Error ? error.message : String(error)}\n`);
151
+ return 1;
152
+ }
153
+ const commands = buildCommands(selected.map((item) => item.scenario), options, datasetPath, adapter);
154
+ deps.stdout.write(`[ruhroh] selected=${commands.map((command) => command.scenarioId).join(",")}\n`);
155
+ for (const command of commands) {
156
+ deps.stdout.write(`[ruhroh] harbor: ${formatCommand(options.harborBin, command.args)}\n`);
157
+ }
158
+ if (options.dryRun) {
159
+ deps.stdout.write("[ruhroh] dry run complete. No Harbor tasks were started.\n");
160
+ return 0;
161
+ }
162
+ let failed = false;
163
+ const harborEnv = buildHarborSpawnEnv(adapter.env);
164
+ for (const command of commands) {
165
+ const result = deps.spawn(options.harborBin, command.args, {
166
+ cwd: deps.cwd,
167
+ env: harborEnv,
168
+ stdio: "inherit",
169
+ });
170
+ if (result.status !== 0 || result.error !== undefined) {
171
+ deps.stderr.write(`[ruhroh] Harbor command failed for ${command.scenarioId}.\n${formatSpawnFailure(result)}`);
172
+ failed = true;
173
+ }
174
+ }
175
+ return failed ? 1 : 0;
176
+ }
177
+ function selectScenarios(loaded, options) {
178
+ if (options.scenarioId !== undefined) {
179
+ const scenario = loaded.find((item) => item.scenario.id === options.scenarioId);
180
+ if (scenario === undefined) {
181
+ throw new Error(`Unknown Ruhroh scenario: ${options.scenarioId}`);
182
+ }
183
+ return [scenario];
184
+ }
185
+ return loaded.filter((item) => item.scenario.tier === (options.tier ?? "smoke"));
186
+ }
187
+ function buildCommands(scenarios, options, datasetPath, adapter) {
188
+ return buildRuhrohHarborCommands({
189
+ scenarios,
190
+ adapter: adapter.adapterId,
191
+ datasetPath,
192
+ iterations: options.iterations,
193
+ env: adapter.env,
194
+ agentImportPath: RUHROH_HARBOR_AGENT_IMPORT_PATH,
195
+ artifacts: RUHROH_ARTIFACTS,
196
+ });
197
+ }
198
+ function resolveAdapterSelection(options, cwd, env) {
199
+ const adapter = options.adapter?.trim();
200
+ if (adapter === undefined || adapter.length === 0) {
201
+ throw new Error("--adapter is required for ruhroh run and dry-run commands");
202
+ }
203
+ if (looksLikeAdapterCommand(adapter)) {
204
+ const command = adapter.includes(" ")
205
+ ? adapter
206
+ : path.resolve(cwd, adapter);
207
+ const adapterId = adapterIdFromCommand(command);
208
+ return {
209
+ adapterId,
210
+ env: {
211
+ ...env,
212
+ RUHROH_RUN_AGENT_COMMAND: command,
213
+ },
214
+ };
215
+ }
216
+ return { adapterId: adapter, env };
217
+ }
218
+ function looksLikeAdapterCommand(adapter) {
219
+ return /\s/u.test(adapter) || adapter.startsWith(".") || adapter.startsWith("/") || adapter.includes(path.sep);
220
+ }
221
+ function adapterIdFromCommand(command) {
222
+ const firstPathLikePart = command.split(/\s+/u).find((part) => part.includes(path.sep)) ?? command;
223
+ return path.basename(firstPathLikePart).replace(/\.[cm]?[jt]sx?$/u, "") || "command";
224
+ }
225
+ export function resolveRuhrohPackageRoot() {
226
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
227
+ const basename = path.basename(moduleDir);
228
+ return basename === "dist" || basename === "src" ? path.dirname(moduleDir) : moduleDir;
229
+ }
230
+ export function resolveRuhrohPythonPath() {
231
+ return path.join(resolveRuhrohPackageRoot(), "python");
232
+ }
233
+ export function buildHarborSpawnEnv(env) {
234
+ const pythonPath = resolveRuhrohPythonPath();
235
+ return {
236
+ ...env,
237
+ PYTHONPATH: env.PYTHONPATH === undefined || env.PYTHONPATH.trim().length === 0
238
+ ? pythonPath
239
+ : `${pythonPath}${path.delimiter}${env.PYTHONPATH}`,
240
+ };
241
+ }
242
+ function parseTier(value) {
243
+ if (value === "smoke" || value === "nightly" || value === "release") {
244
+ return value;
245
+ }
246
+ throw new Error(`Unknown Ruhroh tier: ${value}`);
247
+ }
248
+ function parsePositiveInteger(value, arg) {
249
+ const parsed = Number.parseInt(value, 10);
250
+ if (!Number.isFinite(parsed) || parsed <= 0) {
251
+ throw new Error(`${arg} must be a positive integer`);
252
+ }
253
+ return parsed;
254
+ }
255
+ function assertSafeScenarioId(value) {
256
+ if (!/^[a-zA-Z0-9._-]+$/u.test(value) || value === "." || value === "..") {
257
+ throw new Error(`Unsafe Ruhroh scenario id: ${value}`);
258
+ }
259
+ return value;
260
+ }
261
+ function readValue(argv, index, arg) {
262
+ const value = argv[index + 1];
263
+ if (value === undefined || value.length === 0) {
264
+ throw new Error(`${arg} requires a value`);
265
+ }
266
+ return value;
267
+ }
268
+ function formatCommand(command, args) {
269
+ return [command, ...args].map((part) => (/^[a-zA-Z0-9_./:=@-]+$/u.test(part) ? part : JSON.stringify(part))).join(" ");
270
+ }
271
+ function formatSpawnFailure(result) {
272
+ const stdout = result.stdout ?? Buffer.alloc(0);
273
+ const stderr = result.stderr ?? Buffer.alloc(0);
274
+ const parts = [
275
+ `status=${String(result.status)}`,
276
+ result.error === undefined ? "" : `error=${result.error.message}`,
277
+ stdout.length > 0 ? `stdout:\n${stdout.toString("utf8")}` : "",
278
+ stderr.length > 0 ? `stderr:\n${stderr.toString("utf8")}` : "",
279
+ ].filter(Boolean);
280
+ return `${parts.join("\n")}\n`;
281
+ }
282
+ function helpText() {
283
+ return `Usage: ruhroh [run|generate] [options]\n\nCommon commands:\n ruhroh --list\n ruhroh --scenario simple-newsletter --generate-only\n ruhroh --scenario simple-newsletter --dry-run\n ruhroh generate --tier smoke\n\nOptions:\n --list List JSON scenarios.\n --scenario <id> Select one scenario.\n --tier <tier> Select scenarios in tier: smoke, nightly, release. Default: smoke.\n --scenario-dir <path> Scenario root. Default: bundled package scenarios.\n --generated-dir <path> Generated output root. Default: .generated/ruhroh.\n --iterations <n> Override implementation iterations.\n --adapter <id-or-command> Select run-agent adapter by id or command path. Required for run/dry-run.\n --generate-only Generate Harbor task directories without running Harbor.\n --harbor-bin <path> Harbor binary. Default: harbor.\n --dry-run Print Harbor commands without writing tasks or running Harbor.\n`;
284
+ }
285
+ async function main() {
286
+ const code = await runRuhrohCli(process.argv.slice(2), {
287
+ spawn: spawnSync,
288
+ env: process.env,
289
+ cwd: process.cwd(),
290
+ stdout: process.stdout,
291
+ stderr: process.stderr,
292
+ });
293
+ process.exitCode = code;
294
+ }
295
+ if (isDirectCliInvocation()) {
296
+ main().catch((error) => {
297
+ process.stderr.write(`ruhroh failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
298
+ process.exitCode = 1;
299
+ });
300
+ }
301
+ function isDirectCliInvocation() {
302
+ if (process.argv[1] === undefined) {
303
+ return false;
304
+ }
305
+ const modulePath = fileURLToPath(import.meta.url);
306
+ try {
307
+ return realpathSync(process.argv[1]) === realpathSync(modulePath);
308
+ }
309
+ catch {
310
+ return path.resolve(process.argv[1]) === modulePath;
311
+ }
312
+ }
313
+ //# sourceMappingURL=cli.js.map