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,109 @@
1
+ /**
2
+ * Step 8: poll the MR/PR's pipeline (at config.pollIntervalSeconds) until it
3
+ * succeeds; on failure, hand the failing jobs' logs to the configured agent
4
+ * (with pipeline-worker's own forge MCP server available so it can pull further
5
+ * detail itself), commit the fix, push, and retry — capped at
6
+ * config.maxFixAttempts before escalating via an MR comment. Never retries
7
+ * indefinitely, and never spends agent tokens on pipelines that are not
8
+ * actually failed (canceled/skipped go straight to a human).
9
+ */
10
+ import { writeFileSync, unlinkSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { randomUUID } from 'node:crypto';
13
+ import { tmpdir } from 'node:os';
14
+ import { stageAll, commit, push, hasChanges } from '../git/commit.js';
15
+ import { saveRunState } from '../state/runState.js';
16
+ const MAX_POLL_WINDOW_MS = 2 * 60 * 60 * 1000; // per pipeline attempt, as a safety net
17
+ const TERMINAL_STATUSES = ['success', 'failed', 'canceled', 'skipped'];
18
+ function sleep(ms) {
19
+ return new Promise((resolve) => setTimeout(resolve, ms));
20
+ }
21
+ /**
22
+ * Waits for a *new* terminal pipeline: `previousPipelineId` is the one we
23
+ * already handled, and it stays "latest" on the forge until the pipeline for
24
+ * our fix push is created — without this, the loop would re-fix stale logs
25
+ * and burn attempts on a single real failure.
26
+ */
27
+ async function pollLatestPipeline(forge, mrIid, intervalMs, previousPipelineId) {
28
+ const maxPolls = Math.max(1, Math.ceil(MAX_POLL_WINDOW_MS / intervalMs));
29
+ for (let i = 0; i < maxPolls; i++) {
30
+ const pipelines = await forge.getMrPipelines(mrIid);
31
+ const latest = pipelines[0];
32
+ if (latest && latest.id !== previousPipelineId && TERMINAL_STATUSES.includes(latest.status)) {
33
+ return latest;
34
+ }
35
+ await sleep(intervalMs);
36
+ }
37
+ throw new Error(`Pipeline for MR ${mrIid} did not reach a terminal state within the polling window`);
38
+ }
39
+ function writeAgentMcpConfig() {
40
+ const path = join(tmpdir(), `pipeline-worker-mcp-${randomUUID()}.json`);
41
+ const config = { mcpServers: { 'pipeline-worker-forge': { type: 'stdio', command: 'npx', args: ['pipeline-worker', 'serve'], env: {} } } };
42
+ writeFileSync(path, JSON.stringify(config), 'utf-8');
43
+ return path;
44
+ }
45
+ function buildFixPrompt(pipeline, jobs) {
46
+ const jobSummaries = jobs
47
+ .map(({ job, log }) => `### Job "${job.name}" (stage: ${job.stage})\n\`\`\`\n${log.slice(-4000)}\n\`\`\``)
48
+ .join('\n\n');
49
+ return (`The CI pipeline ${pipeline.webUrl} failed. Fix the underlying issue in this worktree so the pipeline passes. ` +
50
+ 'You have access to the pipeline-worker-forge MCP server for further pipeline/job detail if these excerpts are not enough.\n\n' +
51
+ jobSummaries);
52
+ }
53
+ async function escalate(forge, mrIid, message, state, repoRoot) {
54
+ await forge.createMrNote(mrIid, message);
55
+ state.phase = 'escalated';
56
+ saveRunState(repoRoot, state);
57
+ }
58
+ export async function watchPipeline(forge, config, agent, worktreePath, branch, mrIid, state, repoRoot) {
59
+ const intervalMs = config.pollIntervalSeconds * 1000;
60
+ state.phase = 'watch';
61
+ saveRunState(repoRoot, state);
62
+ let previousPipelineId;
63
+ for (;;) {
64
+ const pipeline = await pollLatestPipeline(forge, mrIid, intervalMs, previousPipelineId);
65
+ state.pipelineId = pipeline.id;
66
+ saveRunState(repoRoot, state);
67
+ if (pipeline.status === 'success') {
68
+ state.phase = 'done';
69
+ saveRunState(repoRoot, state);
70
+ return;
71
+ }
72
+ if (pipeline.status !== 'failed') {
73
+ // canceled/skipped: there are no failing jobs to fix — don't spend agent tokens.
74
+ await escalate(forge, mrIid, `pipeline-worker: pipeline ${pipeline.webUrl} ended as "${pipeline.status}" — nothing to auto-fix, needs a human decision.`, state, repoRoot);
75
+ return;
76
+ }
77
+ state.attempt += 1;
78
+ saveRunState(repoRoot, state);
79
+ if (state.attempt > config.maxFixAttempts) {
80
+ await escalate(forge, mrIid, `pipeline-worker: automated fix attempts exhausted (${state.attempt - 1} attempts). ` +
81
+ `Pipeline ${pipeline.webUrl} is still failing and needs a human to take over.`, state, repoRoot);
82
+ return;
83
+ }
84
+ const failedJobs = await forge.getFailedJobs(pipeline.id);
85
+ const logs = await Promise.all(failedJobs.map(async (job) => ({ job, log: await forge.getJobLog(job.id) })));
86
+ const mcpConfigPath = writeAgentMcpConfig();
87
+ try {
88
+ await agent.invoke({
89
+ prompt: buildFixPrompt(pipeline, logs),
90
+ cwd: worktreePath,
91
+ mcpConfigPath,
92
+ permissionMode: 'acceptEdits',
93
+ });
94
+ }
95
+ finally {
96
+ unlinkSync(mcpConfigPath);
97
+ }
98
+ if (!(await hasChanges(worktreePath))) {
99
+ // Re-pushing an identical tree would never produce a new pipeline; stop here.
100
+ await escalate(forge, mrIid, `pipeline-worker: fix attempt ${state.attempt} produced no changes for pipeline ${pipeline.webUrl} — escalating to a human.`, state, repoRoot);
101
+ return;
102
+ }
103
+ await stageAll(worktreePath);
104
+ await commit(worktreePath, `fix: address CI failure (attempt ${state.attempt})`);
105
+ await push(worktreePath, 'origin', branch);
106
+ previousPipelineId = pipeline.id;
107
+ }
108
+ }
109
+ //# sourceMappingURL=watchPipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchPipeline.js","sourceRoot":"","sources":["../../src/workflow/watchPipeline.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "pipeline-worker",
3
+ "version": "0.1.0",
4
+ "description": "Automated git-worktree workflow: captures intent from a diff via Claude Code or GitHub Copilot CLI, runs build/lint/test, opens a GitLab MR or GitHub PR, and auto-fixes failing pipelines — with a companion forge MCP server (TOON-encoded responses).",
5
+ "license": "MIT",
6
+ "author": "Mohan TN <mohan.tn100@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MohanTn/pipeline_worker.git"
10
+ },
11
+ "bugs": "https://github.com/MohanTn/pipeline_worker/issues",
12
+ "homepage": "https://github.com/MohanTn/pipeline_worker#readme",
13
+ "keywords": [
14
+ "automation",
15
+ "ci",
16
+ "gitlab",
17
+ "github",
18
+ "merge-request",
19
+ "pull-request",
20
+ "claude",
21
+ "copilot",
22
+ "mcp",
23
+ "agent"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "provenance": true
28
+ },
29
+ "type": "module",
30
+ "main": "dist/mcp/server.js",
31
+ "bin": {
32
+ "pipeline-worker": "dist/cli.js"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE",
38
+ ".pipeline-worker.yml.example",
39
+ ".env.example"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20.12"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.json",
46
+ "lint": "eslint . --ext .ts",
47
+ "test": "node --import tsx --test test/**/*.test.ts",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "dependencies": {
51
+ "@modelcontextprotocol/sdk": "^1.29.0",
52
+ "@toon-format/toon": "^2.3.0",
53
+ "commander": "^12.0.0",
54
+ "js-yaml": "^4.1.0",
55
+ "zod": "^3.25.0"
56
+ },
57
+ "devDependencies": {
58
+ "@eslint/js": "^9.0.0",
59
+ "@types/js-yaml": "^4.0.0",
60
+ "@types/node": "^22.0.0",
61
+ "tsx": "^4.0.0",
62
+ "typescript": "^5.5.0",
63
+ "typescript-eslint": "^8.0.0"
64
+ }
65
+ }