@revisium/orchestrator 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (154) hide show
  1. package/control-plane/bootstrap.config.json +103 -2
  2. package/control-plane/default-playbook/catalog/pipelines.json +360 -0
  3. package/control-plane/default-playbook/catalog/roles.json +56 -0
  4. package/control-plane/default-playbook/package.json +5 -0
  5. package/control-plane/default-playbook/playbook.json +13 -0
  6. package/control-plane/default-playbook/prompts/analyst.md +27 -0
  7. package/control-plane/default-playbook/prompts/developer.md +26 -0
  8. package/control-plane/default-playbook/prompts/integrator.md +20 -0
  9. package/control-plane/default-playbook/prompts/orchestrator.md +23 -0
  10. package/control-plane/default-playbook/prompts/reviewer.md +28 -0
  11. package/control-plane/default-playbook/prompts/watcher.md +24 -0
  12. package/dist/cli/commands/bootstrap.js +68 -7
  13. package/dist/cli/commands/bootstrap.js.map +1 -1
  14. package/dist/cli/commands/inbox.js +0 -21
  15. package/dist/cli/commands/inbox.js.map +1 -1
  16. package/dist/cli/commands/run.js +76 -49
  17. package/dist/cli/commands/run.js.map +1 -1
  18. package/dist/cli/needs-host.js +7 -8
  19. package/dist/cli/needs-host.js.map +1 -1
  20. package/dist/cli/program.js +0 -2
  21. package/dist/cli/program.js.map +1 -1
  22. package/dist/config.js +18 -1
  23. package/dist/config.js.map +1 -1
  24. package/dist/control-plane/client-transport.js +73 -40
  25. package/dist/control-plane/client-transport.js.map +1 -1
  26. package/dist/control-plane/data-access.js +3 -2
  27. package/dist/control-plane/data-access.js.map +1 -1
  28. package/dist/control-plane/definitions.js +1 -1
  29. package/dist/control-plane/definitions.js.map +1 -1
  30. package/dist/control-plane/json-fields.js +2 -0
  31. package/dist/control-plane/json-fields.js.map +1 -1
  32. package/dist/control-plane/schema-migration.js +86 -0
  33. package/dist/control-plane/schema-migration.js.map +1 -0
  34. package/dist/control-plane/seed-default-playbook.js +71 -0
  35. package/dist/control-plane/seed-default-playbook.js.map +1 -0
  36. package/dist/control-plane/tables.js +1 -0
  37. package/dist/control-plane/tables.js.map +1 -1
  38. package/dist/e2e/kit/agents.js +113 -0
  39. package/dist/e2e/kit/agents.js.map +1 -0
  40. package/dist/e2e/kit/assertions.js +119 -0
  41. package/dist/e2e/kit/assertions.js.map +1 -0
  42. package/dist/e2e/kit/crash.js +44 -0
  43. package/dist/e2e/kit/crash.js.map +1 -0
  44. package/dist/e2e/kit/drive.js +49 -0
  45. package/dist/e2e/kit/drive.js.map +1 -0
  46. package/dist/e2e/kit/env.js +16 -0
  47. package/dist/e2e/kit/env.js.map +1 -0
  48. package/dist/e2e/kit/fake-integrator.js +42 -0
  49. package/dist/e2e/kit/fake-integrator.js.map +1 -0
  50. package/dist/e2e/kit/gh-emulator.js +49 -0
  51. package/dist/e2e/kit/gh-emulator.js.map +1 -0
  52. package/dist/e2e/kit/git-target-repo.js +83 -0
  53. package/dist/e2e/kit/git-target-repo.js.map +1 -0
  54. package/dist/e2e/kit/harness.js +59 -0
  55. package/dist/e2e/kit/harness.js.map +1 -0
  56. package/dist/e2e/kit/index.js +14 -0
  57. package/dist/e2e/kit/index.js.map +1 -0
  58. package/dist/e2e/kit/mcp.js +32 -0
  59. package/dist/e2e/kit/mcp.js.map +1 -0
  60. package/dist/e2e/kit/scenarios.js +196 -0
  61. package/dist/e2e/kit/scenarios.js.map +1 -0
  62. package/dist/e2e/recovery-crash-child.js +29 -0
  63. package/dist/e2e/recovery-crash-child.js.map +1 -0
  64. package/dist/e2e/recovery-dd-crash-child.js +42 -0
  65. package/dist/e2e/recovery-dd-crash-child.js.map +1 -0
  66. package/dist/engine/ensure-postgres.js +13 -1
  67. package/dist/engine/ensure-postgres.js.map +1 -1
  68. package/dist/mcp/mcp-capabilities.js +1 -0
  69. package/dist/mcp/mcp-capabilities.js.map +1 -1
  70. package/dist/mcp/mcp-facade.service.js +24 -3
  71. package/dist/mcp/mcp-facade.service.js.map +1 -1
  72. package/dist/mcp/mcp-tools.js +16 -7
  73. package/dist/mcp/mcp-tools.js.map +1 -1
  74. package/dist/pipeline/data-driven-task.workflow.js +493 -0
  75. package/dist/pipeline/data-driven-task.workflow.js.map +1 -0
  76. package/dist/pipeline/data-driven-template.js +57 -0
  77. package/dist/pipeline/data-driven-template.js.map +1 -0
  78. package/dist/pipeline/develop-task.workflow.js +151 -316
  79. package/dist/pipeline/develop-task.workflow.js.map +1 -1
  80. package/dist/pipeline/pipeline.module.js +1 -1
  81. package/dist/pipeline/route-contract.js +82 -0
  82. package/dist/pipeline/route-contract.js.map +1 -0
  83. package/dist/pipeline-core/index.js +16 -0
  84. package/dist/pipeline-core/index.js.map +1 -0
  85. package/dist/pipeline-core/interpret.js +351 -0
  86. package/dist/pipeline-core/interpret.js.map +1 -0
  87. package/dist/pipeline-core/kit/assertions.js +61 -0
  88. package/dist/pipeline-core/kit/assertions.js.map +1 -0
  89. package/dist/pipeline-core/kit/builders.js +136 -0
  90. package/dist/pipeline-core/kit/builders.js.map +1 -0
  91. package/dist/pipeline-core/kit/drive.js +96 -0
  92. package/dist/pipeline-core/kit/drive.js.map +1 -0
  93. package/dist/pipeline-core/kit/fixtures.js +248 -0
  94. package/dist/pipeline-core/kit/fixtures.js.map +1 -0
  95. package/dist/pipeline-core/kit/index.js +13 -0
  96. package/dist/pipeline-core/kit/index.js.map +1 -0
  97. package/dist/pipeline-core/types.js +132 -0
  98. package/dist/pipeline-core/types.js.map +1 -0
  99. package/dist/pipeline-core/validate.js +1192 -0
  100. package/dist/pipeline-core/validate.js.map +1 -0
  101. package/dist/playbook/catalog-loader.js +14 -1
  102. package/dist/playbook/catalog-loader.js.map +1 -1
  103. package/dist/playbook/import-mapper.js +14 -38
  104. package/dist/playbook/import-mapper.js.map +1 -1
  105. package/dist/playbook/manifest.js +1 -1
  106. package/dist/revisium/playbooks.service.js +95 -17
  107. package/dist/revisium/playbooks.service.js.map +1 -1
  108. package/dist/revisium/roles.service.js +1 -1
  109. package/dist/revisium/roles.service.js.map +1 -1
  110. package/dist/revisium/run.service.js +16 -0
  111. package/dist/revisium/run.service.js.map +1 -1
  112. package/dist/run/append-event.js +24 -1
  113. package/dist/run/append-event.js.map +1 -1
  114. package/dist/run/block-run.js +17 -0
  115. package/dist/run/block-run.js.map +1 -0
  116. package/dist/run/create-run.js +28 -5
  117. package/dist/run/create-run.js.map +1 -1
  118. package/dist/run/inspect-run.js +9 -0
  119. package/dist/run/inspect-run.js.map +1 -1
  120. package/dist/run/run-outputs.js +88 -0
  121. package/dist/run/run-outputs.js.map +1 -0
  122. package/dist/run/terminal-run-status.js +89 -7
  123. package/dist/run/terminal-run-status.js.map +1 -1
  124. package/dist/runners/claude-code.service.js +4 -0
  125. package/dist/runners/claude-code.service.js.map +1 -1
  126. package/dist/runners/integrator.js +25 -9
  127. package/dist/runners/integrator.js.map +1 -1
  128. package/dist/task-control-plane/task-control-plane-api.service.js +247 -28
  129. package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -1
  130. package/dist/worker/artifact-store.js +111 -0
  131. package/dist/worker/artifact-store.js.map +1 -0
  132. package/dist/worker/build-context.js +17 -0
  133. package/dist/worker/build-context.js.map +1 -1
  134. package/dist/worker/claude-code-runner.js +44 -5
  135. package/dist/worker/claude-code-runner.js.map +1 -1
  136. package/dist/worker/process-executor.js +23 -2
  137. package/dist/worker/process-executor.js.map +1 -1
  138. package/dist/worker/runner-dispatch.js +1 -0
  139. package/dist/worker/runner-dispatch.js.map +1 -1
  140. package/dist/worker/runner.js +11 -1
  141. package/dist/worker/runner.js.map +1 -1
  142. package/dist/worker/stub-runner.js +7 -45
  143. package/dist/worker/stub-runner.js.map +1 -1
  144. package/package.json +3 -1
  145. package/dist/cli/commands/work.js +0 -149
  146. package/dist/cli/commands/work.js.map +0 -1
  147. package/dist/worker/git-worktree-manager.js +0 -84
  148. package/dist/worker/git-worktree-manager.js.map +0 -1
  149. package/dist/worker/loop.js +0 -122
  150. package/dist/worker/loop.js.map +0 -1
  151. package/dist/worker/script-runner.js +0 -28
  152. package/dist/worker/script-runner.js.map +0 -1
  153. package/dist/worker/worker-id.js +0 -18
  154. package/dist/worker/worker-id.js.map +0 -1
