git-vibe-setup 3.0.1 → 3.0.3

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 CHANGED
@@ -3,9 +3,19 @@
3
3
  Local initializer for GitVibe consumer repositories.
4
4
 
5
5
  ```bash
6
- npx --package=git-vibe-setup git-vibe-setup
6
+ npx git-vibe-setup setup
7
7
  ```
8
8
 
9
- The command writes `.github` and `.git-vibe` starter files, pins reusable
9
+ The `setup` command writes `.github` and `.git-vibe` starter files, pins reusable
10
10
  workflow refs to the latest stable `markhuangai/git-vibe` release, and fails
11
11
  before writing if release lookup or target-file validation fails.
12
+
13
+ ```bash
14
+ npx git-vibe-setup update
15
+ ```
16
+
17
+ The `update` command rewrites only `.github/workflows/*.yml` GitVibe wrapper
18
+ files from the latest package templates and pins them to the latest stable
19
+ release. It does not update `.github/git-vibe.yml`, `.git-vibe`, secrets, or
20
+ variables, and it refuses to overwrite workflow files that do not look like
21
+ GitVibe wrappers.
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  interface SetupCliRuntime {
3
+ argv?: string[];
3
4
  cwd?: string;
4
5
  error?: (message: string) => void;
5
6
  fetchImpl?: typeof fetch;
@@ -7,5 +8,7 @@ interface SetupCliRuntime {
7
8
  repositoryRoot?: string;
8
9
  }
9
10
  export declare function runSetup(runtime?: SetupCliRuntime): Promise<void>;
11
+ export declare function runUpdate(runtime?: SetupCliRuntime): Promise<void>;
10
12
  export declare function setupCli(runtime?: SetupCliRuntime): Promise<number>;
13
+ export declare function isDirectRun(moduleUrl: string, entrypoint?: string): boolean;
11
14
  export {};
package/dist/cli.js CHANGED
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
  import { fileURLToPath } from "node:url";
5
- import { blockingInstallPaths, buildInstallFiles, existingFilesError, installFiles, } from "./install.js";
6
+ import { blockingInstallPaths, buildInstallFiles, buildWorkflowUpdateFiles, existingFilesError, installFiles, unmanagedWorkflowUpdateError, unmanagedWorkflowUpdatePaths, updateFiles, } from "./install.js";
6
7
  import { renderManualSetupInstructions } from "./instructions.js";
7
8
  import { latestStableReleaseTag } from "./releases.js";
9
+ const usage = `Usage:
10
+ git-vibe-setup setup
11
+ git-vibe-setup update
12
+ git-vibe-setup
13
+
14
+ Commands:
15
+ setup Install GitVibe starter files into the current repository.
16
+ update Update GitVibe workflow wrapper files in the current repository.
17
+
18
+ Options:
19
+ -h, --help Show this help message.`;
8
20
  export async function runSetup(runtime = {}) {
9
21
  const cwd = runtime.cwd || process.cwd();
10
22
  const repositoryRoot = runtime.repositoryRoot || packageRoot();
@@ -16,9 +28,33 @@ export async function runSetup(runtime = {}) {
16
28
  installFiles(files);
17
29
  (runtime.log || console.log)(renderManualSetupInstructions(releaseTag));
18
30
  }
31
+ export async function runUpdate(runtime = {}) {
32
+ const cwd = runtime.cwd || process.cwd();
33
+ const repositoryRoot = runtime.repositoryRoot || packageRoot();
34
+ const releaseTag = await latestStableReleaseTag(runtime.fetchImpl || fetch);
35
+ const files = buildWorkflowUpdateFiles({ cwd, releaseTag, repositoryRoot });
36
+ const unmanagedPaths = unmanagedWorkflowUpdatePaths(files);
37
+ if (unmanagedPaths.length > 0)
38
+ throw unmanagedWorkflowUpdateError(unmanagedPaths, cwd);
39
+ updateFiles(files);
40
+ (runtime.log || console.log)(`GitVibe workflow files updated with reusable workflows pinned to ${releaseTag}.`);
41
+ }
19
42
  export async function setupCli(runtime = {}) {
43
+ const argv = runtime.argv || process.argv.slice(2);
44
+ const command = argv[0] || "setup";
45
+ if (command === "--help" || command === "-h" || command === "help") {
46
+ (runtime.log || console.log)(usage);
47
+ return 0;
48
+ }
49
+ if (command !== "setup" && command !== "update") {
50
+ (runtime.error || console.error)(`Unknown command: ${command}\n\n${usage}`);
51
+ return 1;
52
+ }
20
53
  try {
21
- await runSetup(runtime);
54
+ if (command === "update")
55
+ await runUpdate(runtime);
56
+ else
57
+ await runSetup(runtime);
22
58
  return 0;
23
59
  }
24
60
  catch (error) {
@@ -36,6 +72,12 @@ if (isDirectRun(import.meta.url)) {
36
72
  process.exitCode = exitCode;
37
73
  }
38
74
  /* c8 ignore stop */
39
- function isDirectRun(moduleUrl, entrypoint = process.argv[1]) {
40
- return Boolean(entrypoint && moduleUrl === pathToFileURL(resolve(entrypoint)).href);
75
+ export function isDirectRun(moduleUrl, entrypoint = process.argv[1]) {
76
+ if (!entrypoint)
77
+ return false;
78
+ return (pathToFileURL(realpathSync(resolve(entrypoint))).href ===
79
+ pathToFileURL(modulePath(moduleUrl)).href);
80
+ }
81
+ function modulePath(moduleUrl) {
82
+ return realpathSync(fileURLToPath(moduleUrl));
41
83
  }
package/dist/install.d.ts CHANGED
@@ -8,7 +8,15 @@ export declare function buildInstallFiles(options: {
8
8
  releaseTag: string;
9
9
  repositoryRoot: string;
10
10
  }): InstallFile[];
11
+ export declare function buildWorkflowUpdateFiles(options: {
12
+ cwd: string;
13
+ releaseTag: string;
14
+ repositoryRoot: string;
15
+ }): InstallFile[];
11
16
  export declare function blockingInstallPaths(files: InstallFile[]): string[];
17
+ export declare function unmanagedWorkflowUpdatePaths(files: InstallFile[]): string[];
12
18
  export declare function installFiles(files: InstallFile[]): void;
19
+ export declare function updateFiles(files: InstallFile[]): void;
13
20
  export declare function existingFilesError(paths: string[], cwd: string): Error;
21
+ export declare function unmanagedWorkflowUpdateError(paths: string[], cwd: string): Error;
14
22
  export declare function pinWorkflowReleaseRefs(content: string, releaseTag: string): string;
package/dist/install.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { dirname, join, relative } from "node:path";
2
+ import { basename, dirname, join, relative } from "node:path";
3
3
  export function buildInstallFiles(options) {
4
4
  return installSources(options.repositoryRoot).flatMap((source) => listRelativeFiles(source.sourceDirectory).map((relativePath) => {
5
5
  const sourcePath = join(source.sourceDirectory, relativePath);
@@ -12,12 +12,31 @@ export function buildInstallFiles(options) {
12
12
  };
13
13
  }));
14
14
  }
15
+ export function buildWorkflowUpdateFiles(options) {
16
+ const sourceDirectory = join(options.repositoryRoot, "templates", ".github", "workflows");
17
+ return listRelativeFiles(sourceDirectory).map((relativePath) => {
18
+ const sourcePath = join(sourceDirectory, relativePath);
19
+ const targetPath = join(options.cwd, ".github", "workflows", relativePath);
20
+ const content = readFileSync(sourcePath, "utf8");
21
+ return {
22
+ content: pinWorkflowReleaseRefs(content, options.releaseTag),
23
+ sourcePath,
24
+ targetPath,
25
+ };
26
+ });
27
+ }
15
28
  export function blockingInstallPaths(files) {
16
29
  return files
17
30
  .map((file) => file.targetPath)
18
31
  .filter((targetPath) => existsSync(targetPath))
19
32
  .sort();
20
33
  }
34
+ export function unmanagedWorkflowUpdatePaths(files) {
35
+ return files
36
+ .filter((file) => existsSync(file.targetPath) && !isManagedWorkflowTarget(file))
37
+ .map((file) => file.targetPath)
38
+ .sort();
39
+ }
21
40
  export function installFiles(files) {
22
41
  const createdDirectories = [];
23
42
  const createdFiles = [];
@@ -33,10 +52,29 @@ export function installFiles(files) {
33
52
  throw error;
34
53
  }
35
54
  }
55
+ export function updateFiles(files) {
56
+ const createdDirectories = [];
57
+ const snapshots = [];
58
+ try {
59
+ for (const file of files) {
60
+ ensureDirectory(dirname(file.targetPath), createdDirectories);
61
+ snapshots.push(snapshotFile(file.targetPath));
62
+ writeFileSync(file.targetPath, file.content);
63
+ }
64
+ }
65
+ catch (error) {
66
+ rollbackUpdate(snapshots, createdDirectories);
67
+ throw error;
68
+ }
69
+ }
36
70
  export function existingFilesError(paths, cwd) {
37
71
  const listed = paths.map((path) => `- ${relative(cwd, path) || path}`).join("\n");
38
72
  return new Error(`git-vibe-setup found existing GitVibe files and did not overwrite them:\n${listed}\nRemove the listed files before running setup again.`);
39
73
  }
74
+ export function unmanagedWorkflowUpdateError(paths, cwd) {
75
+ const listed = paths.map((path) => `- ${relative(cwd, path) || path}`).join("\n");
76
+ return new Error(`git-vibe-setup found workflow files that do not look like GitVibe wrappers and did not overwrite them:\n${listed}`);
77
+ }
40
78
  export function pinWorkflowReleaseRefs(content, releaseTag) {
41
79
  return content.replace(/(uses:\s*markhuangai\/git-vibe\/\.github\/workflows\/[^\s@]+)@[^\s]+/g, (_match, workflowReference) => `${workflowReference}@${releaseTag}`);
42
80
  }
@@ -52,6 +90,21 @@ function installSources(repositoryRoot) {
52
90
  },
53
91
  ];
54
92
  }
93
+ function isManagedWorkflowTarget(file) {
94
+ try {
95
+ const workflowName = basename(file.targetPath);
96
+ return managedWorkflowPattern(workflowName).test(readFileSync(file.targetPath, "utf8"));
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ function managedWorkflowPattern(workflowName) {
103
+ return new RegExp(`uses:\\s*markhuangai/git-vibe/\\.github/workflows/${escapeRegExp(workflowName)}@`);
104
+ }
105
+ function escapeRegExp(value) {
106
+ return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
107
+ }
55
108
  function listRelativeFiles(directory) {
56
109
  return readdirSync(directory, { withFileTypes: true })
57
110
  .flatMap((entry) => {
@@ -64,6 +117,15 @@ function listRelativeFiles(directory) {
64
117
  })
65
118
  .sort();
66
119
  }
120
+ function snapshotFile(targetPath) {
121
+ if (!existsSync(targetPath))
122
+ return { existed: false, targetPath };
123
+ return {
124
+ content: readFileSync(targetPath, "utf8"),
125
+ existed: true,
126
+ targetPath,
127
+ };
128
+ }
67
129
  function ensureDirectory(directory, createdDirectories) {
68
130
  const missing = missingDirectories(directory);
69
131
  for (const path of missing) {
@@ -84,6 +146,19 @@ function missingDirectories(directory) {
84
146
  }
85
147
  return missing.reverse();
86
148
  }
149
+ function rollbackUpdate(snapshots, createdDirectories) {
150
+ for (const snapshot of [...snapshots].reverse()) {
151
+ if (snapshot.existed) {
152
+ writeFileSync(snapshot.targetPath, snapshot.content || "");
153
+ }
154
+ else {
155
+ rmSync(snapshot.targetPath, { force: true });
156
+ }
157
+ }
158
+ for (const directory of [...createdDirectories].reverse()) {
159
+ rmSync(directory, { force: true, recursive: false });
160
+ }
161
+ }
87
162
  function rollbackInstall(createdFiles, createdDirectories) {
88
163
  for (const file of [...createdFiles].reverse()) {
89
164
  rmSync(file, { force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-vibe-setup",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,16 @@ on:
8
8
  description: Pull request number with feedback to address.
9
9
  required: true
10
10
  type: string
11
+ timeout_minutes:
12
+ description: Maximum minutes for the feedback remediation job.
13
+ required: false
14
+ type: number
15
+ default: 120
16
+ max_turns:
17
+ description: Maximum AI turns for feedback remediation.
18
+ required: false
19
+ type: number
20
+ default: 120
11
21
  dry-run:
12
22
  description: Validate without writing to GitHub.
13
23
  required: false
@@ -25,8 +35,8 @@ jobs:
25
35
  with:
26
36
  pr-number: ${{ inputs.pr-number }}
27
37
  runner: ubuntu-latest
28
- timeout_minutes: 120
29
- max_turns: 120
38
+ timeout_minutes: ${{ inputs.timeout_minutes }}
39
+ max_turns: ${{ inputs.max_turns }}
30
40
  dry-run: ${{ inputs.dry-run }}
31
41
  source-comment: ${{ inputs.source-comment }}
32
42
  secrets:
@@ -8,6 +8,41 @@ on:
8
8
  description: Approved implementation issue number.
9
9
  required: true
10
10
  type: string
11
+ implementation_timeout_minutes:
12
+ description: Maximum minutes for the implementation job.
13
+ required: false
14
+ type: number
15
+ default: 120
16
+ review_timeout_minutes:
17
+ description: Maximum minutes for the review matrix job.
18
+ required: false
19
+ type: number
20
+ default: 60
21
+ create_pr_timeout_minutes:
22
+ description: Maximum minutes for the PR creation job.
23
+ required: false
24
+ type: number
25
+ default: 15
26
+ max_turns:
27
+ description: Default maximum AI turns for stages in this workflow.
28
+ required: false
29
+ type: number
30
+ default: 90
31
+ implementation_max_turns:
32
+ description: Maximum AI turns for implementation and feedback-style coding work.
33
+ required: false
34
+ type: number
35
+ default: 200
36
+ validation_repair_attempts:
37
+ description: Maximum validation repair attempts inside each implementation run.
38
+ required: false
39
+ type: number
40
+ default: 3
41
+ validation_repair_max_turns:
42
+ description: Maximum AI turns for each validation repair attempt.
43
+ required: false
44
+ type: number
45
+ default: 45
11
46
  dry-run:
12
47
  description: Validate without writing to GitHub.
13
48
  required: false
@@ -25,13 +60,13 @@ jobs:
25
60
  with:
26
61
  issue-number: ${{ inputs.issue-number }}
27
62
  runner: ubuntu-latest
28
- implementation_timeout_minutes: 120
29
- review_timeout_minutes: 60
30
- create_pr_timeout_minutes: 15
31
- max_turns: 90
32
- implementation_max_turns: 120
33
- validation_repair_attempts: 2
34
- validation_repair_max_turns: 90
63
+ implementation_timeout_minutes: ${{ inputs.implementation_timeout_minutes }}
64
+ review_timeout_minutes: ${{ inputs.review_timeout_minutes }}
65
+ create_pr_timeout_minutes: ${{ inputs.create_pr_timeout_minutes }}
66
+ max_turns: ${{ inputs.max_turns }}
67
+ implementation_max_turns: ${{ inputs.implementation_max_turns }}
68
+ validation_repair_attempts: ${{ inputs.validation_repair_attempts }}
69
+ validation_repair_max_turns: ${{ inputs.validation_repair_max_turns }}
35
70
  dry-run: ${{ inputs.dry-run }}
36
71
  source-comment: ${{ inputs.source-comment }}
37
72
  secrets:
@@ -8,6 +8,16 @@ on:
8
8
  description: Issue number to investigate.
9
9
  required: true
10
10
  type: string
11
+ timeout_minutes:
12
+ description: Maximum minutes for the investigation job.
13
+ required: false
14
+ type: number
15
+ default: 60
16
+ max_turns:
17
+ description: Maximum AI turns for this stage.
18
+ required: false
19
+ type: number
20
+ default: 90
11
21
  dry-run:
12
22
  description: Validate without writing to GitHub.
13
23
  required: false
@@ -25,8 +35,8 @@ jobs:
25
35
  with:
26
36
  issue-number: ${{ inputs.issue-number }}
27
37
  runner: ubuntu-latest
28
- timeout_minutes: 60
29
- max_turns: 90
38
+ timeout_minutes: ${{ inputs.timeout_minutes }}
39
+ max_turns: ${{ inputs.max_turns }}
30
40
  dry-run: ${{ inputs.dry-run }}
31
41
  source-comment: ${{ inputs.source-comment }}
32
42
  secrets:
@@ -8,6 +8,16 @@ on:
8
8
  description: Discussion number to materialize.
9
9
  required: true
10
10
  type: string
11
+ timeout_minutes:
12
+ description: Maximum minutes for this job.
13
+ required: false
14
+ type: number
15
+ default: 60
16
+ max_turns:
17
+ description: Maximum AI turns for this stage.
18
+ required: false
19
+ type: number
20
+ default: 90
11
21
  dry-run:
12
22
  description: Validate without writing to GitHub.
13
23
  required: false
@@ -25,8 +35,8 @@ jobs:
25
35
  with:
26
36
  discussion-number: ${{ inputs.discussion-number }}
27
37
  runner: ubuntu-latest
28
- timeout_minutes: 60
29
- max_turns: 90
38
+ timeout_minutes: ${{ inputs.timeout_minutes }}
39
+ max_turns: ${{ inputs.max_turns }}
30
40
  dry-run: ${{ inputs.dry-run }}
31
41
  source-comment: ${{ inputs.source-comment }}
32
42
  secrets:
@@ -8,6 +8,16 @@ on:
8
8
  description: Pull request number to review.
9
9
  required: true
10
10
  type: string
11
+ timeout_minutes:
12
+ description: Maximum minutes for the review matrix job.
13
+ required: false
14
+ type: number
15
+ default: 60
16
+ max_turns:
17
+ description: Maximum AI turns for the review stage.
18
+ required: false
19
+ type: number
20
+ default: 90
11
21
  dry-run:
12
22
  description: Validate without writing to GitHub.
13
23
  required: false
@@ -25,8 +35,8 @@ jobs:
25
35
  with:
26
36
  pr-number: ${{ inputs.pr-number }}
27
37
  runner: ubuntu-latest
28
- timeout_minutes: 60
29
- max_turns: 90
38
+ timeout_minutes: ${{ inputs.timeout_minutes }}
39
+ max_turns: ${{ inputs.max_turns }}
30
40
  dry-run: ${{ inputs.dry-run }}
31
41
  source-comment: ${{ inputs.source-comment }}
32
42
  secrets:
@@ -14,6 +14,16 @@ on:
14
14
  required: false
15
15
  type: string
16
16
  default: ""
17
+ timeout_minutes:
18
+ description: Maximum minutes for this job.
19
+ required: false
20
+ type: number
21
+ default: 60
22
+ max_turns:
23
+ description: Maximum AI turns for this stage.
24
+ required: false
25
+ type: number
26
+ default: 90
17
27
  dry-run:
18
28
  description: Validate without writing to GitHub.
19
29
  required: false
@@ -32,8 +42,8 @@ jobs:
32
42
  issue-number: ${{ inputs.issue-number }}
33
43
  discussion-number: ${{ inputs.discussion-number }}
34
44
  runner: ubuntu-latest
35
- timeout_minutes: 60
36
- max_turns: 90
45
+ timeout_minutes: ${{ inputs.timeout_minutes }}
46
+ max_turns: ${{ inputs.max_turns }}
37
47
  dry-run: ${{ inputs.dry-run }}
38
48
  source-comment: ${{ inputs.source-comment }}
39
49
  secrets: