pipeline-worker 0.1.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 (74) hide show
  1. package/.env.example +27 -0
  2. package/.pipeline-worker.yml.example +24 -0
  3. package/LICENSE +21 -0
  4. package/README.md +91 -0
  5. package/dist/agent/claude.d.ts +8 -0
  6. package/dist/agent/claude.js +40 -0
  7. package/dist/agent/claude.js.map +1 -0
  8. package/dist/agent/copilot.d.ts +13 -0
  9. package/dist/agent/copilot.js +42 -0
  10. package/dist/agent/copilot.js.map +1 -0
  11. package/dist/agent/index.d.ts +7 -0
  12. package/dist/agent/index.js +15 -0
  13. package/dist/agent/index.js.map +1 -0
  14. package/dist/agent/types.d.ts +22 -0
  15. package/dist/agent/types.js +3 -0
  16. package/dist/agent/types.js.map +1 -0
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +77 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/config/detectChecks.d.ts +14 -0
  21. package/dist/config/detectChecks.js +53 -0
  22. package/dist/config/detectChecks.js.map +1 -0
  23. package/dist/config/loader.d.ts +11 -0
  24. package/dist/config/loader.js +98 -0
  25. package/dist/config/loader.js.map +1 -0
  26. package/dist/forge/github.d.ts +30 -0
  27. package/dist/forge/github.js +134 -0
  28. package/dist/forge/github.js.map +1 -0
  29. package/dist/forge/gitlab.d.ts +15 -0
  30. package/dist/forge/gitlab.js +99 -0
  31. package/dist/forge/gitlab.js.map +1 -0
  32. package/dist/forge/index.d.ts +4 -0
  33. package/dist/forge/index.js +12 -0
  34. package/dist/forge/index.js.map +1 -0
  35. package/dist/forge/types.d.ts +25 -0
  36. package/dist/forge/types.js +7 -0
  37. package/dist/forge/types.js.map +1 -0
  38. package/dist/git/commit.d.ts +8 -0
  39. package/dist/git/commit.js +27 -0
  40. package/dist/git/commit.js.map +1 -0
  41. package/dist/git/diff.d.ts +11 -0
  42. package/dist/git/diff.js +22 -0
  43. package/dist/git/diff.js.map +1 -0
  44. package/dist/git/worktree.d.ts +21 -0
  45. package/dist/git/worktree.js +75 -0
  46. package/dist/git/worktree.js.map +1 -0
  47. package/dist/mcp/server.d.ts +11 -0
  48. package/dist/mcp/server.js +109 -0
  49. package/dist/mcp/server.js.map +1 -0
  50. package/dist/state/runState.d.ts +9 -0
  51. package/dist/state/runState.js +37 -0
  52. package/dist/state/runState.js.map +1 -0
  53. package/dist/toon/envelope.d.ts +21 -0
  54. package/dist/toon/envelope.js +36 -0
  55. package/dist/toon/envelope.js.map +1 -0
  56. package/dist/types.d.ts +60 -0
  57. package/dist/types.js +3 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/workflow/captureIntent.d.ts +4 -0
  60. package/dist/workflow/captureIntent.js +35 -0
  61. package/dist/workflow/captureIntent.js.map +1 -0
  62. package/dist/workflow/openMergeRequest.d.ts +4 -0
  63. package/dist/workflow/openMergeRequest.js +20 -0
  64. package/dist/workflow/openMergeRequest.js.map +1 -0
  65. package/dist/workflow/orchestrate.d.ts +2 -0
  66. package/dist/workflow/orchestrate.js +82 -0
  67. package/dist/workflow/orchestrate.js.map +1 -0
  68. package/dist/workflow/runChecks.d.ts +8 -0
  69. package/dist/workflow/runChecks.js +47 -0
  70. package/dist/workflow/runChecks.js.map +1 -0
  71. package/dist/workflow/watchPipeline.d.ts +13 -0
  72. package/dist/workflow/watchPipeline.js +109 -0
  73. package/dist/workflow/watchPipeline.js.map +1 -0
  74. package/package.json +65 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * GitHub REST ForgeClient. Maps GitHub concepts onto the forge-neutral
