@samaksh/git-worker 0.0.0-stage → 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 (115) hide show
  1. package/GIT_WORKER_PROJECT_PLAN.md +499 -0
  2. package/README.md +114 -2
  3. package/demo/README.md +58 -0
  4. package/demo/index.ts +526 -0
  5. package/dist/app.d.ts +2 -0
  6. package/dist/app.js +48 -0
  7. package/dist/cli/commands.d.ts +6 -0
  8. package/dist/cli/commands.js +111 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +115 -0
  11. package/dist/config/defaults.d.ts +2 -0
  12. package/dist/config/defaults.js +44 -0
  13. package/dist/config/load.d.ts +3 -0
  14. package/dist/config/load.js +65 -0
  15. package/dist/config/schema.d.ts +200 -0
  16. package/dist/config/schema.js +45 -0
  17. package/dist/core/errors.d.ts +12 -0
  18. package/dist/core/errors.js +34 -0
  19. package/dist/core/pipeline.d.ts +3 -0
  20. package/dist/core/pipeline.js +14 -0
  21. package/dist/core/types.d.ts +153 -0
  22. package/dist/core/types.js +16 -0
  23. package/dist/services/editor/index.d.ts +9 -0
  24. package/dist/services/editor/index.js +59 -0
  25. package/dist/services/git/ops.d.ts +30 -0
  26. package/dist/services/git/ops.js +139 -0
  27. package/dist/services/git/reader.d.ts +14 -0
  28. package/dist/services/git/reader.js +124 -0
  29. package/dist/services/report/index.d.ts +18 -0
  30. package/dist/services/report/index.js +68 -0
  31. package/dist/services/review/index.d.ts +30 -0
  32. package/dist/services/review/index.js +59 -0
  33. package/dist/services/state/index.d.ts +20 -0
  34. package/dist/services/state/index.js +49 -0
  35. package/dist/services/template/env.d.ts +23 -0
  36. package/dist/services/template/env.js +146 -0
  37. package/dist/services/template/interpolator.d.ts +30 -0
  38. package/dist/services/template/interpolator.js +75 -0
  39. package/dist/services/template/transform.d.ts +22 -0
  40. package/dist/services/template/transform.js +75 -0
  41. package/dist/services/template/validator.d.ts +5 -0
  42. package/dist/services/template/validator.js +68 -0
  43. package/dist/services/workspace/cache.d.ts +32 -0
  44. package/dist/services/workspace/cache.js +107 -0
  45. package/dist/services/workspace/manager.d.ts +44 -0
  46. package/dist/services/workspace/manager.js +301 -0
  47. package/dist/stages/apply.d.ts +3 -0
  48. package/dist/stages/apply.js +161 -0
  49. package/dist/stages/edit-template.d.ts +3 -0
  50. package/dist/stages/edit-template.js +198 -0
  51. package/dist/stages/eligibility.d.ts +3 -0
  52. package/dist/stages/eligibility.js +44 -0
  53. package/dist/stages/load-template.d.ts +3 -0
  54. package/dist/stages/load-template.js +168 -0
  55. package/dist/stages/plan.d.ts +3 -0
  56. package/dist/stages/plan.js +270 -0
  57. package/dist/stages/push.d.ts +3 -0
  58. package/dist/stages/push.js +99 -0
  59. package/dist/stages/report.d.ts +3 -0
  60. package/dist/stages/report.js +103 -0
  61. package/dist/stages/resolve-project.d.ts +3 -0
  62. package/dist/stages/resolve-project.js +294 -0
  63. package/dist/stages/review.d.ts +3 -0
  64. package/dist/stages/review.js +197 -0
  65. package/dist/stages/select.d.ts +13 -0
  66. package/dist/stages/select.js +250 -0
  67. package/dist/stages/sync.d.ts +3 -0
  68. package/dist/stages/sync.js +47 -0
  69. package/dist/utils/hash.d.ts +2 -0
  70. package/dist/utils/hash.js +7 -0
  71. package/dist/utils/ui.d.ts +21 -0
  72. package/dist/utils/ui.js +113 -0
  73. package/dist/utils/which.d.ts +5 -0
  74. package/dist/utils/which.js +16 -0
  75. package/docs/FEATURES.md +84 -0
  76. package/docs/SERVICES.md +57 -0
  77. package/docs/architecture.md +38 -0
  78. package/docs/usage.md +337 -0
  79. package/package.json +48 -4
  80. package/src/app.ts +69 -0
  81. package/src/cli/commands.ts +142 -0
  82. package/src/cli/index.ts +123 -0
  83. package/src/config/defaults.ts +46 -0
  84. package/src/config/load.ts +70 -0
  85. package/src/config/schema.ts +48 -0
  86. package/src/core/errors.ts +45 -0
  87. package/src/core/pipeline.ts +17 -0
  88. package/src/core/types.ts +188 -0
  89. package/src/services/editor/index.ts +85 -0
  90. package/src/services/git/ops.ts +170 -0
  91. package/src/services/git/reader.ts +148 -0
  92. package/src/services/report/index.ts +104 -0
  93. package/src/services/review/index.ts +80 -0
  94. package/src/services/state/index.ts +77 -0
  95. package/src/services/template/env.ts +169 -0
  96. package/src/services/template/interpolator.ts +109 -0
  97. package/src/services/template/transform.ts +97 -0
  98. package/src/services/template/validator.ts +75 -0
  99. package/src/services/workspace/cache.ts +144 -0
  100. package/src/services/workspace/manager.ts +370 -0
  101. package/src/stages/apply.ts +194 -0
  102. package/src/stages/edit-template.ts +227 -0
  103. package/src/stages/eligibility.ts +67 -0
  104. package/src/stages/load-template.ts +188 -0
  105. package/src/stages/plan.ts +362 -0
  106. package/src/stages/push.ts +120 -0
  107. package/src/stages/report.ts +126 -0
  108. package/src/stages/resolve-project.ts +341 -0
  109. package/src/stages/review.ts +228 -0
  110. package/src/stages/select.ts +302 -0
  111. package/src/stages/sync.ts +63 -0
  112. package/src/utils/hash.ts +15 -0
  113. package/src/utils/ui.ts +137 -0
  114. package/src/utils/which.ts +18 -0
  115. package/tsconfig.json +18 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { run } from "../app.js";
4
+ import { handleCacheList, handleCacheClean } from "./commands.js";
5
+ import chalk from "chalk";
6
+ const program = new Command();
7
+ program
8
+ .name("git-worker")
9
+ .description("Safe, composable CLI/TUI for applying reviewed file changes across many Git branches")
10
+ .version("0.1.0")
11
+ .option("-r, --repo <path>", "Repository path or remote URL (default: current directory)")
12
+ .option("-c, --cached", "Select from previously cloned and cached repositories")
13
+ .option("-b, --branch <pattern>", "Branch pattern — glob (uat/*) or regex (/uat.*/)")
14
+ .option("-i, --invert-branch", "Invert the branch selection pattern")
15
+ .option("-f, --file <path>", "Target file path inside the repository (e.g. .env)")
16
+ .option("-e, --editor <name>", "Editor to use: code (VS Code) or vim")
17
+ .option("-m, --commit-msg <message>", "Commit message")
18
+ .option("-p, --push <policy>", "Push policy: prompt (default) | always | never")
19
+ .option("--remote <name>", "Git remote name (default: origin)")
20
+ .option("--auto-approve", "Auto-approve all changes (skip interactive review)")
21
+ .option("--ci", "CI mode: non-interactive, fail on missing dynamic values")
22
+ .action(async (opts) => {
23
+ // Validate push policy
24
+ if (opts.push && !["prompt", "always", "never"].includes(opts.push)) {
25
+ console.error(chalk.red(` ✗ Invalid push policy '${opts.push}'. Must be: prompt | always | never`));
26
+ process.exit(1);
27
+ }
28
+ // Validate incompatible combinations
29
+ if (opts.ci && opts.push === "prompt") {
30
+ console.error(chalk.yellow(` ⚠ Warning: --ci with --push prompt will not push (no interactive prompt in CI). Use --push always or --push never.`));
31
+ }
32
+ try {
33
+ const result = await run({
34
+ repo: opts.repo,
35
+ cached: opts.cached ?? false,
36
+ branch: opts.branch,
37
+ invertBranch: opts.invertBranch ?? false,
38
+ file: opts.file,
39
+ editor: opts.editor,
40
+ commitMsg: opts.commitMsg,
41
+ push: opts.push,
42
+ remote: opts.remote,
43
+ autoApprove: opts.autoApprove ?? false,
44
+ ci: opts.ci ?? false,
45
+ });
46
+ if (!result.ok) {
47
+ const { error } = result;
48
+ if (error.category === "INPUT_CANCELLED") {
49
+ console.log(chalk.gray("\n Cancelled."));
50
+ process.exit(0);
51
+ }
52
+ console.error(chalk.red(`\n ✗ [${error.category}] ${error.message}`));
53
+ process.exit(1);
54
+ }
55
+ process.exit(0);
56
+ }
57
+ catch (e) {
58
+ console.error(chalk.red(`\n ✗ Fatal: ${e.message}`));
59
+ if (process.env.DEBUG) {
60
+ console.error(e.stack);
61
+ }
62
+ process.exit(1);
63
+ }
64
+ });
65
+ // ── Cache management subcommands ──────────────────────────────────────────
66
+ const cacheCmd = program
67
+ .command("cache")
68
+ .description("Manage git-worker's cached workspaces and repositories (~/.git-worker)");
69
+ cacheCmd
70
+ .command("list")
71
+ .alias("ls")
72
+ .description("List all cached projects")
73
+ .action(async () => {
74
+ try {
75
+ await handleCacheList();
76
+ process.exit(0);
77
+ }
78
+ catch (e) {
79
+ console.error(chalk.red(` ✗ Error: ${e.message}`));
80
+ process.exit(1);
81
+ }
82
+ });
83
+ cacheCmd
84
+ .command("clean [target]")
85
+ .alias("clear")
86
+ .description("Clean workspace cache (all or a specific project)")
87
+ .option("-a, --all", "Clean all cached projects")
88
+ .option("-y, --yes", "Skip confirmation prompt")
89
+ .action(async (target, opts) => {
90
+ try {
91
+ await handleCacheClean(target, opts);
92
+ process.exit(0);
93
+ }
94
+ catch (e) {
95
+ console.error(chalk.red(` ✗ Error: ${e.message}`));
96
+ process.exit(1);
97
+ }
98
+ });
99
+ // Convenient top-level alias: git-worker clean [target] [-a]
100
+ program
101
+ .command("clean [target]")
102
+ .description("Shorthand to clean cached workspace(s)")
103
+ .option("-a, --all", "Clean all cached projects")
104
+ .option("-y, --yes", "Skip confirmation prompt")
105
+ .action(async (target, opts) => {
106
+ try {
107
+ await handleCacheClean(target, opts);
108
+ process.exit(0);
109
+ }
110
+ catch (e) {
111
+ console.error(chalk.red(` ✗ Error: ${e.message}`));
112
+ process.exit(1);
113
+ }
114
+ });
115
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { AppConfig } from "./schema.js";
2
+ export declare const DEFAULT_CONFIG: AppConfig;
@@ -0,0 +1,44 @@
1
+ export const DEFAULT_CONFIG = {
2
+ git: {
3
+ remote: "origin",
4
+ fetchPrune: true,
5
+ cleanMode: "tracked-and-untracked",
6
+ pushPolicy: "prompt",
7
+ commitMessage: "chore: sync configuration",
8
+ },
9
+ workspace: {
10
+ root: ".git-worker",
11
+ lockTimeoutMs: 30000,
12
+ },
13
+ editor: {
14
+ name: "code",
15
+ editCommand: ["code", "--wait", "{file}"],
16
+ diffCommand: ["code", "--diff", "{before}", "{after}"],
17
+ },
18
+ template: {
19
+ placeholderOpen: "{",
20
+ placeholderClose: "}",
21
+ missingValuePolicy: "prompt-each-branch",
22
+ emptyValueIsPresent: true,
23
+ unmanagedKeyPolicy: "preserve",
24
+ removedTemplateKeyPolicy: "preserve",
25
+ },
26
+ review: {
27
+ autoApprove: false,
28
+ readOnlySnapshots: true,
29
+ },
30
+ execution: {
31
+ concurrency: 1,
32
+ stopOnBranchError: false,
33
+ },
34
+ report: {
35
+ writeJson: false,
36
+ writeMarkdown: false,
37
+ redactValues: true,
38
+ warnOnSecrets: true,
39
+ },
40
+ safety: {
41
+ rejectNoRemote: true,
42
+ saveOnPartialSuccess: true,
43
+ },
44
+ };
@@ -0,0 +1,3 @@
1
+ import { AppConfig } from "./schema.js";
2
+ import { CliInput } from "../core/types.js";
3
+ export declare function loadConfig(cliInput: CliInput): AppConfig;
@@ -0,0 +1,65 @@
1
+ import { AppConfigSchema } from "./schema.js";
2
+ import { DEFAULT_CONFIG } from "./defaults.js";
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { join, resolve } from "path";
5
+ function loadProjectConfig(repoPath) {
6
+ const candidates = [
7
+ join(resolve(repoPath), "git-worker.config.json"),
8
+ join(resolve(repoPath), ".git-worker.config.json"),
9
+ ];
10
+ for (const candidate of candidates) {
11
+ if (existsSync(candidate)) {
12
+ try {
13
+ const raw = readFileSync(candidate, "utf-8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ // ignore malformed
18
+ }
19
+ }
20
+ }
21
+ return {};
22
+ }
23
+ function deepMerge(base, override) {
24
+ const result = { ...base };
25
+ for (const key of Object.keys(override)) {
26
+ const overrideVal = override[key];
27
+ if (overrideVal === undefined)
28
+ continue;
29
+ if (typeof overrideVal === "object" &&
30
+ overrideVal !== null &&
31
+ !Array.isArray(overrideVal) &&
32
+ typeof result[key] === "object" &&
33
+ result[key] !== null) {
34
+ result[key] = deepMerge(result[key], overrideVal);
35
+ }
36
+ else {
37
+ result[key] = overrideVal;
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ export function loadConfig(cliInput) {
43
+ // Load project config from repo directory
44
+ const repoPath = cliInput.repo || process.cwd();
45
+ const projectConfig = loadProjectConfig(repoPath);
46
+ // Merge: defaults < project config < CLI overrides
47
+ const cliOverrides = {
48
+ git: {
49
+ ...DEFAULT_CONFIG.git,
50
+ ...(cliInput.push ? { pushPolicy: cliInput.push } : {}),
51
+ ...(cliInput.commitMsg ? { commitMessage: cliInput.commitMsg } : {}),
52
+ ...(cliInput.remote ? { remote: cliInput.remote } : {}),
53
+ },
54
+ editor: {
55
+ ...DEFAULT_CONFIG.editor,
56
+ ...(cliInput.editor ? { name: cliInput.editor } : {}),
57
+ },
58
+ review: {
59
+ ...DEFAULT_CONFIG.review,
60
+ ...(cliInput.autoApprove !== undefined ? { autoApprove: cliInput.autoApprove } : {}),
61
+ },
62
+ };
63
+ const merged = deepMerge(deepMerge(DEFAULT_CONFIG, projectConfig), cliOverrides);
64
+ return Object.freeze(AppConfigSchema.parse(merged));
65
+ }
@@ -0,0 +1,200 @@
1
+ import { z } from "zod";
2
+ export declare const AppConfigSchema: z.ZodObject<{
3
+ git: z.ZodObject<{
4
+ remote: z.ZodDefault<z.ZodString>;
5
+ fetchPrune: z.ZodDefault<z.ZodBoolean>;
6
+ cleanMode: z.ZodDefault<z.ZodEnum<["tracked-and-untracked", "tracked-only"]>>;
7
+ pushPolicy: z.ZodDefault<z.ZodEnum<["prompt", "always", "never"]>>;
8
+ commitMessage: z.ZodDefault<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ remote: string;
11
+ fetchPrune: boolean;
12
+ cleanMode: "tracked-and-untracked" | "tracked-only";
13
+ pushPolicy: "never" | "prompt" | "always";
14
+ commitMessage: string;
15
+ }, {
16
+ remote?: string | undefined;
17
+ fetchPrune?: boolean | undefined;
18
+ cleanMode?: "tracked-and-untracked" | "tracked-only" | undefined;
19
+ pushPolicy?: "never" | "prompt" | "always" | undefined;
20
+ commitMessage?: string | undefined;
21
+ }>;
22
+ workspace: z.ZodObject<{
23
+ root: z.ZodDefault<z.ZodString>;
24
+ lockTimeoutMs: z.ZodDefault<z.ZodNumber>;
25
+ }, "strip", z.ZodTypeAny, {
26
+ root: string;
27
+ lockTimeoutMs: number;
28
+ }, {
29
+ root?: string | undefined;
30
+ lockTimeoutMs?: number | undefined;
31
+ }>;
32
+ editor: z.ZodObject<{
33
+ name: z.ZodDefault<z.ZodString>;
34
+ editCommand: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
35
+ diffCommand: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ name: string;
38
+ editCommand: string[];
39
+ diffCommand: string[];
40
+ }, {
41
+ name?: string | undefined;
42
+ editCommand?: string[] | undefined;
43
+ diffCommand?: string[] | undefined;
44
+ }>;
45
+ template: z.ZodObject<{
46
+ placeholderOpen: z.ZodDefault<z.ZodString>;
47
+ placeholderClose: z.ZodDefault<z.ZodString>;
48
+ missingValuePolicy: z.ZodDefault<z.ZodEnum<["prompt-each-branch"]>>;
49
+ emptyValueIsPresent: z.ZodDefault<z.ZodBoolean>;
50
+ unmanagedKeyPolicy: z.ZodDefault<z.ZodEnum<["preserve"]>>;
51
+ removedTemplateKeyPolicy: z.ZodDefault<z.ZodEnum<["preserve"]>>;
52
+ }, "strip", z.ZodTypeAny, {
53
+ placeholderOpen: string;
54
+ placeholderClose: string;
55
+ missingValuePolicy: "prompt-each-branch";
56
+ emptyValueIsPresent: boolean;
57
+ unmanagedKeyPolicy: "preserve";
58
+ removedTemplateKeyPolicy: "preserve";
59
+ }, {
60
+ placeholderOpen?: string | undefined;
61
+ placeholderClose?: string | undefined;
62
+ missingValuePolicy?: "prompt-each-branch" | undefined;
63
+ emptyValueIsPresent?: boolean | undefined;
64
+ unmanagedKeyPolicy?: "preserve" | undefined;
65
+ removedTemplateKeyPolicy?: "preserve" | undefined;
66
+ }>;
67
+ review: z.ZodObject<{
68
+ autoApprove: z.ZodDefault<z.ZodBoolean>;
69
+ readOnlySnapshots: z.ZodDefault<z.ZodBoolean>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ autoApprove: boolean;
72
+ readOnlySnapshots: boolean;
73
+ }, {
74
+ autoApprove?: boolean | undefined;
75
+ readOnlySnapshots?: boolean | undefined;
76
+ }>;
77
+ execution: z.ZodObject<{
78
+ concurrency: z.ZodDefault<z.ZodNumber>;
79
+ stopOnBranchError: z.ZodDefault<z.ZodBoolean>;
80
+ }, "strip", z.ZodTypeAny, {
81
+ concurrency: number;
82
+ stopOnBranchError: boolean;
83
+ }, {
84
+ concurrency?: number | undefined;
85
+ stopOnBranchError?: boolean | undefined;
86
+ }>;
87
+ report: z.ZodObject<{
88
+ writeJson: z.ZodDefault<z.ZodBoolean>;
89
+ writeMarkdown: z.ZodDefault<z.ZodBoolean>;
90
+ redactValues: z.ZodDefault<z.ZodBoolean>;
91
+ warnOnSecrets: z.ZodDefault<z.ZodBoolean>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ writeJson: boolean;
94
+ writeMarkdown: boolean;
95
+ redactValues: boolean;
96
+ warnOnSecrets: boolean;
97
+ }, {
98
+ writeJson?: boolean | undefined;
99
+ writeMarkdown?: boolean | undefined;
100
+ redactValues?: boolean | undefined;
101
+ warnOnSecrets?: boolean | undefined;
102
+ }>;
103
+ safety: z.ZodObject<{
104
+ rejectNoRemote: z.ZodDefault<z.ZodBoolean>;
105
+ saveOnPartialSuccess: z.ZodDefault<z.ZodBoolean>;
106
+ }, "strip", z.ZodTypeAny, {
107
+ rejectNoRemote: boolean;
108
+ saveOnPartialSuccess: boolean;
109
+ }, {
110
+ rejectNoRemote?: boolean | undefined;
111
+ saveOnPartialSuccess?: boolean | undefined;
112
+ }>;
113
+ }, "strip", z.ZodTypeAny, {
114
+ git: {
115
+ remote: string;
116
+ fetchPrune: boolean;
117
+ cleanMode: "tracked-and-untracked" | "tracked-only";
118
+ pushPolicy: "never" | "prompt" | "always";
119
+ commitMessage: string;
120
+ };
121
+ workspace: {
122
+ root: string;
123
+ lockTimeoutMs: number;
124
+ };
125
+ editor: {
126
+ name: string;
127
+ editCommand: string[];
128
+ diffCommand: string[];
129
+ };
130
+ template: {
131
+ placeholderOpen: string;
132
+ placeholderClose: string;
133
+ missingValuePolicy: "prompt-each-branch";
134
+ emptyValueIsPresent: boolean;
135
+ unmanagedKeyPolicy: "preserve";
136
+ removedTemplateKeyPolicy: "preserve";
137
+ };
138
+ review: {
139
+ autoApprove: boolean;
140
+ readOnlySnapshots: boolean;
141
+ };
142
+ execution: {
143
+ concurrency: number;
144
+ stopOnBranchError: boolean;
145
+ };
146
+ report: {
147
+ writeJson: boolean;
148
+ writeMarkdown: boolean;
149
+ redactValues: boolean;
150
+ warnOnSecrets: boolean;
151
+ };
152
+ safety: {
153
+ rejectNoRemote: boolean;
154
+ saveOnPartialSuccess: boolean;
155
+ };
156
+ }, {
157
+ git: {
158
+ remote?: string | undefined;
159
+ fetchPrune?: boolean | undefined;
160
+ cleanMode?: "tracked-and-untracked" | "tracked-only" | undefined;
161
+ pushPolicy?: "never" | "prompt" | "always" | undefined;
162
+ commitMessage?: string | undefined;
163
+ };
164
+ workspace: {
165
+ root?: string | undefined;
166
+ lockTimeoutMs?: number | undefined;
167
+ };
168
+ editor: {
169
+ name?: string | undefined;
170
+ editCommand?: string[] | undefined;
171
+ diffCommand?: string[] | undefined;
172
+ };
173
+ template: {
174
+ placeholderOpen?: string | undefined;
175
+ placeholderClose?: string | undefined;
176
+ missingValuePolicy?: "prompt-each-branch" | undefined;
177
+ emptyValueIsPresent?: boolean | undefined;
178
+ unmanagedKeyPolicy?: "preserve" | undefined;
179
+ removedTemplateKeyPolicy?: "preserve" | undefined;
180
+ };
181
+ review: {
182
+ autoApprove?: boolean | undefined;
183
+ readOnlySnapshots?: boolean | undefined;
184
+ };
185
+ execution: {
186
+ concurrency?: number | undefined;
187
+ stopOnBranchError?: boolean | undefined;
188
+ };
189
+ report: {
190
+ writeJson?: boolean | undefined;
191
+ writeMarkdown?: boolean | undefined;
192
+ redactValues?: boolean | undefined;
193
+ warnOnSecrets?: boolean | undefined;
194
+ };
195
+ safety: {
196
+ rejectNoRemote?: boolean | undefined;
197
+ saveOnPartialSuccess?: boolean | undefined;
198
+ };
199
+ }>;
200
+ export type AppConfig = z.infer<typeof AppConfigSchema>;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ export const AppConfigSchema = z.object({
3
+ git: z.object({
4
+ remote: z.string().default("origin"),
5
+ fetchPrune: z.boolean().default(true),
6
+ cleanMode: z.enum(["tracked-and-untracked", "tracked-only"]).default("tracked-and-untracked"),
7
+ pushPolicy: z.enum(["prompt", "always", "never"]).default("prompt"),
8
+ commitMessage: z.string().default("chore: sync configuration"),
9
+ }),
10
+ workspace: z.object({
11
+ root: z.string().default(".git-worker"),
12
+ lockTimeoutMs: z.number().default(30000),
13
+ }),
14
+ editor: z.object({
15
+ name: z.string().default("code"),
16
+ editCommand: z.array(z.string()).default(["code", "--wait", "{file}"]),
17
+ diffCommand: z.array(z.string()).default(["code", "--diff", "{before}", "{after}"]),
18
+ }),
19
+ template: z.object({
20
+ placeholderOpen: z.string().default("{"),
21
+ placeholderClose: z.string().default("}"),
22
+ missingValuePolicy: z.enum(["prompt-each-branch"]).default("prompt-each-branch"),
23
+ emptyValueIsPresent: z.boolean().default(true),
24
+ unmanagedKeyPolicy: z.enum(["preserve"]).default("preserve"),
25
+ removedTemplateKeyPolicy: z.enum(["preserve"]).default("preserve"),
26
+ }),
27
+ review: z.object({
28
+ autoApprove: z.boolean().default(false),
29
+ readOnlySnapshots: z.boolean().default(true),
30
+ }),
31
+ execution: z.object({
32
+ concurrency: z.number().default(1),
33
+ stopOnBranchError: z.boolean().default(false),
34
+ }),
35
+ report: z.object({
36
+ writeJson: z.boolean().default(false),
37
+ writeMarkdown: z.boolean().default(false),
38
+ redactValues: z.boolean().default(true),
39
+ warnOnSecrets: z.boolean().default(true),
40
+ }),
41
+ safety: z.object({
42
+ rejectNoRemote: z.boolean().default(true),
43
+ saveOnPartialSuccess: z.boolean().default(true),
44
+ }),
45
+ });
@@ -0,0 +1,12 @@
1
+ import { AppError } from "./types.js";
2
+ export declare function configError(msg: string, details?: unknown): AppError;
3
+ export declare function gitError(msg: string, details?: unknown): AppError;
4
+ export declare function editorError(msg: string, details?: unknown): AppError;
5
+ export declare function templateError(msg: string, details?: unknown): AppError;
6
+ export declare function eligibilityError(msg: string, details?: unknown): AppError;
7
+ export declare function inputCancelledError(msg: string, details?: unknown): AppError;
8
+ export declare function stalePlanError(msg: string, details?: unknown): AppError;
9
+ export declare function applyError(msg: string, details?: unknown): AppError;
10
+ export declare function pushError(msg: string, details?: unknown): AppError;
11
+ export declare function workspaceError(msg: string, details?: unknown): AppError;
12
+ export declare function validationError(msg: string, details?: unknown): AppError;
@@ -0,0 +1,34 @@
1
+ import { AppError } from "./types.js";
2
+ export function configError(msg, details) {
3
+ return new AppError("CONFIG", msg, details);
4
+ }
5
+ export function gitError(msg, details) {
6
+ return new AppError("GIT", msg, details);
7
+ }
8
+ export function editorError(msg, details) {
9
+ return new AppError("EDITOR", msg, details);
10
+ }
11
+ export function templateError(msg, details) {
12
+ return new AppError("TEMPLATE", msg, details);
13
+ }
14
+ export function eligibilityError(msg, details) {
15
+ return new AppError("ELIGIBILITY", msg, details);
16
+ }
17
+ export function inputCancelledError(msg, details) {
18
+ return new AppError("INPUT_CANCELLED", msg, details);
19
+ }
20
+ export function stalePlanError(msg, details) {
21
+ return new AppError("STALE_PLAN", msg, details);
22
+ }
23
+ export function applyError(msg, details) {
24
+ return new AppError("APPLY", msg, details);
25
+ }
26
+ export function pushError(msg, details) {
27
+ return new AppError("PUSH", msg, details);
28
+ }
29
+ export function workspaceError(msg, details) {
30
+ return new AppError("WORKSPACE", msg, details);
31
+ }
32
+ export function validationError(msg, details) {
33
+ return new AppError("VALIDATION", msg, details);
34
+ }
@@ -0,0 +1,3 @@
1
+ import { Result, AppError } from "./types.js";
2
+ export type Stage<C, D> = (context: C, deps: D) => Promise<Result<C, AppError>>;
3
+ export declare function pipeline<C, D>(...stages: Stage<C, D>[]): Stage<C, D>;
@@ -0,0 +1,14 @@
1
+ import { ok } from "./types.js";
2
+ export function pipeline(...stages) {
3
+ return async (initial, deps) => {
4
+ let current = initial;
5
+ for (const stage of stages) {
6
+ const result = await stage(current, deps);
7
+ if (!result.ok) {
8
+ return result;
9
+ }
10
+ current = result.value;
11
+ }
12
+ return ok(current);
13
+ };
14
+ }