pipeline-worker 0.1.0 → 0.1.2
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.
- package/README.md +42 -36
- package/dist/agent/claude.d.ts +17 -0
- package/dist/agent/claude.js +79 -5
- package/dist/agent/claude.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/detectChecks.js +51 -2
- package/dist/config/detectChecks.js.map +1 -1
- package/dist/config/loader.js +26 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/forge/github.js +12 -1
- package/dist/forge/github.js.map +1 -1
- package/dist/forge/gitlab.d.ts +1 -1
- package/dist/forge/gitlab.js +18 -5
- package/dist/forge/gitlab.js.map +1 -1
- package/dist/forge/types.d.ts +9 -0
- package/dist/git/commit.d.ts +20 -0
- package/dist/git/commit.js +37 -0
- package/dist/git/commit.js.map +1 -1
- package/dist/git/diff.d.ts +6 -0
- package/dist/git/diff.js +7 -1
- package/dist/git/diff.js.map +1 -1
- package/dist/git/resolveProjectPath.d.ts +22 -0
- package/dist/git/resolveProjectPath.js +37 -0
- package/dist/git/resolveProjectPath.js.map +1 -0
- package/dist/git/worktree.d.ts +24 -1
- package/dist/git/worktree.js +63 -3
- package/dist/git/worktree.js.map +1 -1
- package/dist/types.d.ts +16 -1
- package/dist/ui/steps.d.ts +34 -0
- package/dist/ui/steps.js +83 -0
- package/dist/ui/steps.js.map +1 -0
- package/dist/ui/welcome.d.ts +5 -0
- package/dist/ui/welcome.js +34 -0
- package/dist/ui/welcome.js.map +1 -0
- package/dist/workflow/captureIntent.js +71 -8
- package/dist/workflow/captureIntent.js.map +1 -1
- package/dist/workflow/openMergeRequest.d.ts +2 -0
- package/dist/workflow/openMergeRequest.js +24 -13
- package/dist/workflow/openMergeRequest.js.map +1 -1
- package/dist/workflow/orchestrate.js +48 -13
- package/dist/workflow/orchestrate.js.map +1 -1
- package/dist/workflow/runChecks.d.ts +6 -0
- package/dist/workflow/runChecks.js +33 -5
- package/dist/workflow/runChecks.js.map +1 -1
- package/dist/workflow/watchPipeline.d.ts +8 -1
- package/dist/workflow/watchPipeline.js +112 -22
- package/dist/workflow/watchPipeline.js.map +1 -1
- package/package.json +2 -4
- package/.env.example +0 -27
- package/.pipeline-worker.yml.example +0 -24
package/dist/git/commit.d.ts
CHANGED
|
@@ -4,5 +4,25 @@ export declare function commit(worktreePath: string, message: string): Promise<v
|
|
|
4
4
|
export declare function push(worktreePath: string, remote: string, branch: string): Promise<void>;
|
|
5
5
|
/** True when the worktree has staged, unstaged, or untracked changes. */
|
|
6
6
|
export declare function hasChanges(worktreePath: string): Promise<boolean>;
|
|
7
|
+
/** Files git still reports as unmerged — conflict markers from a `git apply --3way` or `git merge` not yet resolved and staged. */
|
|
8
|
+
export declare function listConflictedFiles(worktreePath: string): Promise<string[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Which of `files` still literally contain an unresolved `<<<<<<<`/`>>>>>>>`
|
|
11
|
+
* conflict marker line. Deliberately reads file content rather than reusing
|
|
12
|
+
* listConflictedFiles: git's index keeps a file flagged "unmerged" until
|
|
13
|
+
* `git add` re-stages it, regardless of whether its content still has
|
|
14
|
+
* markers — so checking the index right after an editor (human or agent)
|
|
15
|
+
* fixes the content, but before staging, would always report "still
|
|
16
|
+
* conflicted" even when it's actually resolved. Checking content directly
|
|
17
|
+
* also catches the opposite mistake: `git add` doesn't validate content, so
|
|
18
|
+
* staging first and then checking the index would silently accept a file
|
|
19
|
+
* that still has markers in it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findUnresolvedConflictMarkers(worktreePath: string, files: string[]): string[];
|
|
7
22
|
export declare function currentSha(worktreePath: string): Promise<string>;
|
|
8
23
|
export declare function currentBranch(cwd: string): Promise<string>;
|
|
24
|
+
/** Reads git config user.name/user.email for display purposes; never throws — unset config just reads as ''. */
|
|
25
|
+
export declare function getGitUser(cwd: string): Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
}>;
|
package/dist/git/commit.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Thin git plumbing wrappers used against the isolated worktree. */
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
3
5
|
import { promisify } from 'node:util';
|
|
4
6
|
const execFileAsync = promisify(execFile);
|
|
5
7
|
export async function stageAll(worktreePath) {
|
|
@@ -16,6 +18,27 @@ export async function hasChanges(worktreePath) {
|
|
|
16
18
|
const { stdout } = await execFileAsync('git', ['status', '--porcelain'], { cwd: worktreePath });
|
|
17
19
|
return stdout.trim().length > 0;
|
|
18
20
|
}
|
|
21
|
+
/** Files git still reports as unmerged — conflict markers from a `git apply --3way` or `git merge` not yet resolved and staged. */
|
|
22
|
+
export async function listConflictedFiles(worktreePath) {
|
|
23
|
+
const { stdout } = await execFileAsync('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: worktreePath });
|
|
24
|
+
return stdout.trim().split('\n').filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
const CONFLICT_MARKER = /^(<{7} |>{7} )/m;
|
|
27
|
+
/**
|
|
28
|
+
* Which of `files` still literally contain an unresolved `<<<<<<<`/`>>>>>>>`
|
|
29
|
+
* conflict marker line. Deliberately reads file content rather than reusing
|
|
30
|
+
* listConflictedFiles: git's index keeps a file flagged "unmerged" until
|
|
31
|
+
* `git add` re-stages it, regardless of whether its content still has
|
|
32
|
+
* markers — so checking the index right after an editor (human or agent)
|
|
33
|
+
* fixes the content, but before staging, would always report "still
|
|
34
|
+
* conflicted" even when it's actually resolved. Checking content directly
|
|
35
|
+
* also catches the opposite mistake: `git add` doesn't validate content, so
|
|
36
|
+
* staging first and then checking the index would silently accept a file
|
|
37
|
+
* that still has markers in it.
|
|
38
|
+
*/
|
|
39
|
+
export function findUnresolvedConflictMarkers(worktreePath, files) {
|
|
40
|
+
return files.filter((file) => CONFLICT_MARKER.test(readFileSync(join(worktreePath, file), 'utf-8')));
|
|
41
|
+
}
|
|
19
42
|
export async function currentSha(worktreePath) {
|
|
20
43
|
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: worktreePath });
|
|
21
44
|
return stdout.trim();
|
|
@@ -24,4 +47,18 @@ export async function currentBranch(cwd) {
|
|
|
24
47
|
const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
|
|
25
48
|
return stdout.trim();
|
|
26
49
|
}
|
|
50
|
+
/** Reads git config user.name/user.email for display purposes; never throws — unset config just reads as ''. */
|
|
51
|
+
export async function getGitUser(cwd) {
|
|
52
|
+
async function readConfig(key) {
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execFileAsync('git', ['config', key], { cwd });
|
|
55
|
+
return stdout.trim();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const [name, email] = await Promise.all([readConfig('user.name'), readConfig('user.email')]);
|
|
62
|
+
return { name, email };
|
|
63
|
+
}
|
|
27
64
|
//# sourceMappingURL=commit.js.map
|
package/dist/git/commit.js.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../src/git/commit.ts"],"names":[],"mappings":"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"}
|
package/dist/git/diff.d.ts
CHANGED
|
@@ -7,5 +7,11 @@ export interface CapturedDiff {
|
|
|
7
7
|
* Captures staged+unstaged changes (`git diff HEAD`) plus the list of
|
|
8
8
|
* untracked files (which `git diff` never includes) so both can be carried
|
|
9
9
|
* into an isolated worktree.
|
|
10
|
+
*
|
|
11
|
+
* `--full-index` records full (not abbreviated) blob hashes, which
|
|
12
|
+
* worktree.ts's `git apply --3way` needs to reliably locate each blob's
|
|
13
|
+
* common ancestor when the worktree has since been rebased onto a newer
|
|
14
|
+
* origin — otherwise a 3-way merge could fail to resolve an otherwise
|
|
15
|
+
* legitimate abbreviated hash.
|
|
10
16
|
*/
|
|
11
17
|
export declare function captureDiff(repoRoot: string): Promise<CapturedDiff>;
|
package/dist/git/diff.js
CHANGED
|
@@ -6,9 +6,15 @@ const execFileAsync = promisify(execFile);
|
|
|
6
6
|
* Captures staged+unstaged changes (`git diff HEAD`) plus the list of
|
|
7
7
|
* untracked files (which `git diff` never includes) so both can be carried
|
|
8
8
|
* into an isolated worktree.
|
|
9
|
+
*
|
|
10
|
+
* `--full-index` records full (not abbreviated) blob hashes, which
|
|
11
|
+
* worktree.ts's `git apply --3way` needs to reliably locate each blob's
|
|
12
|
+
* common ancestor when the worktree has since been rebased onto a newer
|
|
13
|
+
* origin — otherwise a 3-way merge could fail to resolve an otherwise
|
|
14
|
+
* legitimate abbreviated hash.
|
|
9
15
|
*/
|
|
10
16
|
export async function captureDiff(repoRoot) {
|
|
11
|
-
const { stdout: diffText } = await execFileAsync('git', ['diff', 'HEAD'], {
|
|
17
|
+
const { stdout: diffText } = await execFileAsync('git', ['diff', 'HEAD', '--full-index'], {
|
|
12
18
|
cwd: repoRoot,
|
|
13
19
|
maxBuffer: 64 * 1024 * 1024,
|
|
14
20
|
});
|
package/dist/git/diff.js.map
CHANGED
|
@@ -1 +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
|
|
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;;;;;;;;;;GAUG;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,EAAE,cAAc,CAAC,EAAE;QACxF,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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a GitLab namespace/project path from a local directory structure that
|
|
3
|
+
* mirrors the GitLab group hierarchy. Used when PIPELINE_WORKER_GITLAB_REPO_BASE
|
|
4
|
+
* is set and no explicit projectId is configured, so the user can run
|
|
5
|
+
* `pipeline-worker run` from any repo under the base without per-project config.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Converts a single path segment to lowercase kebab-case.
|
|
9
|
+
* Handles both PascalCase ("RetailMediaPortal" → "retail-media-portal") and
|
|
10
|
+
* already-hyphenated names ("Store-Media-Api" → "store-media-api").
|
|
11
|
+
*/
|
|
12
|
+
export declare function toKebabCase(segment: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Derives the GitLab project path from a local repo root and a base directory.
|
|
15
|
+
*
|
|
16
|
+
* @param repoBase - Local directory that mirrors the GitLab host root, e.g. '/home/user/REPO'
|
|
17
|
+
* @param repoRoot - Absolute path of the local repo, e.g. '/home/user/REPO/Media/RetailMediaPortal/Instore/Store-Media-Api'
|
|
18
|
+
* @returns GitLab project path, e.g. 'media/retail-media-portal/instore/store-media-api'
|
|
19
|
+
*
|
|
20
|
+
* @throws if repoRoot is not inside repoBase
|
|
21
|
+
*/
|
|
22
|
+
export declare function deriveProjectPath(repoBase: string, repoRoot: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a GitLab namespace/project path from a local directory structure that
|
|
3
|
+
* mirrors the GitLab group hierarchy. Used when PIPELINE_WORKER_GITLAB_REPO_BASE
|
|
4
|
+
* is set and no explicit projectId is configured, so the user can run
|
|
5
|
+
* `pipeline-worker run` from any repo under the base without per-project config.
|
|
6
|
+
*/
|
|
7
|
+
import { relative, sep } from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Converts a single path segment to lowercase kebab-case.
|
|
10
|
+
* Handles both PascalCase ("RetailMediaPortal" → "retail-media-portal") and
|
|
11
|
+
* already-hyphenated names ("Store-Media-Api" → "store-media-api").
|
|
12
|
+
*/
|
|
13
|
+
export function toKebabCase(segment) {
|
|
14
|
+
return segment
|
|
15
|
+
.replace(/([a-z\d])([A-Z])/g, '$1-$2')
|
|
16
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Derives the GitLab project path from a local repo root and a base directory.
|
|
21
|
+
*
|
|
22
|
+
* @param repoBase - Local directory that mirrors the GitLab host root, e.g. '/home/user/REPO'
|
|
23
|
+
* @param repoRoot - Absolute path of the local repo, e.g. '/home/user/REPO/Media/RetailMediaPortal/Instore/Store-Media-Api'
|
|
24
|
+
* @returns GitLab project path, e.g. 'media/retail-media-portal/instore/store-media-api'
|
|
25
|
+
*
|
|
26
|
+
* @throws if repoRoot is not inside repoBase
|
|
27
|
+
*/
|
|
28
|
+
export function deriveProjectPath(repoBase, repoRoot) {
|
|
29
|
+
const rel = relative(repoBase, repoRoot);
|
|
30
|
+
if (!rel || rel.startsWith('..')) {
|
|
31
|
+
throw new Error(`Cannot derive GitLab project path: '${repoRoot}' is not inside repoBase '${repoBase}'. ` +
|
|
32
|
+
`Set PIPELINE_WORKER_GITLAB_REPO_BASE (or gitlab.repoBase in .pipeline-worker.yml) ` +
|
|
33
|
+
`to the local directory that mirrors the GitLab host namespace root.`);
|
|
34
|
+
}
|
|
35
|
+
return rel.split(sep).map(toKebabCase).join('/');
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=resolveProjectPath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveProjectPath.js","sourceRoot":"","sources":["../../src/git/resolveProjectPath.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,OAAO;SACX,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC;SACrC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,WAAW,EAAE,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAgB;IAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,uCAAuC,QAAQ,6BAA6B,QAAQ,KAAK;YACvF,oFAAoF;YACpF,qEAAqE,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnD,CAAC"}
|
package/dist/git/worktree.d.ts
CHANGED
|
@@ -6,11 +6,34 @@
|
|
|
6
6
|
export declare function generateTempBranchName(): string;
|
|
7
7
|
/** Creates a new worktree off HEAD on a fresh branch, returning its path. */
|
|
8
8
|
export declare function createWorktree(repoRoot: string, branchName: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Fetches origin and rebases the worktree's branch onto origin/targetBranch,
|
|
11
|
+
* so the diff applied afterward lands on the freshest possible base instead
|
|
12
|
+
* of whatever commit repoRoot's local HEAD happened to be at when the
|
|
13
|
+
* worktree was created. Safe to call before any diff is applied: the
|
|
14
|
+
* worktree has no uncommitted changes yet, so there's nothing for the
|
|
15
|
+
* rebase to conflict with except the diff's own base commit — if that
|
|
16
|
+
* conflicts, git reports it and the run fails clearly, same as a real rebase
|
|
17
|
+
* conflict would for a human.
|
|
18
|
+
*/
|
|
19
|
+
export declare function syncWithOrigin(worktreePath: string, targetBranch: string): Promise<void>;
|
|
20
|
+
export interface ApplyDiffResult {
|
|
21
|
+
/** True when the diff didn't apply cleanly and left conflict markers for the caller to resolve. */
|
|
22
|
+
conflicted: boolean;
|
|
23
|
+
conflictedFiles: string[];
|
|
24
|
+
}
|
|
9
25
|
/**
|
|
10
26
|
* Applies a captured diff (staged+unstaged) and copies untracked files into
|
|
11
27
|
* the worktree, so it ends up with exactly the caller's original change set.
|
|
28
|
+
*
|
|
29
|
+
* Uses `--3way`: the diff was captured against repoRoot's HEAD *before*
|
|
30
|
+
* syncWithOrigin rebased the worktree onto the latest origin, so a plain
|
|
31
|
+
* `git apply` can fail outright once origin has moved. `--3way` falls back
|
|
32
|
+
* to a real three-way merge using the blobs recorded in the diff, leaving
|
|
33
|
+
* standard conflict markers (like a merge conflict) instead of a hard
|
|
34
|
+
* failure — the caller resolves them the same way as any other conflict.
|
|
12
35
|
*/
|
|
13
|
-
export declare function applyDiffToWorktree(worktreePath: string, diffText: string, untrackedFiles: string[], repoRoot: string): Promise<
|
|
36
|
+
export declare function applyDiffToWorktree(worktreePath: string, diffText: string, untrackedFiles: string[], repoRoot: string): Promise<ApplyDiffResult>;
|
|
14
37
|
/** Renames the worktree's current branch (used once the agent has proposed a real name). */
|
|
15
38
|
export declare function renameBranch(worktreePath: string, newBranchName: string): Promise<void>;
|
|
16
39
|
/**
|
package/dist/git/worktree.js
CHANGED
|
@@ -5,14 +5,46 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { execFile } from 'node:child_process';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
-
import { mkdtempSync, mkdirSync, cpSync, writeFileSync, unlinkSync, rmSync } from 'node:fs';
|
|
8
|
+
import { mkdtempSync, mkdirSync, cpSync, writeFileSync, unlinkSync, rmSync, existsSync, symlinkSync } from 'node:fs';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
11
|
import { join, dirname } from 'node:path';
|
|
12
|
+
import { listConflictedFiles } from './commit.js';
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
export function generateTempBranchName() {
|
|
14
15
|
return `pipeline-worker/tmp-${randomUUID().slice(0, 8)}`;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Points worktreePath/node_modules at repoRoot/node_modules. git worktrees
|
|
19
|
+
* don't include node_modules (it's normally gitignored, not tracked), so
|
|
20
|
+
* build/lint/test would otherwise fail on a missing toolchain in every repo
|
|
21
|
+
* that has one. Symlinking the source repo's node_modules in is the same
|
|
22
|
+
* trick turbo/lerna use for worktree-based tooling: instant, no network, and
|
|
23
|
+
* safe because node_modules is never part of the diff being tested.
|
|
24
|
+
*
|
|
25
|
+
* Called only from applyDiffToWorktree, once the diff is fully applied —
|
|
26
|
+
* never from createWorktree. If node_modules was ever accidentally
|
|
27
|
+
* committed (as happened in this very repo), `git apply` needs to see the
|
|
28
|
+
* worktree exactly as `git worktree add` checked it out; pre-placing a
|
|
29
|
+
* symlink here would make that tracked path's working-tree content diverge
|
|
30
|
+
* from git's index before git apply runs, and its behavior on a mismatch
|
|
31
|
+
* ranges from silently dropping the symlink to rejecting the hunk outright.
|
|
32
|
+
* Linking only after the apply step sidesteps that entirely.
|
|
33
|
+
*/
|
|
34
|
+
function linkNodeModules(repoRoot, worktreePath) {
|
|
35
|
+
const sourceNodeModules = join(repoRoot, 'node_modules');
|
|
36
|
+
if (!existsSync(sourceNodeModules))
|
|
37
|
+
return;
|
|
38
|
+
const destNodeModules = join(worktreePath, 'node_modules');
|
|
39
|
+
try {
|
|
40
|
+
unlinkSync(destNodeModules);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== 'ENOENT')
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
symlinkSync(sourceNodeModules, destNodeModules, 'dir');
|
|
47
|
+
}
|
|
16
48
|
/** Creates a new worktree off HEAD on a fresh branch, returning its path. */
|
|
17
49
|
export async function createWorktree(repoRoot, branchName) {
|
|
18
50
|
const parentDir = mkdtempSync(join(tmpdir(), 'pipeline-worker-'));
|
|
@@ -20,16 +52,42 @@ export async function createWorktree(repoRoot, branchName) {
|
|
|
20
52
|
await execFileAsync('git', ['worktree', 'add', '-b', branchName, worktreePath, 'HEAD'], { cwd: repoRoot });
|
|
21
53
|
return worktreePath;
|
|
22
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetches origin and rebases the worktree's branch onto origin/targetBranch,
|
|
57
|
+
* so the diff applied afterward lands on the freshest possible base instead
|
|
58
|
+
* of whatever commit repoRoot's local HEAD happened to be at when the
|
|
59
|
+
* worktree was created. Safe to call before any diff is applied: the
|
|
60
|
+
* worktree has no uncommitted changes yet, so there's nothing for the
|
|
61
|
+
* rebase to conflict with except the diff's own base commit — if that
|
|
62
|
+
* conflicts, git reports it and the run fails clearly, same as a real rebase
|
|
63
|
+
* conflict would for a human.
|
|
64
|
+
*/
|
|
65
|
+
export async function syncWithOrigin(worktreePath, targetBranch) {
|
|
66
|
+
await execFileAsync('git', ['pull', '--rebase', 'origin', targetBranch], { cwd: worktreePath });
|
|
67
|
+
}
|
|
23
68
|
/**
|
|
24
69
|
* Applies a captured diff (staged+unstaged) and copies untracked files into
|
|
25
70
|
* the worktree, so it ends up with exactly the caller's original change set.
|
|
71
|
+
*
|
|
72
|
+
* Uses `--3way`: the diff was captured against repoRoot's HEAD *before*
|
|
73
|
+
* syncWithOrigin rebased the worktree onto the latest origin, so a plain
|
|
74
|
+
* `git apply` can fail outright once origin has moved. `--3way` falls back
|
|
75
|
+
* to a real three-way merge using the blobs recorded in the diff, leaving
|
|
76
|
+
* standard conflict markers (like a merge conflict) instead of a hard
|
|
77
|
+
* failure — the caller resolves them the same way as any other conflict.
|
|
26
78
|
*/
|
|
27
79
|
export async function applyDiffToWorktree(worktreePath, diffText, untrackedFiles, repoRoot) {
|
|
80
|
+
let conflictedFiles = [];
|
|
28
81
|
if (diffText.trim().length > 0) {
|
|
29
82
|
const diffFile = join(tmpdir(), `pipeline-worker-diff-${randomUUID()}.patch`);
|
|
30
83
|
writeFileSync(diffFile, diffText, 'utf-8');
|
|
31
84
|
try {
|
|
32
|
-
await execFileAsync('git', ['apply', '--index', diffFile], { cwd: worktreePath });
|
|
85
|
+
await execFileAsync('git', ['apply', '--3way', '--index', diffFile], { cwd: worktreePath });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
conflictedFiles = await listConflictedFiles(worktreePath);
|
|
89
|
+
if (conflictedFiles.length === 0)
|
|
90
|
+
throw error; // not a recoverable conflict — surface the real error
|
|
33
91
|
}
|
|
34
92
|
finally {
|
|
35
93
|
unlinkSync(diffFile);
|
|
@@ -41,9 +99,11 @@ export async function applyDiffToWorktree(worktreePath, diffText, untrackedFiles
|
|
|
41
99
|
mkdirSync(dirname(dest), { recursive: true });
|
|
42
100
|
cpSync(src, dest, { recursive: true });
|
|
43
101
|
}
|
|
44
|
-
if (untrackedFiles.length > 0) {
|
|
102
|
+
if (untrackedFiles.length > 0 && conflictedFiles.length === 0) {
|
|
45
103
|
await execFileAsync('git', ['add', '-A'], { cwd: worktreePath });
|
|
46
104
|
}
|
|
105
|
+
linkNodeModules(repoRoot, worktreePath);
|
|
106
|
+
return { conflicted: conflictedFiles.length > 0, conflictedFiles };
|
|
47
107
|
}
|
|
48
108
|
/** Renames the worktree's current branch (used once the agent has proposed a real name). */
|
|
49
109
|
export async function renameBranch(worktreePath, newBranchName) {
|
package/dist/git/worktree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/git/worktree.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/git/worktree.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export interface PipelineWorkerConfig {
|
|
|
6
6
|
forge: ForgeName;
|
|
7
7
|
gitlab: {
|
|
8
8
|
host: string;
|
|
9
|
-
projectId: number;
|
|
9
|
+
projectId: number | string;
|
|
10
|
+
repoBase?: string;
|
|
10
11
|
};
|
|
11
12
|
github: {
|
|
12
13
|
/** "owner/name" slug of the repository. */
|
|
@@ -22,16 +23,30 @@ export interface PipelineWorkerConfig {
|
|
|
22
23
|
export type RunPhase = 'diff' | 'intent' | 'checks' | 'mr' | 'watch' | 'done' | 'escalated';
|
|
23
24
|
export interface RunState {
|
|
24
25
|
branch: string;
|
|
26
|
+
targetBranch: string;
|
|
25
27
|
worktreePath: string;
|
|
26
28
|
mrIid?: number;
|
|
27
29
|
pipelineId?: number;
|
|
28
30
|
attempt: number;
|
|
29
31
|
phase: RunPhase;
|
|
30
32
|
}
|
|
33
|
+
export type RiskLevel = 'low' | 'medium' | 'high';
|
|
34
|
+
export interface FileChangeSummary {
|
|
35
|
+
file: string;
|
|
36
|
+
summary: string;
|
|
37
|
+
}
|
|
31
38
|
export interface CapturedIntent {
|
|
39
|
+
/** One short sentence: why this change exists / what problem it solves. */
|
|
40
|
+
intent: string;
|
|
32
41
|
summary: string;
|
|
33
42
|
branchName: string;
|
|
34
43
|
commitMessage: string;
|
|
44
|
+
fileChanges: FileChangeSummary[];
|
|
45
|
+
risk: RiskLevel;
|
|
46
|
+
/** One short sentence justifying the risk level. */
|
|
47
|
+
riskReason: string;
|
|
48
|
+
/** Concrete scenarios a reviewer should verify before merging. */
|
|
49
|
+
testScenarios: string[];
|
|
35
50
|
}
|
|
36
51
|
export interface CheckResult {
|
|
37
52
|
name: 'build' | 'lint' | 'test';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Friendly progress narration for `pipeline-worker run`: a bold, colored,
|
|
3
|
+
* numbered headline per workflow step (with an icon and a "[n/TOTAL_STAGES]"
|
|
4
|
+
* counter) and a dim technical detail line underneath — mirrors how Claude
|
|
5
|
+
* Code narrates its own tool calls, so a non-technical user can follow along
|
|
6
|
+
* without reading source.
|
|
7
|
+
*/
|
|
8
|
+
import type { RiskLevel } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* The workflow always runs stages 1-10 once in order, then stage 11 (watching
|
|
11
|
+
* the pipeline, and fixing/escalating on failure) until it resolves — so
|
|
12
|
+
* every step, including the CI-fix loop's sub-steps, is stage 11.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TOTAL_STAGES = 11;
|
|
15
|
+
/** A one-off status line with no associated work to wait on (e.g. a final summary). */
|
|
16
|
+
export declare function step(icon: string, title: string, detail?: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* An indented supplementary detail line under the current step (e.g. an
|
|
19
|
+
* agent's summary, a result). Input is freeform (agent output, forge data)
|
|
20
|
+
* with no single-line guarantee, so collapse any embedded newlines —
|
|
21
|
+
* otherwise a multi-line value would break out of the "one dim line" format.
|
|
22
|
+
*/
|
|
23
|
+
export declare function note(text: string): void;
|
|
24
|
+
/** Like note(), but colors the text by risk level (green/yellow/red for low/medium/high). */
|
|
25
|
+
export declare function noteRisk(risk: RiskLevel, reason: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Runs one numbered workflow stage with a colored, iconed title, then a live
|
|
28
|
+
* status line beneath it while `task` runs: a spinner plus a counting-up
|
|
29
|
+
* elapsed timer in a TTY, settling into a green checkmark or red cross with
|
|
30
|
+
* the total duration on completion. Falls back to a single static print when
|
|
31
|
+
* stdout isn't a TTY (CI logs, redirected files) since carriage-return
|
|
32
|
+
* spinners would just garble those.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runStep<T>(stage: number, icon: string, title: string, detail: string, task: () => Promise<T>): Promise<T>;
|
package/dist/ui/steps.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Friendly progress narration for `pipeline-worker run`: a bold, colored,
|
|
3
|
+
* numbered headline per workflow step (with an icon and a "[n/TOTAL_STAGES]"
|
|
4
|
+
* counter) and a dim technical detail line underneath — mirrors how Claude
|
|
5
|
+
* Code narrates its own tool calls, so a non-technical user can follow along
|
|
6
|
+
* without reading source.
|
|
7
|
+
*/
|
|
8
|
+
import { styleText } from 'node:util';
|
|
9
|
+
const RISK_COLOR = { low: 'green', medium: 'yellow', high: 'red' };
|
|
10
|
+
/**
|
|
11
|
+
* The workflow always runs stages 1-10 once in order, then stage 11 (watching
|
|
12
|
+
* the pipeline, and fixing/escalating on failure) until it resolves — so
|
|
13
|
+
* every step, including the CI-fix loop's sub-steps, is stage 11.
|
|
14
|
+
*/
|
|
15
|
+
export const TOTAL_STAGES = 11;
|
|
16
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
17
|
+
const SPINNER_INTERVAL_MS = 80;
|
|
18
|
+
function formatElapsed(ms) {
|
|
19
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
20
|
+
}
|
|
21
|
+
function stageHeader(stage, icon, title) {
|
|
22
|
+
return styleText(['bold', 'cyan'], `[${stage}/${TOTAL_STAGES}] ${icon} ${title}`);
|
|
23
|
+
}
|
|
24
|
+
/** A one-off status line with no associated work to wait on (e.g. a final summary). */
|
|
25
|
+
export function step(icon, title, detail) {
|
|
26
|
+
console.log(); // blank line for clear separation between stages
|
|
27
|
+
console.log(styleText('bold', `${icon} ${title}`));
|
|
28
|
+
if (detail)
|
|
29
|
+
console.log(styleText('dim', ` ${detail}`));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* An indented supplementary detail line under the current step (e.g. an
|
|
33
|
+
* agent's summary, a result). Input is freeform (agent output, forge data)
|
|
34
|
+
* with no single-line guarantee, so collapse any embedded newlines —
|
|
35
|
+
* otherwise a multi-line value would break out of the "one dim line" format.
|
|
36
|
+
*/
|
|
37
|
+
export function note(text) {
|
|
38
|
+
console.log(styleText('dim', ` ${text.replace(/\s*\n\s*/g, ' ')}`));
|
|
39
|
+
}
|
|
40
|
+
/** Like note(), but colors the text by risk level (green/yellow/red for low/medium/high). */
|
|
41
|
+
export function noteRisk(risk, reason) {
|
|
42
|
+
console.log(styleText('dim', ' ') + styleText(RISK_COLOR[risk], `risk: ${risk} — ${reason.replace(/\s*\n\s*/g, ' ')}`));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Runs one numbered workflow stage with a colored, iconed title, then a live
|
|
46
|
+
* status line beneath it while `task` runs: a spinner plus a counting-up
|
|
47
|
+
* elapsed timer in a TTY, settling into a green checkmark or red cross with
|
|
48
|
+
* the total duration on completion. Falls back to a single static print when
|
|
49
|
+
* stdout isn't a TTY (CI logs, redirected files) since carriage-return
|
|
50
|
+
* spinners would just garble those.
|
|
51
|
+
*/
|
|
52
|
+
export async function runStep(stage, icon, title, detail, task) {
|
|
53
|
+
console.log(); // blank line for clear separation between stages
|
|
54
|
+
console.log(stageHeader(stage, icon, title));
|
|
55
|
+
if (!process.stdout.isTTY) {
|
|
56
|
+
console.log(styleText('dim', ` ${detail}`));
|
|
57
|
+
return task();
|
|
58
|
+
}
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
let frame = 0;
|
|
61
|
+
const render = (glyph, color = 'dim') => {
|
|
62
|
+
process.stdout.write(`\r\x1b[K${styleText(color, ` ${glyph} ${detail} (${formatElapsed(Date.now() - start)})`)}`);
|
|
63
|
+
};
|
|
64
|
+
render(SPINNER_FRAMES[0]);
|
|
65
|
+
const timer = setInterval(() => {
|
|
66
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
67
|
+
render(SPINNER_FRAMES[frame]);
|
|
68
|
+
}, SPINNER_INTERVAL_MS);
|
|
69
|
+
try {
|
|
70
|
+
const result = await task();
|
|
71
|
+
clearInterval(timer);
|
|
72
|
+
render('✓', 'green');
|
|
73
|
+
process.stdout.write('\n');
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
clearInterval(timer);
|
|
78
|
+
render('✗', 'red');
|
|
79
|
+
process.stdout.write('\n');
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/ui/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Prints a one-time banner summarizing this run's configuration before any workflow stage starts. */
|
|
2
|
+
import type { PipelineWorkerConfig } from '../types.js';
|
|
3
|
+
export declare function repositoryUrl(config: PipelineWorkerConfig): string;
|
|
4
|
+
export declare function agentDescription(config: PipelineWorkerConfig): string;
|
|
5
|
+
export declare function printWelcome(config: PipelineWorkerConfig, repoRoot: string): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Prints a one-time banner summarizing this run's configuration before any workflow stage starts. */
|
|
2
|
+
import { styleText } from 'node:util';
|
|
3
|
+
import { getGitUser } from '../git/commit.js';
|
|
4
|
+
const RULE_WIDTH = 60;
|
|
5
|
+
export function repositoryUrl(config) {
|
|
6
|
+
if (config.forge === 'github') {
|
|
7
|
+
return config.github.repo ? `https://github.com/${config.github.repo}` : '(not configured)';
|
|
8
|
+
}
|
|
9
|
+
return config.gitlab.host ? `${config.gitlab.host} (project ${config.gitlab.projectId})` : '(not configured)';
|
|
10
|
+
}
|
|
11
|
+
export function agentDescription(config) {
|
|
12
|
+
// Mirrors captureIntent.ts's INTENT_MODEL choice: only claude supports
|
|
13
|
+
// per-invocation model selection today, so only claude has a "mode" to show.
|
|
14
|
+
return config.agent === 'claude' ? 'claude (haiku for intent capture, default model for CI fixes)' : 'copilot';
|
|
15
|
+
}
|
|
16
|
+
export async function printWelcome(config, repoRoot) {
|
|
17
|
+
const user = await getGitUser(repoRoot);
|
|
18
|
+
const rows = [
|
|
19
|
+
['Agent', agentDescription(config)],
|
|
20
|
+
['Forge', config.forge],
|
|
21
|
+
['Repository', repositoryUrl(config)],
|
|
22
|
+
['Git user', user.name && user.email ? `${user.name} <${user.email}>` : '(not configured)'],
|
|
23
|
+
];
|
|
24
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
25
|
+
const rule = styleText('cyan', '─'.repeat(RULE_WIDTH));
|
|
26
|
+
console.log(rule);
|
|
27
|
+
console.log(styleText(['bold', 'cyan'], ' 🚀 pipeline-worker'));
|
|
28
|
+
console.log(rule);
|
|
29
|
+
for (const [label, value] of rows) {
|
|
30
|
+
console.log(` ${styleText('cyan', label.padEnd(labelWidth))} ${value}`);
|
|
31
|
+
}
|
|
32
|
+
console.log(rule);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=welcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/ui/welcome.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,UAAU,aAAa,CAAC,MAA4B;IACxD,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,sBAAsB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC9F,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,aAAa,MAAM,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC;AAChH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAA4B;IAC3D,uEAAuE;IACvE,6EAA6E;IAC7E,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,+DAA+D,CAAC,CAAC,CAAC,SAAS,CAAC;AACjH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAA4B,EAAE,QAAgB;IAC/E,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,IAAI,GAA4B;QACpC,CAAC,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;QACvB,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC;KAC5F,CAAC;IACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAEvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"}
|