3
+ * shapes: pull request -> MergeRequest (iid = PR number), the set of Actions
4
+ * workflow runs for the PR's head SHA -> one aggregated Pipeline, workflow
5
+ * jobs -> PipelineJob. The token is deliberately sourced only from an
6
+ * environment variable — never from `.pipeline-worker.yml`.
7
+ */
8
+ import type { PipelineWorkerConfig, Pipeline } from '../types.js';
9
+ import type { ForgeClient } from './types.js';
10
+ export interface GithubAuth {
11
+ apiUrl: string;
12
+ /** "owner/name" slug. */
13
+ repo: string;
14
+ token: string;
15
+ }
16
+ export declare function resolveGithubAuth(config: PipelineWorkerConfig): GithubAuth;
17
+ export interface WorkflowRun {
18
+ id: number;
19
+ status: string;
20
+ conclusion: string | null;
21
+ html_url: string;
22
+ }
23
+ /**
24
+ * Collapses all workflow runs for one head SHA into a single Pipeline.
25
+ * Priority: any run still running -> running (wait for the full picture);
26
+ * else any failed -> failed (its id is what getFailedJobs needs); else any
27
+ * canceled -> canceled; else success unless every run was skipped.
28
+ */
29
+ export declare function aggregateRuns(runs: WorkflowRun[]): Pipeline | undefined;
30
+ export declare function createGithubForge(config: PipelineWorkerConfig): ForgeClient;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * GitHub REST ForgeClient. Maps GitHub concepts onto the forge-neutral
3
+ * shapes: pull request -> MergeRequest (iid = PR number), the set of Actions
4
+ * workflow runs for the PR's head SHA -> one aggregated Pipeline, workflow
5
+ * jobs -> PipelineJob. The token is deliberately sourced only from an
6
+ * environment variable — never from `.pipeline-worker.yml`.
7
+ */
8
+ export function resolveGithubAuth(config) {
9
+ const apiUrl = process.env.PIPELINE_WORKER_GITHUB_API_URL || 'https://api.github.com';
10
+ const repo = process.env.PIPELINE_WORKER_GITHUB_REPO || config.github.repo;
11
+ const token = process.env.PIPELINE_WORKER_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
12
+ if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) {
13
+ throw new Error('GitHub repo is not configured (set github.repo to "owner/name" in .pipeline-worker.yml or PIPELINE_WORKER_GITHUB_REPO).');
14
+ }
15
+ if (!token)
16
+ throw new Error('PIPELINE_WORKER_GITHUB_TOKEN (or GITHUB_TOKEN) environment variable is not set.');
17
+ return { apiUrl: apiUrl.replace(/\/$/, ''), repo, token };
18
+ }
19
+ async function githubRequest(auth, path, init) {
20
+ const res = await fetch(`${auth.apiUrl}/repos/${auth.repo}${path}`, {
21
+ ...init,
22
+ headers: {
23
+ Authorization: `Bearer ${auth.token}`,
24
+ Accept: 'application/vnd.github+json',
25
+ 'X-GitHub-Api-Version': '2022-11-28',
26
+ 'Content-Type': 'application/json',
27
+ ...(init?.headers ?? {}),
28
+ },
29
+ });
30
+ if (!res.ok) {
31
+ const body = await res.text().catch(() => '');
32
+ throw new Error(`GitHub API ${init?.method ?? 'GET'} ${path} failed: ${res.status} ${res.statusText} — ${body}`);
33
+ }
34
+ return res;
35
+ }
36
+ function toMergeRequest(raw) {
37
+ return {
38
+ iid: raw.number,
39
+ webUrl: raw.html_url,
40
+ sourceBranch: raw.head.ref,
41
+ targetBranch: raw.base.ref,
42
+ state: raw.state,
43
+ };
44
+ }
45
+ function runStatus(run) {
46
+ if (run.status !== 'completed')
47
+ return 'running';
48
+ switch (run.conclusion) {
49
+ case 'success':
50
+ return 'success';
51
+ case 'cancelled':
52
+ case 'stale':
53
+ return 'canceled';
54
+ case 'skipped':
55
+ case 'neutral':
56
+ return 'skipped';
57
+ default: // failure, timed_out, startup_failure, action_required, …
58
+ return 'failed';
59
+ }
60
+ }
61
+ /**
62
+ * Collapses all workflow runs for one head SHA into a single Pipeline.
63
+ * Priority: any run still running -> running (wait for the full picture);
64
+ * else any failed -> failed (its id is what getFailedJobs needs); else any
65
+ * canceled -> canceled; else success unless every run was skipped.
66
+ */
67
+ export function aggregateRuns(runs) {
68
+ if (runs.length === 0)
69
+ return undefined;
70
+ const byStatus = (wanted) => runs.find((run) => runStatus(run) === wanted);
71
+ const pick = byStatus('running') ?? byStatus('failed') ?? byStatus('canceled') ?? byStatus('success') ?? runs[0];
72
+ return { id: pick.id, status: runStatus(pick), webUrl: pick.html_url };
73
+ }
74
+ function isFailedConclusion(conclusion) {
75
+ return conclusion !== null && !['success', 'skipped', 'neutral'].includes(conclusion);
76
+ }
77
+ export function createGithubForge(config) {
78
+ const auth = resolveGithubAuth(config);
79
+ const owner = auth.repo.split('/')[0];
80
+ return {
81
+ async findExistingMr(sourceBranch) {
82
+ const res = await githubRequest(auth, `/pulls?head=${encodeURIComponent(`${owner}:${sourceBranch}`)}&state=open`);
83
+ const list = (await res.json());
84
+ return list.length > 0 ? toMergeRequest(list[0]) : undefined;
85
+ },
86
+ async createMergeRequest(args) {
87
+ const res = await githubRequest(auth, '/pulls', {
88
+ method: 'POST',
89
+ body: JSON.stringify({
90
+ title: args.title,
91
+ body: args.description,
92
+ head: args.sourceBranch,
93
+ base: args.targetBranch,
94
+ }),
95
+ });
96
+ return toMergeRequest(await res.json());
97
+ },
98
+ async getMrPipelines(mrIid) {
99
+ const prRes = await githubRequest(auth, `/pulls/${mrIid}`);
100
+ const pr = (await prRes.json());
101
+ const runsRes = await githubRequest(auth, `/actions/runs?head_sha=${pr.head.sha}&per_page=100`);
102
+ const { workflow_runs: runs } = (await runsRes.json());
103
+ const aggregate = aggregateRuns(runs);
104
+ return aggregate ? [aggregate] : [];
105
+ },
106
+ async getFailedJobs(pipelineId) {
107
+ const res = await githubRequest(auth, `/actions/runs/${pipelineId}/jobs?filter=latest&per_page=100`);
108
+ const { jobs } = (await res.json());
109
+ return jobs
110
+ .filter((job) => isFailedConclusion(job.conclusion))
111
+ .map((job) => ({ id: job.id, name: job.name, stage: job.workflow_name ?? 'workflow' }));
112
+ },
113
+ async getJobLog(jobId) {
114
+ // Redirects to short-lived blob storage; fetch follows it and drops the auth header cross-origin.
115
+ const res = await githubRequest(auth, `/actions/jobs/${jobId}/logs`);
116
+ return res.text();
117
+ },
118
+ async retryPipeline(pipelineId) {
119
+ await githubRequest(auth, `/actions/runs/${pipelineId}/rerun-failed-jobs`, { method: 'POST' });
120
+ const res = await githubRequest(auth, `/actions/runs/${pipelineId}`);
121
+ const run = (await res.json());
122
+ return { id: run.id, status: runStatus(run), webUrl: run.html_url };
123
+ },
124
+ async createMrNote(mrIid, body) {
125
+ const res = await githubRequest(auth, `/issues/${mrIid}/comments`, {
126
+ method: 'POST',
127
+ body: JSON.stringify({ body }),
128
+ });
129
+ const note = (await res.json());
130
+ return { id: note.id };
131
+ },
132
+ };
133
+ }
134
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/forge/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * GitLab REST (v4) ForgeClient. The token is deliberately sourced only from
3
+ * an environment variable — it must never live in `.pipeline-worker.yml` or be
4
+ * logged. Used both by the workflow orchestrator and the MCP tool handlers,
5
+ * so there is exactly one place that knows how to talk to GitLab.
6
+ */
7
+ import type { PipelineWorkerConfig } from '../types.js';
8
+ import type { ForgeClient } from './types.js';
9
+ export interface GitlabAuth {
10
+ host: string;
11
+ projectId: number;
12
+ token: string;
13
+ }
14
+ export declare function resolveGitlabAuth(config: PipelineWorkerConfig): GitlabAuth;
15
+ export declare function createGitlabForge(config: PipelineWorkerConfig): ForgeClient;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * GitLab REST (v4) ForgeClient. The token is deliberately sourced only from
3
+ * an environment variable — it must never live in `.pipeline-worker.yml` or be
4
+ * logged. Used both by the workflow orchestrator and the MCP tool handlers,
5
+ * so there is exactly one place that knows how to talk to GitLab.
6
+ */
7
+ export function resolveGitlabAuth(config) {
8
+ const host = process.env.PIPELINE_WORKER_GITLAB_HOST || config.gitlab.host;
9
+ const projectIdEnv = process.env.PIPELINE_WORKER_GITLAB_PROJECT_ID;
10
+ const projectId = projectIdEnv ? Number(projectIdEnv) : config.gitlab.projectId;
11
+ const token = process.env.PIPELINE_WORKER_GITLAB_TOKEN;
12
+ if (!host)
13
+ throw new Error('GitLab host is not configured (set gitlab.host in .pipeline-worker.yml or PIPELINE_WORKER_GITLAB_HOST).');
14
+ if (!projectId)
15
+ throw new Error('GitLab projectId is not configured (set gitlab.projectId in .pipeline-worker.yml or PIPELINE_WORKER_GITLAB_PROJECT_ID).');
16
+ if (!token)
17
+ throw new Error('PIPELINE_WORKER_GITLAB_TOKEN environment variable is not set.');
18
+ return { host, projectId, token };
19
+ }
20
+ async function gitlabRequest(auth, path, init) {
21
+ const url = `${auth.host.replace(/\/$/, '')}/api/v4/projects/${auth.projectId}${path}`;
22
+ const res = await fetch(url, {
23
+ ...init,
24
+ headers: {
25
+ 'PRIVATE-TOKEN': auth.token,
26
+ 'Content-Type': 'application/json',
27
+ ...(init?.headers ?? {}),
28
+ },
29
+ });
30
+ if (!res.ok) {
31
+ const body = await res.text().catch(() => '');
32
+ throw new Error(`GitLab API ${init?.method ?? 'GET'} ${path} failed: ${res.status} ${res.statusText} — ${body}`);
33
+ }
34
+ return res;
35
+ }
36
+ function toMergeRequest(raw) {
37
+ return {
38
+ iid: raw.iid,
39
+ webUrl: raw.web_url,
40
+ sourceBranch: raw.source_branch,
41
+ targetBranch: raw.target_branch,
42
+ state: raw.state,
43
+ };
44
+ }
45
+ function toPipeline(raw) {
46
+ return { id: raw.id, status: raw.status, webUrl: raw.web_url };
47
+ }
48
+ function toPipelineJob(raw) {
49
+ return { id: raw.id, name: raw.name, stage: raw.stage };
50
+ }
51
+ export function createGitlabForge(config) {
52
+ const auth = resolveGitlabAuth(config);
53
+ return {
54
+ async findExistingMr(sourceBranch) {
55
+ const res = await gitlabRequest(auth, `/merge_requests?source_branch=${encodeURIComponent(sourceBranch)}&state=opened`);
56
+ const list = (await res.json());
57
+ return list.length > 0 ? toMergeRequest(list[0]) : undefined;
58
+ },
59
+ async createMergeRequest(args) {
60
+ const res = await gitlabRequest(auth, '/merge_requests', {
61
+ method: 'POST',
62
+ body: JSON.stringify({
63
+ source_branch: args.sourceBranch,
64
+ target_branch: args.targetBranch,
65
+ title: args.title,
66
+ description: args.description,
67
+ }),
68
+ });
69
+ return toMergeRequest(await res.json());
70
+ },
71
+ async getMrPipelines(mrIid) {
72
+ const res = await gitlabRequest(auth, `/merge_requests/${mrIid}/pipelines`);
73
+ const list = (await res.json());
74
+ return list.map(toPipeline);
75
+ },
76
+ async getFailedJobs(pipelineId) {
77
+ const res = await gitlabRequest(auth, `/pipelines/${pipelineId}/jobs?scope[]=failed`);
78
+ const list = (await res.json());
79
+ return list.map(toPipelineJob);
80
+ },
81
+ async getJobLog(jobId) {
82
+ const res = await gitlabRequest(auth, `/jobs/${jobId}/trace`);
83
+ return res.text();
84
+ },
85
+ async retryPipeline(pipelineId) {
86
+ const res = await gitlabRequest(auth, `/pipelines/${pipelineId}/retry`, { method: 'POST' });
87
+ return toPipeline(await res.json());
88
+ },
89
+ async createMrNote(mrIid, body) {
90
+ const res = await gitlabRequest(auth, `/merge_requests/${mrIid}/notes`, {
91
+ method: 'POST',
92
+ body: JSON.stringify({ body }),
93
+ });
94
+ const note = (await res.json());
95
+ return { id: note.id };
96
+ },
97
+ };
98
+ }
99
+ //# sourceMappingURL=gitlab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab.js","sourceRoot":"","sources":["../../src/forge/gitlab.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { PipelineWorkerConfig } from '../types.js';
2
+ import type { ForgeClient } from './types.js';
3
+ /** Picks the forge named in config (.pipeline-worker.yml / PIPELINE_WORKER_FORGE). No runtime fallback. */
4
+ export declare function createForge(config: PipelineWorkerConfig): ForgeClient;
@@ -0,0 +1,12 @@
1
+ import { createGitlabForge } from './gitlab.js';
2
+ import { createGithubForge } from './github.js';
3
+ /** Picks the forge named in config (.pipeline-worker.yml / PIPELINE_WORKER_FORGE). No runtime fallback. */
4
+ export function createForge(config) {
5
+ switch (config.forge) {
6
+ case 'gitlab':
7
+ return createGitlabForge(config);
8
+ case 'github':
9
+ return createGithubForge(config);
10
+ }
11
+ }
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/forge/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,2GAA2G;AAC3G,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,QAAQ,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACnC,KAAK,QAAQ;YACX,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Forge-neutral client interface. "MR" naming follows GitLab; the GitHub
3
+ * implementation maps pull requests / workflow runs / jobs onto the same
4
+ * shapes so the workflow and MCP server never branch on the forge.
5
+ */
6
+ import type { MergeRequest, Pipeline, PipelineJob } from '../types.js';
7
+ export interface CreateMrArgs {
8
+ sourceBranch: string;
9
+ targetBranch: string;
10
+ title: string;
11
+ description: string;
12
+ }
13
+ export interface ForgeClient {
14
+ /** Idempotency check: finds an already-open MR/PR for this branch, if any. */
15
+ findExistingMr(sourceBranch: string): Promise<MergeRequest | undefined>;
16
+ createMergeRequest(args: CreateMrArgs): Promise<MergeRequest>;
17
+ /** Latest-first pipelines (GitHub: workflow runs aggregated into one) for an MR/PR. */
18
+ getMrPipelines(mrIid: number): Promise<Pipeline[]>;
19
+ getFailedJobs(pipelineId: number): Promise<PipelineJob[]>;
20
+ getJobLog(jobId: number): Promise<string>;
21
+ retryPipeline(pipelineId: number): Promise<Pipeline>;
22
+ createMrNote(mrIid: number, body: string): Promise<{
23
+ id: number;
24
+ }>;
25
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Forge-neutral client interface. "MR" naming follows GitLab; the GitHub
3
+ * implementation maps pull requests / workflow runs / jobs onto the same
4
+ * shapes so the workflow and MCP server never branch on the forge.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/forge/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,8 @@
1
+ /** Thin git plumbing wrappers used against the isolated worktree. */
2
+ export declare function stageAll(worktreePath: string): Promise<void>;
3
+ export declare function commit(worktreePath: string, message: string): Promise<void>;
4
+ export declare function push(worktreePath: string, remote: string, branch: string): Promise<void>;
5
+ /** True when the worktree has staged, unstaged, or untracked changes. */
6
+ export declare function hasChanges(worktreePath: string): Promise<boolean>;
7
+ export declare function currentSha(worktreePath: string): Promise<string>;
8
+ export declare function currentBranch(cwd: string): Promise<string>;
@@ -0,0 +1,27 @@
1
+ /** Thin git plumbing wrappers used against the isolated worktree. */
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ const execFileAsync = promisify(execFile);
5
+ export async function stageAll(worktreePath) {
6
+ await execFileAsync('git', ['add', '-A'], { cwd: worktreePath });
7
+ }
8
+ export async function commit(worktreePath, message) {
9
+ await execFileAsync('git', ['commit', '-m', message], { cwd: worktreePath });
10
+ }
11
+ export async function push(worktreePath, remote, branch) {
12
+ await execFileAsync('git', ['push', '--set-upstream', remote, branch], { cwd: worktreePath });
13
+ }
14
+ /** True when the worktree has staged, unstaged, or untracked changes. */
15
+ export async function hasChanges(worktreePath) {
16
+ const { stdout } = await execFileAsync('git', ['status', '--porcelain'], { cwd: worktreePath });
17
+ return stdout.trim().length > 0;
18
+ }
19
+ export async function currentSha(worktreePath) {
20
+ const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: worktreePath });
21
+ return stdout.trim();
22
+ }
23
+ export async function currentBranch(cwd) {
24
+ const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
25
+ return stdout.trim();
26
+ }
27
+ //# sourceMappingURL=commit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../src/git/commit.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,YAAoB;IACjD,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,YAAoB,EAAE,OAAe;IAChE,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,MAAc;IAC7E,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;AAChG,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,YAAoB;IACnD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IAChG,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,YAAoB;IACnD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5F,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9F,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** Reads the caller's in-progress change set without touching their working tree. */
2
+ export interface CapturedDiff {
3
+ diffText: string;
4
+ untrackedFiles: string[];
5
+ }
6
+ /**
7
+ * Captures staged+unstaged changes (`git diff HEAD`) plus the list of
8
+ * untracked files (which `git diff` never includes) so both can be carried
9
+ * into an isolated worktree.
10
+ */
11
+ export declare function captureDiff(repoRoot: string): Promise<CapturedDiff>;
@@ -0,0 +1,22 @@
1
+ /** Reads the caller's in-progress change set without touching their working tree. */
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ const execFileAsync = promisify(execFile);
5
+ /**
6
+ * Captures staged+unstaged changes (`git diff HEAD`) plus the list of
7
+ * untracked files (which `git diff` never includes) so both can be carried
8
+ * into an isolated worktree.
9
+ */
10
+ export async function captureDiff(repoRoot) {
11
+ const { stdout: diffText } = await execFileAsync('git', ['diff', 'HEAD'], {
12
+ cwd: repoRoot,
13
+ maxBuffer: 64 * 1024 * 1024,
14
+ });
15
+ const { stdout: statusOut } = await execFileAsync('git', ['status', '--porcelain'], { cwd: repoRoot });
16
+ const untrackedFiles = statusOut
17
+ .split('\n')
18
+ .filter((line) => line.startsWith('?? '))
19
+ .map((line) => line.slice(3).trim());
20
+ return { diffText, untrackedFiles };
21
+ }
22
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/git/diff.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAO1C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB;IAChD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;QACxE,GAAG,EAAE,QAAQ;QACb,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvG,MAAM,cAAc,GAAG,SAAS;SAC7B,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAEvC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Isolated git worktree management. The caller's own working directory is
3
+ * never touched — all workflow steps run inside a disposable worktree that
4
+ * is always removed afterward (see workflow/orchestrate.ts's finally block).
5
+ */
6
+ export declare function generateTempBranchName(): string;
7
+ /** Creates a new worktree off HEAD on a fresh branch, returning its path. */
8
+ export declare function createWorktree(repoRoot: string, branchName: string): Promise<string>;
9
+ /**
10
+ * Applies a captured diff (staged+unstaged) and copies untracked files into
11
+ * the worktree, so it ends up with exactly the caller's original change set.
12
+ */
13
+ export declare function applyDiffToWorktree(worktreePath: string, diffText: string, untrackedFiles: string[], repoRoot: string): Promise<void>;
14
+ /** Renames the worktree's current branch (used once the agent has proposed a real name). */
15
+ export declare function renameBranch(worktreePath: string, newBranchName: string): Promise<void>;
16
+ /**
17
+ * Removes the worktree and the mkdtemp parent directory createWorktree put it
18
+ * in. Tolerates its own failures (logs + continues) so a cleanup problem
19
+ * never masks the real error from the workflow that called it.
20
+ */
21
+ export declare function removeWorktree(repoRoot: string, worktreePath: string): Promise<void>;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Isolated git worktree management. The caller's own working directory is
3
+ * never touched — all workflow steps run inside a disposable worktree that
4
+ * is always removed afterward (see workflow/orchestrate.ts's finally block).
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { promisify } from 'node:util';
8
+ import { mkdtempSync, mkdirSync, cpSync, writeFileSync, unlinkSync, rmSync } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import { randomUUID } from 'node:crypto';
11
+ import { join, dirname } from 'node:path';
12
+ const execFileAsync = promisify(execFile);
13
+ export function generateTempBranchName() {
14
+ return `pipeline-worker/tmp-${randomUUID().slice(0, 8)}`;
15
+ }
16
+ /** Creates a new worktree off HEAD on a fresh branch, returning its path. */
17
+ export async function createWorktree(repoRoot, branchName) {
18
+ const parentDir = mkdtempSync(join(tmpdir(), 'pipeline-worker-'));
19
+ const worktreePath = join(parentDir, 'worktree');
20
+ await execFileAsync('git', ['worktree', 'add', '-b', branchName, worktreePath, 'HEAD'], { cwd: repoRoot });
21
+ return worktreePath;
22
+ }
23
+ /**
24
+ * Applies a captured diff (staged+unstaged) and copies untracked files into
25
+ * the worktree, so it ends up with exactly the caller's original change set.
26
+ */
27
+ export async function applyDiffToWorktree(worktreePath, diffText, untrackedFiles, repoRoot) {
28
+ if (diffText.trim().length > 0) {
29
+ const diffFile = join(tmpdir(), `pipeline-worker-diff-${randomUUID()}.patch`);
30
+ writeFileSync(diffFile, diffText, 'utf-8');
31
+ try {
32
+ await execFileAsync('git', ['apply', '--index', diffFile], { cwd: worktreePath });
33
+ }
34
+ finally {
35
+ unlinkSync(diffFile);
36
+ }
37
+ }
38
+ for (const relativePath of untrackedFiles) {
39
+ const src = join(repoRoot, relativePath);
40
+ const dest = join(worktreePath, relativePath);
41
+ mkdirSync(dirname(dest), { recursive: true });
42
+ cpSync(src, dest, { recursive: true });
43
+ }
44
+ if (untrackedFiles.length > 0) {
45
+ await execFileAsync('git', ['add', '-A'], { cwd: worktreePath });
46
+ }
47
+ }
48
+ /** Renames the worktree's current branch (used once the agent has proposed a real name). */
49
+ export async function renameBranch(worktreePath, newBranchName) {
50
+ await execFileAsync('git', ['branch', '-m', newBranchName], { cwd: worktreePath });
51
+ }
52
+ /**
53
+ * Removes the worktree and the mkdtemp parent directory createWorktree put it
54
+ * in. Tolerates its own failures (logs + continues) so a cleanup problem
55
+ * never masks the real error from the workflow that called it.
56
+ */
57
+ export async function removeWorktree(repoRoot, worktreePath) {
58
+ try {
59
+ await execFileAsync('git', ['worktree', 'remove', '--force', worktreePath], { cwd: repoRoot });
60
+ }
61
+ catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ console.error(`Warning: failed to remove worktree ${worktreePath}: ${message}`);
64
+ }
65
+ // createWorktree puts worktreePath inside a dedicated mkdtemp parent dir
66
+ // (<tmp>/pipeline-worker-XXXX/worktree) precisely so it can be discarded wholesale here.
67
+ try {
68
+ rmSync(dirname(worktreePath), { recursive: true, force: true });
69
+ }
70
+ catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ console.error(`Warning: failed to remove temp dir for worktree ${worktreePath}: ${message}`);
73
+ }
74
+ }
75
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/git/worktree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Custom forge MCP server (GitLab or GitHub, per config) using stdio
3
+ * transport. Exposes the minimum tool set an agent needs to drive steps 7-8
4
+ * of the pipeline-worker workflow (open an MR/PR, watch its pipeline, inspect
5
+ * failures, leave a human-escalation note). Every tool response is
6
+ * TOON-encoded via toon/envelope.ts to minimize the tokens an agent spends
7
+ * reading forge state.
8
+ */
9
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ export declare function createServer(): Promise<McpServer>;
11
+ export declare function startServer(): Promise<void>;