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
package/.env.example ADDED
@@ -0,0 +1,27 @@
1
+ # Copy to .env at your repo root. Values here are defaults only — variables
2
+ # already set in the real environment always take precedence, and .env is
3
+ # loaded automatically by every pipeline-worker command.
4
+ #
5
+ # Tokens belong here or in the real environment — NEVER in .pipeline-worker.yml.
6
+
7
+ # Which coding agent runs intent capture and CI fixes: claude | copilot
8
+ PIPELINE_WORKER_AGENT=claude
9
+
10
+ # Which forge hosts the repo: gitlab | github
11
+ PIPELINE_WORKER_FORGE=gitlab
12
+
13
+ # Seconds between pipeline polls (default 15; use e.g. 60 for slow pipelines)
14
+ PIPELINE_WORKER_POLL_INTERVAL_SECONDS=15
15
+
16
+ # --- GitLab (PIPELINE_WORKER_FORGE=gitlab) ---
17
+ PIPELINE_WORKER_GITLAB_HOST=https://gitlab.example.com
18
+ PIPELINE_WORKER_GITLAB_PROJECT_ID=12345
19
+ PIPELINE_WORKER_GITLAB_TOKEN=
20
+
21
+ # --- GitHub (PIPELINE_WORKER_FORGE=github) ---
22
+ # PIPELINE_WORKER_GITHUB_REPO=owner/name
23
+ # PIPELINE_WORKER_GITHUB_TOKEN= # falls back to GITHUB_TOKEN if unset
24
+ # PIPELINE_WORKER_GITHUB_API_URL=https://api.github.com # override for GitHub Enterprise
25
+
26
+ # Optional: path to the YAML config file (default: <repo>/.pipeline-worker.yml)
27
+ # PIPELINE_WORKER_CONFIG=
@@ -0,0 +1,24 @@
1
+ # Copy to .pipeline-worker.yml at your repo root and adjust. Every value can also be
2
+ # set (or overridden) via environment variables / a .env file — see
3
+ # .env.example. Tokens must ONLY be set as environment variables
4
+ # (PIPELINE_WORKER_GITLAB_TOKEN / PIPELINE_WORKER_GITHUB_TOKEN) — never in this file.
5
+
6
+ agent: claude # or: copilot (no runtime fallback between the two)
7
+ forge: gitlab # or: github
8
+
9
+ gitlab:
10
+ host: https://gitlab.example.com
11
+ projectId: 12345
12
+
13
+ # github:
14
+ # repo: owner/name
15
+
16
+ # Check commands default to the repo's detected toolchain (Node/TypeScript,
17
+ # .NET, Go, Python — see README "Check command auto-detection"). Uncomment to
18
+ # override; an empty string ('') skips that stage.
19
+ # build: npm run build
20
+ # lint: npm run lint
21
+ # test: npm test
22
+
23
+ maxFixAttempts: 5
24
+ pollIntervalSeconds: 15 # per-repo pipeline poll cadence; bump to 60 for slow pipelines
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohan TN and pipeline-worker contributors
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,91 @@
1
+ # pipeline-worker
2
+
3
+ [![CI](https://github.com/MohanTn/pipeline-worker/actions/workflows/ci.yml/badge.svg)](https://github.com/MohanTn/pipeline-worker/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/pipeline-worker)](https://www.npmjs.com/package/pipeline-worker)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Automate the last mile of your local changes: pipeline-worker takes the uncommitted diff in your repo and drives it — unattended — to a green merge request.
8
+
9
+ 1. Captures your staged + unstaged changes (your working tree is never touched).
10
+ 2. Replays them in a disposable git worktree.
11
+ 3. Asks a coding agent (Claude Code or GitHub Copilot CLI) to infer the intent: branch name, commit message, summary.
12
+ 4. Runs your `build` / `lint` / `test` commands, fail-fast.
13
+ 5. Commits, pushes, and opens a GitLab MR or GitHub PR.
14
+ 6. Polls the CI pipeline; on failure it hands the failing job logs to the agent, commits the fix, pushes, and re-polls — capped at `maxFixAttempts` before escalating to a human with an MR comment.
15
+
16
+ Polling is plain REST and costs zero agent tokens; the agent is invoked only when a pipeline actually fails, with truncated logs and a token-efficient [TOON](https://github.com/toon-format/toon)-encoded MCP server for anything more it needs.
17
+
18
+ ## Requirements
19
+
20
+ - Node.js >= 20.12 and git
21
+ - One coding agent CLI on your PATH: [Claude Code](https://claude.com/claude-code) (`claude`) or [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli) (`copilot`)
22
+ - A GitLab or GitHub token with API access to the repo
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install -g pipeline-worker
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```sh
33
+ cd your-repo
34
+ cp $(npm root -g)/pipeline-worker/.env.example .env # set tokens & defaults
35
+ cp $(npm root -g)/pipeline-worker/.pipeline-worker.yml.example .pipeline-worker.yml
36
+ # hack, hack, hack — leave the changes uncommitted, then:
37
+ pipeline-worker
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ Resolution order per value: **real environment variable → `.env` at repo root → `.pipeline-worker.yml` → built-in default.** Tokens must only be set as environment variables (or in `.env`, which is git-ignored) — never in the YAML file.
43
+
44
+ | YAML key | Env var | Default | Meaning |
45
+ | --- | --- | --- | --- |
46
+ | `agent` | `PIPELINE_WORKER_AGENT` | `claude` | `claude` or `copilot` |
47
+ | `forge` | `PIPELINE_WORKER_FORGE` | `gitlab` | `gitlab` or `github` |
48
+ | `gitlab.host` | `PIPELINE_WORKER_GITLAB_HOST` | — | e.g. `https://gitlab.example.com` |
49
+ | `gitlab.projectId` | `PIPELINE_WORKER_GITLAB_PROJECT_ID` | — | numeric project id |
50
+ | — | `PIPELINE_WORKER_GITLAB_TOKEN` | — | GitLab API token (env only) |
51
+ | `github.repo` | `PIPELINE_WORKER_GITHUB_REPO` | — | `owner/name` slug |
52
+ | — | `PIPELINE_WORKER_GITHUB_TOKEN` | falls back to `GITHUB_TOKEN` | GitHub token (env only) |
53
+ | `build` / `lint` / `test` | — | auto-detected (see below) | local check commands; set to `''` to skip a stage |
54
+ | `maxFixAttempts` | — | `5` | agent fix attempts before escalating |
55
+ | `pollIntervalSeconds` | `PIPELINE_WORKER_POLL_INTERVAL_SECONDS` | `15` | pipeline poll cadence; use `60` for slow pipelines |
56
+
57
+ See [`.env.example`](.env.example) and [`.pipeline-worker.yml.example`](.pipeline-worker.yml.example) for annotated templates.
58
+
59
+ ### Check command auto-detection
60
+
61
+ When `build` / `lint` / `test` are not set, pipeline-worker picks defaults from the repo's toolchain (first marker found wins; mixed-language repos should set the commands explicitly):
62
+
63
+ | Toolchain | Marker | build | lint | test |
64
+ | --- | --- | --- | --- | --- |
65
+ | Node / TypeScript | `package.json` | `npm run build` | `npm run lint` | `npm test` — each only if the script is declared |
66
+ | .NET | `*.sln` / `*.csproj` / `*.fsproj` / `*.vbproj` at root | `dotnet build` | `dotnet format --verify-no-changes` | `dotnet test` |
67
+ | Go | `go.mod` | `go build ./...` | `go vet ./...` | `go test ./...` |
68
+ | Python | `pyproject.toml` / `setup.py` / `requirements.txt` | — | — | `pytest` |
69
+
70
+ A stage with no command (`—`, or `''` in the YAML) is skipped. If no toolchain is detected and no commands are configured, all local checks are skipped with a warning — configure them explicitly for any other stack (`build: make all`, etc.).
71
+
72
+ ## Commands
73
+
74
+ | Command | What it does |
75
+ | --- | --- |
76
+ | `pipeline-worker` (or `pipeline-worker run`) | Capture the current diff and drive it to a green MR/PR |
77
+ | `pipeline-worker serve` | Start the forge MCP server over stdio (used by the agent during fix runs) |
78
+ | `pipeline-worker resume --branch <name>` | Resume watching/fixing a run after a crash |
79
+ | `pipeline-worker status --branch <name>` | Print the persisted state of a run |
80
+
81
+ ## How the fix loop stays bounded
82
+
83
+ Every retry path has a cap: local checks abort the run before an MR is ever opened; pipeline polling gives up after a 2-hour safety window; fix attempts stop at `maxFixAttempts`; a fix attempt that changes no files, or a pipeline that ends `canceled`/`skipped`, escalates immediately instead of spending agent tokens. Escalation always leaves a comment on the MR/PR so a human knows to take over.
84
+
85
+ ## Contributing
86
+
87
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Please note the [Code of Conduct](CODE_OF_CONDUCT.md) and report vulnerabilities per [SECURITY.md](SECURITY.md).
88
+
89
+ ## License
90
+
91
+ [MIT](LICENSE)
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Headless Claude Code adapter. Flags verified against the locally installed
3
+ * `claude` CLI: `-p/--print`, `--output-format json`, `--permission-mode`,
4
+ * `--json-schema`, `--mcp-config`. There is no `--cwd` flag — working
5
+ * directory is controlled by the spawned process's `cwd` option.
6
+ */
7
+ import type { AgentAdapter } from './types.js';
8
+ export declare const claudeAdapter: AgentAdapter;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Headless Claude Code adapter. Flags verified against the locally installed
3
+ * `claude` CLI: `-p/--print`, `--output-format json`, `--permission-mode`,
4
+ * `--json-schema`, `--mcp-config`. There is no `--cwd` flag — working
5
+ * directory is controlled by the spawned process's `cwd` option.
6
+ */
7
+ import { execFile } from 'node:child_process';
8
+ import { promisify } from 'node:util';
9
+ const execFileAsync = promisify(execFile);
10
+ const INVOKE_TIMEOUT_MS = 300_000;
11
+ export const claudeAdapter = {
12
+ async invoke(opts) {
13
+ const args = [
14
+ '-p', opts.prompt,
15
+ '--output-format', 'json',
16
+ '--permission-mode', opts.permissionMode ?? 'acceptEdits',
17
+ ];
18
+ if (opts.jsonSchema) {
19
+ args.push('--json-schema', JSON.stringify(opts.jsonSchema));
20
+ }
21
+ if (opts.mcpConfigPath) {
22
+ args.push('--mcp-config', opts.mcpConfigPath);
23
+ }
24
+ const { stdout } = await execFileAsync('claude', args, {
25
+ cwd: opts.cwd,
26
+ timeout: INVOKE_TIMEOUT_MS,
27
+ maxBuffer: 64 * 1024 * 1024,
28
+ });
29
+ try {
30
+ const parsed = JSON.parse(stdout);
31
+ return { text: parsed.result ?? stdout, raw: parsed };
32
+ }
33
+ catch {
34
+ // --output-format json should always produce parseable JSON; fall back
35
+ // to the raw stream rather than throwing, since the invocation itself succeeded.
36
+ return { text: stdout };
37
+ }
38
+ },
39
+ };
40
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/agent/claude.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAElC,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,KAAK,CAAC,MAAM,CAAC,IAAwB;QACnC,MAAM,IAAI,GAAG;YACX,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,iBAAiB,EAAE,MAAM;YACzB,mBAAmB,EAAE,IAAI,CAAC,cAAc,IAAI,aAAa;SAC1D,CAAC;QACF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE;YACrD,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,OAAO,EAAE,iBAAiB;YAC1B,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAwB,CAAC;YACzD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,IAAI,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,iFAAiF;YACjF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Headless GitHub Copilot CLI adapter. Flags verified against GitHub's
3
+ * "Copilot CLI programmatic reference" docs: `-p <prompt>` (non-interactive
4
+ * single shot), `-s` (suppress stats/decoration, output only the agent's
5
+ * response), `--no-ask-user`, `--allow-all-tools` (required for unattended
6
+ * fix runs). Known gaps vs the Claude adapter, handled here:
7
+ * - no structured-output/JSON-schema flag -> the schema is embedded in the
8
+ * prompt and the JSON object is extracted from the response text;
9
+ * - no per-invocation MCP config flag -> Copilot only reads
10
+ * ~/.copilot/mcp-config.json, so `mcpConfigPath` is ignored with a warning.
11
+ */
12
+ import type { AgentAdapter } from './types.js';
13
+ export declare const copilotAdapter: AgentAdapter;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Headless GitHub Copilot CLI adapter. Flags verified against GitHub's
3
+ * "Copilot CLI programmatic reference" docs: `-p <prompt>` (non-interactive
4
+ * single shot), `-s` (suppress stats/decoration, output only the agent's
5
+ * response), `--no-ask-user`, `--allow-all-tools` (required for unattended
6
+ * fix runs). Known gaps vs the Claude adapter, handled here:
7
+ * - no structured-output/JSON-schema flag -> the schema is embedded in the
8
+ * prompt and the JSON object is extracted from the response text;
9
+ * - no per-invocation MCP config flag -> Copilot only reads
10
+ * ~/.copilot/mcp-config.json, so `mcpConfigPath` is ignored with a warning.
11
+ */
12
+ import { execFile } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+ const execFileAsync = promisify(execFile);
15
+ const INVOKE_TIMEOUT_MS = 300_000;
16
+ /** Pulls the outermost JSON object out of a text answer that may have prose around it. */
17
+ function extractJsonObject(text) {
18
+ const start = text.indexOf('{');
19
+ const end = text.lastIndexOf('}');
20
+ return start !== -1 && end > start ? text.slice(start, end + 1) : text;
21
+ }
22
+ export const copilotAdapter = {
23
+ async invoke(opts) {
24
+ let prompt = opts.prompt;
25
+ if (opts.jsonSchema) {
26
+ prompt +=
27
+ '\n\nRespond with ONLY a single JSON object matching this JSON Schema — no prose, no code fences:\n' +
28
+ JSON.stringify(opts.jsonSchema);
29
+ }
30
+ if (opts.mcpConfigPath) {
31
+ console.error('pipeline-worker: copilot CLI has no per-invocation MCP config flag; ignoring it. ' +
32
+ 'Register the server in ~/.copilot/mcp-config.json to give copilot forge access.');
33
+ }
34
+ const { stdout } = await execFileAsync('copilot', ['-p', prompt, '-s', '--no-ask-user', '--allow-all-tools'], {
35
+ cwd: opts.cwd,
36
+ timeout: INVOKE_TIMEOUT_MS,
37
+ maxBuffer: 64 * 1024 * 1024,
38
+ });
39
+ return { text: opts.jsonSchema ? extractJsonObject(stdout) : stdout.trim() };
40
+ },
41
+ };
42
+ //# sourceMappingURL=copilot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../src/agent/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAElC,0FAA0F;AAC1F,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,KAAK,CAAC,MAAM,CAAC,IAAwB;QACnC,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,MAAM;gBACJ,oGAAoG;oBACpG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CACX,mFAAmF;gBACjF,iFAAiF,CACpF,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,mBAAmB,CAAC,EAAE;YAC5G,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,OAAO,EAAE,iBAAiB;YAC1B,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;QAEH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IAC/E,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PipelineWorkerConfig } from '../types.js';
2
+ import type { AgentAdapter } from './types.js';
3
+ /**
4
+ * Picks the adapter named in .pipeline-worker.yml. Deliberately no runtime
5
+ * fallback/interactive choice — the config names exactly one agent per repo.
6
+ */
7
+ export declare function selectAgent(config: PipelineWorkerConfig): AgentAdapter;
@@ -0,0 +1,15 @@
1
+ import { claudeAdapter } from './claude.js';
2
+ import { copilotAdapter } from './copilot.js';
3
+ /**
4
+ * Picks the adapter named in .pipeline-worker.yml. Deliberately no runtime
5
+ * fallback/interactive choice — the config names exactly one agent per repo.
6
+ */
7
+ export function selectAgent(config) {
8
+ switch (config.agent) {
9
+ case 'claude':
10
+ return claudeAdapter;
11
+ case 'copilot':
12
+ return copilotAdapter;
13
+ }
14
+ }
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,QAAQ,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC;QACvB,KAAK,SAAS;YACZ,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ /** Common interface both the Claude Code and Copilot CLI adapters implement. */
2
+ export interface AgentInvokeOptions {
3
+ /** The instruction given to the agent for this turn. */
4
+ prompt: string;
5
+ /** Working directory the agent should operate in (the worktree). */
6
+ cwd: string;
7
+ /** Optional JSON schema the agent should conform its final answer to. */
8
+ jsonSchema?: object;
9
+ /** Optional path to an MCP config file (e.g. pipeline-worker's own `serve` registration) to load for this turn. */
10
+ mcpConfigPath?: string;
11
+ /** Optional permission mode override; adapters default to an auto-accept mode suitable for unattended runs. */
12
+ permissionMode?: string;
13
+ }
14
+ export interface AgentInvokeResult {
15
+ /** The agent's final text answer (or, when jsonSchema was supplied, its raw JSON string). */
16
+ text: string;
17
+ /** The parsed structured payload, when the adapter's output format returns one. */
18
+ raw?: unknown;
19
+ }
20
+ export interface AgentAdapter {
21
+ invoke(opts: AgentInvokeOptions): Promise<AgentInvokeResult>;
22
+ }
@@ -0,0 +1,3 @@
1
+ /** Common interface both the Claude Code and Copilot CLI adapters implement. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { runWorkflow } from './workflow/orchestrate.js';
4
+ import { watchPipeline } from './workflow/watchPipeline.js';
5
+ import { startServer } from './mcp/server.js';
6
+ import { loadConfig } from './config/loader.js';
7
+ import { createForge } from './forge/index.js';
8
+ import { selectAgent } from './agent/index.js';
9
+ import { loadRunState } from './state/runState.js';
10
+ import { buildEnvelope, errorEnvelope } from './toon/envelope.js';
11
+ const program = new Command();
12
+ program.name('pipeline-worker').description('Automated git-worktree -> agent-fix -> GitLab/GitHub MR workflow');
13
+ program
14
+ .command('run', { isDefault: true })
15
+ .description('Capture the current diff, validate it, and drive it through to a green MR/PR')
16
+ .action(async () => {
17
+ try {
18
+ await runWorkflow(process.cwd());
19
+ }
20
+ catch (error) {
21
+ console.error('pipeline-worker run failed:', error instanceof Error ? error.message : error);
22
+ process.exit(1);
23
+ }
24
+ });
25
+ program
26
+ .command('serve')
27
+ .description('Start the pipeline-worker forge (GitLab/GitHub) MCP server over stdio')
28
+ .action(async () => {
29
+ try {
30
+ await startServer();
31
+ }
32
+ catch (error) {
33
+ console.error('Failed to start MCP server:', error);
34
+ process.exit(1);
35
+ }
36
+ });
37
+ program
38
+ .command('resume')
39
+ .description('Resume watching/fixing a previously started run after a crash')
40
+ .requiredOption('--branch <name>', 'branch name of the run to resume')
41
+ .action(async (opts) => {
42
+ try {
43
+ const repoRoot = process.cwd();
44
+ const state = loadRunState(repoRoot, opts.branch);
45
+ if (!state || state.mrIid === undefined) {
46
+ console.error(`pipeline-worker: no resumable run found for branch ${opts.branch} (no merge request recorded yet).`);
47
+ process.exit(1);
48
+ }
49
+ const config = loadConfig(repoRoot);
50
+ const forge = createForge(config);
51
+ const agent = selectAgent(config);
52
+ await watchPipeline(forge, config, agent, state.worktreePath, state.branch, state.mrIid, state, repoRoot);
53
+ console.log(`pipeline-worker: resumed run finished with phase "${state.phase}".`);
54
+ }
55
+ catch (error) {
56
+ console.error('pipeline-worker resume failed:', error instanceof Error ? error.message : error);
57
+ process.exit(1);
58
+ }
59
+ });
60
+ program
61
+ .command('status')
62
+ .description('Print the persisted state of a run')
63
+ .requiredOption('--branch <name>', 'branch name of the run to inspect')
64
+ .action((opts) => {
65
+ const repoRoot = process.cwd();
66
+ const state = loadRunState(repoRoot, opts.branch);
67
+ if (!state) {
68
+ console.log(errorEnvelope('not_found', `no run state found for branch ${opts.branch}`));
69
+ return;
70
+ }
71
+ console.log(buildEnvelope({ status: 'success', data: state }));
72
+ });
73
+ program.parseAsync(process.argv).catch((error) => {
74
+ console.error('pipeline-worker: unexpected error:', error instanceof Error ? error.message : error);
75
+ process.exit(1);
76
+ });
77
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Toolchain detection: default build/lint/test commands for the target repo's
3
+ * language. An empty command means "no sensible default here" and the stage is
4
+ * skipped (see runChecks.ts). Explicit .pipeline-worker.yml values always win
5
+ * (see loader.ts). The first marker that matches decides; mixed-language repos
6
+ * should set the commands explicitly.
7
+ */
8
+ export interface DetectedChecks {
9
+ language: 'node' | 'dotnet' | 'go' | 'python' | 'unknown';
10
+ build: string;
11
+ lint: string;
12
+ test: string;
13
+ }
14
+ export declare function detectChecks(repoRoot: string): DetectedChecks;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Toolchain detection: default build/lint/test commands for the target repo's
3
+ * language. An empty command means "no sensible default here" and the stage is
4
+ * skipped (see runChecks.ts). Explicit .pipeline-worker.yml values always win
5
+ * (see loader.ts). The first marker that matches decides; mixed-language repos
6
+ * should set the commands explicitly.
7
+ */
8
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ const DOTNET_PROJECT_SUFFIXES = ['.sln', '.slnx', '.csproj', '.fsproj', '.vbproj'];
11
+ const PYTHON_MARKERS = ['pyproject.toml', 'setup.py', 'requirements.txt'];
12
+ /** Maps each stage to `npm run <script>` only for scripts the repo declares. */
13
+ function detectNode(repoRoot) {
14
+ let scripts;
15
+ try {
16
+ const raw = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8')).scripts;
17
+ scripts = raw !== null && typeof raw === 'object' ? raw : {};
18
+ }
19
+ catch {
20
+ // Corrupt/unreadable package.json: keep the npm commands so npm itself
21
+ // reports the real problem instead of the checks being silently skipped.
22
+ return { language: 'node', build: 'npm run build', lint: 'npm run lint', test: 'npm test' };
23
+ }
24
+ return {
25
+ language: 'node',
26
+ build: 'build' in scripts ? 'npm run build' : '',
27
+ lint: 'lint' in scripts ? 'npm run lint' : '',
28
+ test: 'test' in scripts ? 'npm test' : '',
29
+ };
30
+ }
31
+ export function detectChecks(repoRoot) {
32
+ if (existsSync(join(repoRoot, 'package.json')))
33
+ return detectNode(repoRoot);
34
+ let rootEntries = [];
35
+ try {
36
+ rootEntries = readdirSync(repoRoot);
37
+ }
38
+ catch {
39
+ // Unreadable root: fall through to unknown, honoring loader.ts's never-throw contract.
40
+ }
41
+ if (rootEntries.some((name) => DOTNET_PROJECT_SUFFIXES.some((suffix) => name.endsWith(suffix)))) {
42
+ return { language: 'dotnet', build: 'dotnet build', lint: 'dotnet format --verify-no-changes', test: 'dotnet test' };
43
+ }
44
+ if (existsSync(join(repoRoot, 'go.mod'))) {
45
+ return { language: 'go', build: 'go build ./...', lint: 'go vet ./...', test: 'go test ./...' };
46
+ }
47
+ if (PYTHON_MARKERS.some((marker) => existsSync(join(repoRoot, marker)))) {
48
+ // No universal python build/lint step; pytest is the de-facto test runner.
49
+ return { language: 'python', build: '', lint: '', test: 'pytest' };
50
+ }
51
+ return { language: 'unknown', build: '', lint: '', test: '' };
52
+ }
53
+ //# sourceMappingURL=detectChecks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectChecks.js","sourceRoot":"","sources":["../../src/config/detectChecks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AACnF,MAAM,cAAc,GAAG,CAAC,gBAAgB,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;AAE1E,gFAAgF;AAChF,SAAS,UAAU,CAAC,QAAgB;IAClC,IAAI,OAAgC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/F,OAAO,GAAG,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAE,GAA+B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5F,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;QACvE,yEAAyE;QACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9F,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;QAChD,IAAI,EAAE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE;QAC7C,IAAI,EAAE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;KAC1C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAE5E,IAAI,WAAW,GAAa,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,uFAAuF;IACzF,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,mCAAmC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IACvH,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,2EAA2E;QAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * .pipeline-worker.yml loader. Resolution order per value: environment variable ->
3
+ * .env file at repo root (never overrides real env) -> .pipeline-worker.yml ->
4
+ * built-in default (for build/lint/test: commands auto-detected from the repo's
5
+ * toolchain, see detectChecks.ts). Config file path: explicit override param ->
6
+ * PIPELINE_WORKER_CONFIG env var -> <repoRoot>/.pipeline-worker.yml. Never throws — a
7
+ * missing or unparseable file falls back to defaults (with a warning),
8
+ * mirroring mcp-sonar-analysis's registry.ts read/never-throw contract.
9
+ */
10
+ import type { PipelineWorkerConfig } from '../types.js';
11
+ export declare function loadConfig(repoRoot: string, override?: string): PipelineWorkerConfig;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * .pipeline-worker.yml loader. Resolution order per value: environment variable ->
3
+ * .env file at repo root (never overrides real env) -> .pipeline-worker.yml ->
4
+ * built-in default (for build/lint/test: commands auto-detected from the repo's
5
+ * toolchain, see detectChecks.ts). Config file path: explicit override param ->
6
+ * PIPELINE_WORKER_CONFIG env var -> <repoRoot>/.pipeline-worker.yml. Never throws — a
7
+ * missing or unparseable file falls back to defaults (with a warning),
8
+ * mirroring mcp-sonar-analysis's registry.ts read/never-throw contract.
9
+ */
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { parseEnv } from 'node:util';
13
+ import { load } from 'js-yaml';
14
+ import { detectChecks } from './detectChecks.js';
15
+ const CONFIG_FILE_NAME = '.pipeline-worker.yml';
16
+ const AGENT_NAMES = ['claude', 'copilot'];
17
+ const FORGE_NAMES = ['gitlab', 'github'];
18
+ // build/lint/test defaults come from detectChecks(repoRoot) at load time.
19
+ const DEFAULT_CONFIG = {
20
+ agent: 'claude',
21
+ forge: 'gitlab',
22
+ gitlab: {
23
+ host: '',
24
+ projectId: 0,
25
+ },
26
+ github: {
27
+ repo: '',
28
+ },
29
+ maxFixAttempts: 5,
30
+ pollIntervalSeconds: 15,
31
+ };
32
+ /** Loads <repoRoot>/.env into process.env; already-set variables always win. */
33
+ function loadDotEnv(repoRoot) {
34
+ const envPath = join(repoRoot, '.env');
35
+ if (!existsSync(envPath))
36
+ return;
37
+ try {
38
+ const parsed = parseEnv(readFileSync(envPath, 'utf-8'));
39
+ for (const [key, value] of Object.entries(parsed)) {
40
+ if (!(key in process.env))
41
+ process.env[key] = value;
42
+ }
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ console.error(`Warning: failed to read ${envPath}: ${message}. Ignoring it.`);
47
+ }
48
+ }
49
+ function pickName(value, allowed, fallback) {
50
+ return allowed.includes(value) ? value : fallback;
51
+ }
52
+ /** Parses a positive number, falling back when unset or invalid. */
53
+ function positiveNumber(value, fallback) {
54
+ const num = Number(value);
55
+ return Number.isFinite(num) && num > 0 ? num : fallback;
56
+ }
57
+ function resolveConfigPath(repoRoot, override) {
58
+ return override || process.env.PIPELINE_WORKER_CONFIG || join(repoRoot, CONFIG_FILE_NAME);
59
+ }
60
+ function readYamlConfig(configPath) {
61
+ if (!existsSync(configPath))
62
+ return {};
63
+ try {
64
+ return load(readFileSync(configPath, 'utf-8')) ?? {};
65
+ }
66
+ catch (error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ console.error(`Warning: failed to read ${configPath}: ${message}. Falling back to defaults.`);
69
+ return {};
70
+ }
71
+ }
72
+ export function loadConfig(repoRoot, override) {
73
+ loadDotEnv(repoRoot); // before resolveConfigPath so PIPELINE_WORKER_CONFIG can also come from .env
74
+ const parsed = readYamlConfig(resolveConfigPath(repoRoot, override));
75
+ const detected = detectChecks(repoRoot);
76
+ if (detected.language === 'unknown' && parsed.build === undefined && parsed.lint === undefined && parsed.test === undefined) {
77
+ console.error(`Warning: could not detect the toolchain of ${repoRoot}; build/lint/test will be skipped. Set them in ${CONFIG_FILE_NAME}.`);
78
+ }
79
+ // Each tier is validated independently: an invalid env value falls back to
80
+ // a valid yaml value, not straight to the built-in default.
81
+ return {
82
+ agent: pickName(process.env.PIPELINE_WORKER_AGENT, AGENT_NAMES, pickName(parsed.agent, AGENT_NAMES, DEFAULT_CONFIG.agent)),
83
+ forge: pickName(process.env.PIPELINE_WORKER_FORGE, FORGE_NAMES, pickName(parsed.forge, FORGE_NAMES, DEFAULT_CONFIG.forge)),
84
+ gitlab: {
85
+ host: parsed.gitlab?.host ?? DEFAULT_CONFIG.gitlab.host,
86
+ projectId: parsed.gitlab?.projectId ?? DEFAULT_CONFIG.gitlab.projectId,
87
+ },
88
+ github: {
89
+ repo: parsed.github?.repo ?? DEFAULT_CONFIG.github.repo,
90
+ },
91
+ build: parsed.build ?? detected.build,
92
+ lint: parsed.lint ?? detected.lint,
93
+ test: parsed.test ?? detected.test,
94
+ maxFixAttempts: parsed.maxFixAttempts ?? DEFAULT_CONFIG.maxFixAttempts,
95
+ pollIntervalSeconds: positiveNumber(process.env.PIPELINE_WORKER_POLL_INTERVAL_SECONDS, positiveNumber(parsed.pollIntervalSeconds, DEFAULT_CONFIG.pollIntervalSeconds)),
96
+ };
97
+ }
98
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"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"}