@@ -0,0 +1,113 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ function pickBehavior(spec, role, callIndex) {
4
+ const entry = spec.byRole?.[role];
5
+ if (Array.isArray(entry))
6
+ return entry[Math.min(callIndex, entry.length - 1)] ?? { kind: 'pass' };
7
+ return entry ?? spec.default ?? { kind: 'pass' };
8
+ }
9
+ function runBehavior(behavior, ctx, sink) {
10
+ if (behavior.kind === 'throw') {
11
+ throw new Error(behavior.message ?? `scripted failure from ${ctx.logicalRole}`);
12
+ }
13
+ const writeRepo = ctx.logicalRole === 'developer' ? sink.developerWrites.get(ctx.runId) : undefined;
14
+ if (writeRepo && behavior.kind !== 'needsHuman') {
15
+ writeFileSync(join(writeRepo, `developer-${ctx.attemptId}.txt`), `change from ${ctx.attemptId}\n`);
16
+ }
17
+ const verdict = behavior.kind === 'verdict' || behavior.kind === 'domainVerdict' ? behavior.verdict : 'PASS';
18
+ const cost = behavior.kind === 'cost'
19
+ ? { inputTokens: behavior.inputTokens, outputTokens: behavior.outputTokens, costAmount: behavior.costAmount }
20
+ : { inputTokens: 10, outputTokens: 5, costAmount: 0.001 };
21
+ return {
22
+ output: { verdict, role: ctx.logicalRole, runner: ctx.runner },
23
+ artifacts: {
24
+ process: { ref: `test-artifacts/${ctx.attemptId}`, stdoutTail: `stdout from ${ctx.logicalRole}`, stderrTail: '' },
25
+ },
26
+ nextSteps: [],
27
+ costs: [{ modelProfile: ctx.level, currency: 'USD', ...cost }],
28
+ needsHuman: behavior.kind === 'needsHuman',
29
+ lesson: behavior.kind === 'needsHuman' ? behavior.lesson : undefined,
30
+ };
31
+ }
32
+ /** Agent driven by a single {@link AgentSpec} (same plan for every run). Records into `sink`. */
33
+ export function scriptedAgent(spec, sink) {
34
+ const counts = new Map();
35
+ return async ({ role, profile, attemptId, step, context }) => {
36
+ const logicalRole = role.playbookRoleId ?? role.name;
37
+ sink.agentCalls.push({ role: logicalRole, runner: role.runner, attemptId, runId: step.runId, context });
38
+ const n = counts.get(logicalRole) ?? 0;
39
+ counts.set(logicalRole, n + 1);
40
+ return runBehavior(pickBehavior(spec, logicalRole, n), {
41
+ logicalRole,
42
+ runner: role.runner,
43
+ attemptId,
44
+ runId: step.runId,
45
+ level: profile.level,
46
+ }, sink);
47
+ };
48
+ }
49
+ /**
50
+ * Agent that dispatches to a per-run {@link AgentSpec} from `specs` (keyed by runId), so one harness
51
+ * can drive many runs with different failure scripts. Create the run with `start:false`, register its
52
+ * spec in `specs`, then `startRun` — the workflow then reads this run's plan. Defaults to all-PASS.
53
+ */
54
+ export function routedScriptedAgent(specs, sink) {
55
+ const counts = new Map();
56
+ return async ({ role, profile, attemptId, step, context }) => {
57
+ const logicalRole = role.playbookRoleId ?? role.name;
58
+ sink.agentCalls.push({ role: logicalRole, runner: role.runner, attemptId, runId: step.runId, context });
59
+ const key = `${step.runId}::${logicalRole}`;
60
+ const n = counts.get(key) ?? 0;
61
+ counts.set(key, n + 1);
62
+ const spec = specs.get(step.runId) ?? {};
63
+ return runBehavior(pickBehavior(spec, logicalRole, n), {
64
+ logicalRole,
65
+ runner: role.runner,
66
+ attemptId,
67
+ runId: step.runId,
68
+ level: profile.level,
69
+ }, sink);
70
+ };
71
+ }
72
+ /**
73
+ * Deterministic test agent: records every call, returns a PASS verdict with fixed costs and a
74
+ * process artifact. When the logical role is `developer` and a worktree is registered for the run,
75
+ * it writes a file so the real integrator has a diff to commit.
76
+ *
77
+ * This is the default agent used by `createRunHarness()`; it replaces `claude-code` via
78
+ * `executionProfile.runnerOverrides`, so no real `claude` process is ever spawned in e2e.
79
+ */
80
+ export function deterministicAgent(agentCalls, developerWrites) {
81
+ return async ({ role, profile, attemptId, step, context }) => {
82
+ const logicalRole = role.playbookRoleId ?? role.name;
83
+ agentCalls.push({ role: logicalRole, runner: role.runner, attemptId, runId: step.runId, context });
84
+ const writeRepo = logicalRole === 'developer' ? developerWrites.get(step.runId) : undefined;
85
+ if (writeRepo) {
86
+ writeFileSync(join(writeRepo, `developer-${attemptId}.txt`), `change from ${attemptId}\n`);
87
+ }
88
+ return {
89
+ output: {
90
+ verdict: 'PASS',
91
+ role: logicalRole,
92
+ runner: role.runner,
93
+ },
94
+ artifacts: {
95
+ process: {
96
+ ref: `test-artifacts/${attemptId}`,
97
+ stdoutTail: `stdout from ${logicalRole}`,
98
+ stderrTail: '',
99
+ },
100
+ },
101
+ nextSteps: [],
102
+ costs: [{
103
+ modelProfile: profile.level,
104
+ inputTokens: 10,
105
+ outputTokens: 5,
106
+ costAmount: 0.001,
107
+ currency: 'USD',
108
+ }],
109
+ needsHuman: false,
110
+ };
111
+ };
112
+ }
113
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/e2e/kit/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import assert from 'node:assert/strict';
2
+ import { allSteps } from './drive.js';
3
+ /** Assert the run reached `completed` and left no claimable (`ready`) steps. Returns the detail. */
4
+ export async function assertCompleted(api, runId) {
5
+ // The step-status cascade lags the run-row status patch (recordTerminalRunStatus patches step rows
6
+ // AFTER the run row), so under load a run can read `completed` with a step still `ready` for a beat.
7
+ // Poll briefly for the steps to settle rather than reading once — a step genuinely stuck `ready`
8
+ // still fails after the window (a real bug), but the propagation lag no longer flakes the suite.
9
+ let detail = await api.getRun({ runId, includeEvents: true, includeLog: true });
10
+ for (let waited = 0; waited < 5_000 && allSteps(detail).some((s) => s.status === 'ready'); waited += 250) {
11
+ await new Promise((resolve) => setTimeout(resolve, 250));
12
+ detail = await api.getRun({ runId, includeEvents: true, includeLog: true });
13
+ }
14
+ assert.equal(detail.run.status, 'completed');
15
+ assert.ok(allSteps(detail).every((s) => s.status !== 'ready'), 'terminal run must not leave ready steps');
16
+ return detail;
17
+ }
18
+ /** Assert the recorded attempt verdicts (in order) and that each carries a process artifact ref. */
19
+ export async function assertAttemptVerdicts(api, runId, verdicts) {
20
+ const attempts = await api.getRunLog({ runId, limit: 50 });
21
+ assert.deepEqual(attempts.map((a) => a.verdict), verdicts);
22
+ assert.ok(attempts.every((a) => a.artifactRef?.startsWith('test-artifacts/')), 'attempts carry process artifact refs');
23
+ return attempts;
24
+ }
25
+ /** Assert every listed event type is visible for the run. Polls briefly: a terminal event such as
26
+ * `run_completed` is appended just AFTER the run-row status patch (same ordering `assertCompleted`
27
+ * guards — the run row settles first, the rest lags a beat), so a single read taken the instant
28
+ * `approveUntilTerminal` returns can race the append and miss it (the F1 recovery flake). A type that
29
+ * is genuinely never emitted still fails after the window — this hides propagation lag, not real gaps. */
30
+ export async function assertEventsPresent(api, runId, types) {
31
+ let events = await api.getRunEvents({ runId, limit: 50 });
32
+ const missing = () => types.filter((type) => !events.some((e) => e.type === type));
33
+ for (let waited = 0; waited < 5_000 && missing().length > 0; waited += 250) {
34
+ await new Promise((resolve) => setTimeout(resolve, 250));
35
+ events = await api.getRunEvents({ runId, limit: 50 });
36
+ }
37
+ for (const type of types) {
38
+ assert.ok(events.some((e) => e.type === type), `event "${type}" must be visible`);
39
+ }
40
+ }
41
+ /** Assert the run digest reports `completed`, no pending inbox, and the expected usage totals. */
42
+ export async function assertUsage(api, runId, usage) {
43
+ const digest = await api.getRunDigest(runId);
44
+ assert.equal(digest.run.status, 'completed');
45
+ assert.equal(digest.pendingInbox.length, 0);
46
+ assert.equal(digest.usage.inputTokens, usage.inputTokens);
47
+ assert.equal(digest.usage.outputTokens, usage.outputTokens);
48
+ assert.equal(digest.usage.costAmount, usage.costAmount);
49
+ }
50
+ /** Assert the run did not complete and emitted a `pipeline_blocked` event (preflight/integrate/etc.). */
51
+ export async function assertBlocked(api, runId) {
52
+ const events = await api.getRunEvents({ runId, limit: 50 });
53
+ assert.ok(events.some((e) => e.type === 'pipeline_blocked'), 'a blocked run must emit pipeline_blocked');
54
+ const detail = await api.getRun({ runId });
55
+ assert.notEqual(detail.run.status, 'completed', 'a blocked run must not be completed');
56
+ }
57
+ /** Assert no persisted event for the run carries the raw token (serialized scan over all events). */
58
+ export async function assertNoRawTokenInEvents(api, runId, rawToken) {
59
+ const events = await api.getRunEvents({ runId, limit: 50 });
60
+ assert.ok(!JSON.stringify(events).includes(rawToken), `a raw token must never reach persisted events: ${rawToken}`);
61
+ }
62
+ /**
63
+ * Assert a raw GitHub token never reaches the run's persisted events, and that the blocking
64
+ * `pipeline_blocked` lesson was redacted to `[REDACTED]`. Drives the token through a needsHuman
65
+ * integrator lesson; the persist boundary (appendRunEvent → redactEventPayload) must mask it.
66
+ */
67
+ export async function assertLessonRedacted(api, runId, rawToken) {
68
+ const events = await api.getRunEvents({ runId, limit: 50 });
69
+ const serialized = JSON.stringify(events);
70
+ assert.ok(!serialized.includes(rawToken), `a raw token must never reach persisted events: ${rawToken}`);
71
+ const blocked = events.find((e) => e.type === 'pipeline_blocked');
72
+ assert.ok(blocked, 'a redaction case must still block (pipeline_blocked emitted)');
73
+ const lesson = String(blocked.payload?.lesson ?? '');
74
+ assert.ok(lesson.includes('[REDACTED]'), 'the surfaced lesson must show the redaction marker');
75
+ }
76
+ /**
77
+ * Assert a recovered run's durable record is exactly-once — replay after crash recovery must not
78
+ * duplicate terminal or step events (deterministic ids + ROW_CONFLICT). Guards the idempotency the
79
+ * engine relies on so DBOS workflow replays are side-effect-free.
80
+ */
81
+ export async function assertReplayIdempotent(api, runId) {
82
+ const events = await api.getRunEvents({ runId, limit: 100 });
83
+ const completed = events.filter((e) => e.type === 'run_completed');
84
+ assert.equal(completed.length, 1, 'run_completed must appear exactly once after recovery');
85
+ const stepKeys = events
86
+ .filter((e) => e.type === 'step_succeeded')
87
+ .map((e) => String(e.payload?.stepKey ?? ''));
88
+ assert.equal(stepKeys.length, new Set(stepKeys).size, 'a replayed step must not emit a duplicate step_succeeded');
89
+ }
90
+ /** Assert a gh subcommand was NOT invoked for this run's branch (e.g. `pr create` when reusing a PR). */
91
+ export function assertGhNotCalled(h, taskId, sub) {
92
+ const branchPrefix = `feat/${taskId}-`;
93
+ assert.ok(!h.ghCalls.some((c) => c[0] === sub[0] && c[1] === sub[1] && c.some((a) => a.startsWith(branchPrefix))), `gh ${sub.join(' ')} must not be called for ${taskId}`);
94
+ }
95
+ /** Roles + runners that actually executed for a run (from the deterministic agent's recorded calls). */
96
+ export function executedRoles(h, runId) {
97
+ return h.agentCalls.filter((c) => c.runId === runId).map((c) => [c.role, c.runner]);
98
+ }
99
+ /**
100
+ * Assert the fake gh opened a draft PR for the run's feature branch (list → create → view),
101
+ * scoped by `taskId` so it is robust to other runs' gh calls. Returns the head branch name.
102
+ */
103
+ export function assertPrOpened(h, taskId, repo = 'e2e/repo') {
104
+ const branchPrefix = `feat/${taskId}-`;
105
+ // Scope to THIS run's gh calls via the feature branch (robust if other runs share the harness).
106
+ const list = h.ghCalls.find((c) => c[0] === 'pr' && c[1] === 'list' && c.some((arg) => arg.startsWith(branchPrefix)));
107
+ assert.ok(list, 'fake gh must list existing PRs for this branch before creating');
108
+ const repoIdx = list.indexOf('--repo');
109
+ assert.ok(repoIdx >= 0 && list[repoIdx + 1] === repo, `--repo ${repo} not found in pr list`);
110
+ const headIdx = list.indexOf('--head');
111
+ assert.ok(headIdx >= 0, '--head flag not found in pr list');
112
+ const branch = list[headIdx + 1];
113
+ assert.ok(branch, '--head value missing in pr list');
114
+ assert.ok(branch.startsWith(branchPrefix), `unexpected PR head branch: ${branch}`);
115
+ assert.ok(h.ghCalls.some((c) => c[0] === 'pr' && c[1] === 'create' && c.includes(branch)), 'fake gh must create a draft PR for this branch');
116
+ assert.ok(h.ghCalls.some((c) => c[0] === 'pr' && c[1] === 'view' && c.includes(branch)), 'fake gh must read back created PR metadata for this branch');
117
+ return branch;
118
+ }
119
+ //# sourceMappingURL=assertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../../src/e2e/kit/assertions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ import { repoRoot } from '../../config.js';
4
+ // Run the crash child from SOURCE via tsx (matches how the e2e suite runs; honors the
5
+ // REVO_DATA_DIR/REVO_PORT env so the child targets the isolated test daemon).
6
+ const TSX_BIN = join(repoRoot, 'node_modules', '.bin', 'tsx');
7
+ const CHILD_ENTRY = join(repoRoot, 'src', 'e2e', 'recovery-crash-child.ts');
8
+ const DD_CHILD_ENTRY = join(repoRoot, 'src', 'e2e', 'recovery-dd-crash-child.ts');
9
+ /**
10
+ * Simulate a host crash: run a stubbed feature run in a SEPARATE process up to `stopAt`, then kill
11
+ * that process WITHOUT draining DBOS (the child calls process.exit() with no shutdown). The workflow
12
+ * is left PENDING in the shared test Postgres; the calling test then boots a fresh host that recovers
13
+ * it. Resolves with the crashed run's id. A separate process is mandatory — DBOS is a process-global,
14
+ * so a real crash + recovery cannot be faked in-process.
15
+ */
16
+ export function crashRunAt(stopAt) {
17
+ return spawnCrashChild(CHILD_ENTRY, stopAt);
18
+ }
19
+ /**
20
+ * Group L counterpart: crash a DATA-DRIVEN run (the pipeline-core graph executed by the DBOS adapter)
21
+ * in a separate process up to `stopAt`, then kill it without draining DBOS. Same recovery contract as
22
+ * {@link crashRunAt} — the parent test boots a fresh host that recovers the PENDING data-driven workflow.
23
+ */
24
+ export function crashDataDrivenRunAt(stopAt) {
25
+ return spawnCrashChild(DD_CHILD_ENTRY, stopAt);
26
+ }
27
+ function spawnCrashChild(entry, stopAt) {
28
+ return new Promise((resolve, reject) => {
29
+ const child = spawn(TSX_BIN, [entry, stopAt], { env: process.env, cwd: repoRoot });
30
+ let stdout = '';
31
+ let stderr = '';
32
+ child.stdout.on('data', (d) => (stdout += String(d)));
33
+ child.stderr.on('data', (d) => (stderr += String(d)));
34
+ child.on('error', reject);
35
+ child.on('exit', (code) => {
36
+ const match = stdout.match(/RUNID=(\S+)/);
37
+ if (match?.[1])
38
+ resolve({ runId: match[1] });
39
+ else
40
+ reject(new Error(`crash child did not emit RUNID (exit ${code}). stderr tail: ${stderr.slice(-400)}`));
41
+ });
42
+ });
43
+ }
44
+ //# sourceMappingURL=crash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash.js","sourceRoot":"","sources":["../../../src/e2e/kit/crash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,sFAAsF;AACtF,8EAA8E;AAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,CAAC,CAAC;AAC5E,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,4BAA4B,CAAC,CAAC;AAIlF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAsB;IAC/C,OAAO,eAAe,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,OAAO,eAAe,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,MAAsB;IAC5D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnF,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YAC1C,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;;gBACxC,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,IAAI,mBAAmB,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9G,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,49 @@
1
+ import assert from 'node:assert/strict';
2
+ // Poll at 500ms: tighter intervals can observe a run as terminal before its DBOS workflow status /
3
+ // step-status cascade settle, flaking `workflowStatus`/`no ready steps` assertions on slower CI.
4
+ // A real run settles in a few seconds — if a wait needs >10s the run is stuck (a bug), so fail fast.
5
+ const POLL_MS = 500;
6
+ const WAIT_TIMEOUT_MS = 10_000;
7
+ /** Flatten every step across a run's tasks. */
8
+ export function allSteps(detail) {
9
+ return detail.tasks.flatMap((task) => task.steps);
10
+ }
11
+ /** Poll until the run settles (terminal or parked at a gate). Returns the wait state. */
12
+ export function waitState(api, runId, timeoutMs = WAIT_TIMEOUT_MS) {
13
+ return api.waitForRun({ runId, timeoutMs, intervalMs: POLL_MS });
14
+ }
15
+ /** Wait until the run parks at a gate; assert it is a gate (optionally a specific topic). */
16
+ export async function waitForGate(api, runId, expectedTopic) {
17
+ const state = await api.waitForRun({ runId, timeoutMs: WAIT_TIMEOUT_MS, intervalMs: POLL_MS });
18
+ assert.equal(state.state, 'pending_gate', `expected pending_gate, got ${state.state}`);
19
+ const inbox = state.inbox;
20
+ assert.ok(inbox, 'pending_gate must include the inbox item to resolve');
21
+ const context = inbox.context;
22
+ assert.ok(context !== null && typeof context === 'object' && !Array.isArray(context));
23
+ const topic = context['topic'];
24
+ assert.equal(typeof topic, 'string');
25
+ if (expectedTopic)
26
+ assert.equal(topic, expectedTopic, `expected ${expectedTopic} gate, got ${String(topic)}`);
27
+ return { inboxId: inbox.id, topic: topic };
28
+ }
29
+ /**
30
+ * Drive a gated run to a terminal state by approving each gate as it opens.
31
+ * Returns the terminal `state` plus the ordered list of approved gate topics (e.g. `['plan','merge']`).
32
+ */
33
+ export async function approveUntilTerminal(api, runId) {
34
+ const approvedTopics = [];
35
+ for (;;) {
36
+ const state = await api.waitForRun({ runId, timeoutMs: WAIT_TIMEOUT_MS, intervalMs: POLL_MS });
37
+ if (state.state !== 'pending_gate')
38
+ return { state: state.state, approvedTopics };
39
+ const inbox = state.inbox;
40
+ assert.ok(inbox, 'pending_gate must include the inbox item to resolve');
41
+ const context = inbox.context;
42
+ assert.ok(context !== null && typeof context === 'object' && !Array.isArray(context));
43
+ const topic = context['topic'];
44
+ assert.equal(typeof topic, 'string');
45
+ approvedTopics.push(topic);
46
+ await api.approveGate({ inboxId: inbox.id, resolvedBy: 'e2e' });
47
+ }
48
+ }
49
+ //# sourceMappingURL=drive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drive.js","sourceRoot":"","sources":["../../../src/e2e/kit/drive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ /** Real e2e gate: only run against real DBOS/Revisium when explicitly enabled. */
4
+ export const RUN_REAL_E2E = process.env['REVO_E2E_REAL'] === '1';
5
+ /** `node:test` skip option — a hint string unless REVO_E2E_REAL=1 (then `false` = run). */
6
+ export const e2eSkip = RUN_REAL_E2E
7
+ ? false
8
+ : 'set REVO_E2E_REAL=1 to run real DBOS/Revisium E2E tests';
9
+ /**
10
+ * Playbook installed by the harness. Defaults to the SELF-CONTAINED fixture committed under
11
+ * `src/e2e/fixtures/playbook` (roles + pipelines catalog), so e2e runs are stable and independent
12
+ * of the external agent-playbook checkout. Module-relative (not cwd-dependent). Overridable via
13
+ * REVO_PLAYBOOK_SOURCE to install a different playbook.
14
+ */
15
+ export const PLAYBOOK_SOURCE = process.env['REVO_PLAYBOOK_SOURCE'] ?? resolve(dirname(fileURLToPath(import.meta.url)), '../fixtures/playbook');
16
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/e2e/kit/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,GAAG,CAAC;AAEjE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,OAAO,GAAmB,YAAY;IACjD,CAAC,CAAC,KAAK;IACP,CAAC,CAAC,yDAAyD,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { integrate, preflightLive, stubIntegrate, } from '../../runners/integrator.js';
2
+ import { execGit } from './git-target-repo.js';
3
+ /**
4
+ * Build an `IntegratorService`-shaped object wired to the real `integrate`/`preflightLive`/
5
+ * `stubIntegrate` with a real git (on a temp repo) and a fake `gh`.
6
+ *
7
+ * Deliberately bypasses `IntegratorService.runIntegrate`'s `resolvePinnedGh()` step — tests have no
8
+ * real gh identity to pin. Scenarios that must exercise the fail-loud gh-account pinning (D7) should
9
+ * drive the real `IntegratorService` instead of this fake.
10
+ */
11
+ export function createFakeIntegrator(runs, execGh) {
12
+ const deps = {
13
+ execGit,
14
+ execGh,
15
+ resolveTaskCwd: runs.makeResolveTaskCwd(),
16
+ };
17
+ return {
18
+ runIntegrate: (input) => integrate(input, deps),
19
+ runStub: (input) => stubIntegrate(input),
20
+ runPreflight: (taskId, base) => preflightLive(taskId, base, deps),
21
+ };
22
+ }
23
+ /**
24
+ * Wrap a base IntegratorService so runs whose taskId is registered in `outcomes` get a mocked
25
+ * integrate result (needsHuman or throw); all others delegate to `base` (real integrate/preflight).
26
+ * Mirrors the per-run gh/agent routers — the external boundary is mocked, the workflow is real.
27
+ */
28
+ export function routedIntegrator(outcomes, base) {
29
+ return {
30
+ runIntegrate: (input) => {
31
+ const outcome = outcomes.get(input.taskId);
32
+ if (outcome?.kind === 'throw')
33
+ return Promise.reject(new Error(outcome.message));
34
+ if (outcome?.kind === 'needsHuman')
35
+ return Promise.resolve({ needsHuman: true, lesson: outcome.lesson });
36
+ return base.runIntegrate(input);
37
+ },
38
+ runStub: base.runStub,
39
+ runPreflight: base.runPreflight,
40
+ };
41
+ }
42
+ //# sourceMappingURL=fake-integrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-integrator.js","sourceRoot":"","sources":["../../../src/e2e/kit/fake-integrator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,EACb,aAAa,GAMd,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgB,EAAE,MAAgB;IACrE,MAAM,IAAI,GAAmB;QAC3B,OAAO;QACP,MAAM;QACN,cAAc,EAAE,IAAI,CAAC,kBAAkB,EAAE;KAC1C,CAAC;IACF,OAAO;QACL,YAAY,EAAE,CAAC,KAAK,EAAiD,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC;QAC9F,OAAO,EAAE,CAAC,KAAK,EAAoB,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QAC1D,YAAY,EAAE,CAAC,MAAM,EAAE,IAAI,EAA6C,EAAE,CACxE,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;KACf,CAAC;AACzB,CAAC;AAOD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAwC,EACxC,IAAuB;IAEvB,OAAO;QACL,YAAY,EAAE,CAAC,KAAsB,EAAiD,EAAE;YACtF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,OAAO,EAAE,IAAI,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACjF,IAAI,OAAO,EAAE,IAAI,KAAK,YAAY;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACzG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,YAAY,EAAE,IAAI,CAAC,YAAY;KACX,CAAC;AACzB,CAAC"}
@@ -0,0 +1,49 @@
1
+ const PR_URL = 'https://github.com/e2e/repo/pull/1';
2
+ const PR_URL_2 = 'https://github.com/e2e/repo/pull/2';
3
+ const BASE = 'master'; // createTargetRepo() bases every run on master
4
+ function ghBehavior(scenario, args) {
5
+ if (scenario === 'gh-error') {
6
+ throw new Error('gh: API rate limit exceeded for installation (e2e gh-error scenario)');
7
+ }
8
+ if (scenario === 'gh-token-leak') {
9
+ throw new Error('gh: bad credentials using token gho_abcdEFGH1234567890LEAK rejected by server');
10
+ }
11
+ if (args[0] === 'pr' && args[1] === 'list') {
12
+ if (scenario === 'pr-already-exists')
13
+ return JSON.stringify([{ number: 7, url: PR_URL, baseRefName: BASE }]);
14
+ if (scenario === 'ambiguous-prs') {
15
+ return JSON.stringify([
16
+ { number: 7, url: PR_URL, baseRefName: BASE },
17
+ { number: 8, url: PR_URL_2, baseRefName: BASE },
18
+ ]);
19
+ }
20
+ return JSON.stringify([]);
21
+ }
22
+ if (args[0] === 'pr' && args[1] === 'create')
23
+ return `${PR_URL}\n`;
24
+ if (args[0] === 'pr' && args[1] === 'view') {
25
+ return scenario === 'pr-view-non-json' ? 'not json — gh glitch' : JSON.stringify({ url: PR_URL, number: 1 });
26
+ }
27
+ throw new Error(`unexpected gh call: ${args.join(' ')}`);
28
+ }
29
+ /** Single-scenario fake `gh`, recording argv into `calls`. (Default harness gh = `happy`.) */
30
+ export function createGhEmulator(calls, scenario = 'happy') {
31
+ return (args) => {
32
+ calls.push(args);
33
+ return ghBehavior(scenario, args);
34
+ };
35
+ }
36
+ /**
37
+ * Per-run fake `gh`: routes to a scenario by the feature branch (`feat/<taskId>-…`) present in the
38
+ * gh argv, so one shared harness can drive many runs with different gh outcomes. Register a run's
39
+ * scenario in `scenarios` (keyed by taskId) before starting it; unregistered runs get `happy`.
40
+ */
41
+ export function routedGhEmulator(scenarios, calls) {
42
+ return (args) => {
43
+ calls.push(args);
44
+ const taskId = [...scenarios.keys()].find((id) => args.some((a) => a.startsWith(`feat/${id}-`)));
45
+ const scenario = (taskId !== undefined ? scenarios.get(taskId) : undefined) ?? 'happy';
46
+ return ghBehavior(scenario, args);
47
+ };
48
+ }
49
+ //# sourceMappingURL=gh-emulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh-emulator.js","sourceRoot":"","sources":["../../../src/e2e/kit/gh-emulator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ /** Run a real `git` command in `cwd` and return stdout. */
6
+ export function git(cwd, args) {
7
+ return execFileSync('git', args, {
8
+ encoding: 'utf8',
9
+ cwd,
10
+ timeout: 60_000,
11
+ stdio: ['ignore', 'pipe', 'pipe'],
12
+ });
13
+ }
14
+ // worktree → URL reported for `git remote get-url origin`. Default: a synthetic GitHub URL (so the
15
+ // integrator derives a parseable owner/repo without a real github remote). A target repo can register
16
+ // a non-github URL to exercise the "no parseable github remote" path (D8).
17
+ const reportedRemote = new Map();
18
+ function setReportedRemote(worktree, url) {
19
+ reportedRemote.set(worktree, url);
20
+ try {
21
+ reportedRemote.set(realpathSync(worktree), url); // cwd may arrive realpath-canonicalized
22
+ }
23
+ catch {
24
+ /* worktree exists here; ignore */
25
+ }
26
+ }
27
+ /**
28
+ * `ExecFn` for the integrator: real git, except `remote get-url origin` returns the URL registered
29
+ * for the cwd (default: a synthetic GitHub URL) so owner/repo parsing works without a real remote.
30
+ */
31
+ export const execGit = (args, cwd) => {
32
+ if (args[0] === 'remote' && args[1] === 'get-url' && args[2] === 'origin') {
33
+ return `${reportedRemote.get(cwd) ?? 'git@github.com:e2e/repo.git'}\n`;
34
+ }
35
+ return execFileSync('git', args, {
36
+ encoding: 'utf8',
37
+ cwd,
38
+ timeout: 60_000,
39
+ stdio: ['ignore', 'pipe', 'pipe'],
40
+ });
41
+ };
42
+ /**
43
+ * Create a throwaway bare+worktree git repo with one initial commit pushed to `origin/master`.
44
+ * Pass a {@link TargetRepoState} to leave it in a state that fails the integrator's live preflight.
45
+ */
46
+ export function createTargetRepo(state = {}) {
47
+ const root = mkdtempSync(join(tmpdir(), 'revo-e2e-target-'));
48
+ const origin = join(root, 'origin.git');
49
+ const worktree = join(root, 'worktree');
50
+ git(root, ['init', '--bare', '--initial-branch=master', origin]);
51
+ git(root, ['init', '--initial-branch=master', worktree]);
52
+ git(worktree, ['config', 'user.name', 'Revo E2E']);
53
+ git(worktree, ['config', 'user.email', 'revo-e2e@example.test']);
54
+ writeFileSync(join(worktree, 'README.md'), '# e2e target\n');
55
+ git(worktree, ['add', 'README.md']);
56
+ git(worktree, ['commit', '-m', 'init']);
57
+ git(worktree, ['remote', 'add', 'origin', origin]);
58
+ if (!state.baseMissing) {
59
+ git(worktree, ['push', '-u', 'origin', 'master']); // origin/master exists for the happy path
60
+ }
61
+ if (state.nonGithubRemote) {
62
+ setReportedRemote(worktree, 'https://gitlab.example.test/owner/repo.git');
63
+ }
64
+ if (state.staleBranch) {
65
+ git(worktree, ['switch', '-c', 'stale-feature']); // branch at the initial commit
66
+ git(worktree, ['switch', 'master']);
67
+ writeFileSync(join(worktree, 'moved.txt'), 'master advanced\n');
68
+ git(worktree, ['add', 'moved.txt']);
69
+ git(worktree, ['commit', '-m', 'advance master']);
70
+ git(worktree, ['push', 'origin', 'master']); // origin/master now ahead of stale-feature
71
+ git(worktree, ['switch', 'stale-feature']); // worktree sits on the stale branch
72
+ }
73
+ else if (state.baseAhead) {
74
+ writeFileSync(join(worktree, 'ahead.txt'), 'local-only\n');
75
+ git(worktree, ['add', 'ahead.txt']);
76
+ git(worktree, ['commit', '-m', 'local-only commit (not pushed)']); // local master ahead of origin
77
+ }
78
+ if (state.dirty) {
79
+ writeFileSync(join(worktree, 'dirty.txt'), 'uncommitted change\n'); // untracked → porcelain dirty
80
+ }
81
+ return { root, worktree, cleanup: () => rmSync(root, { recursive: true, force: true }) };
82
+ }
83
+ //# sourceMappingURL=git-target-repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-target-repo.js","sourceRoot":"","sources":["../../../src/e2e/kit/git-target-repo.ts"],"names":[],"mappings":"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"}