@pushpalsdev/cli 1.0.67 → 1.0.68

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.67",
3
+ "version": "1.0.68",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -7,13 +7,17 @@ import { existsSync, readFileSync, rmSync, unlinkSync } from "fs";
7
7
  import { resolve } from "path";
8
8
  import {
9
9
  deriveAutonomyComponentArea,
10
+ buildGitCommitArgs as buildSourceControlGitCommitArgs,
11
+ explicitSourceControlCommitIdentityFromEnv,
10
12
  loadPromptTemplate,
11
13
  loadPushPalsConfig,
12
14
  matchesGlob,
13
15
  normalizeAutonomyComponentArea,
14
16
  normalizeTargetPath,
17
+ sanitizeSourceControlIdentityField,
15
18
  validateScopeInvariants,
16
19
  type AutonomyComponentArea,
20
+ type SourceControlCommitIdentity,
17
21
  } from "shared";
18
22
  import { resolveExecutor, type WorkerpalsRuntimeConfig } from "./common/executor_backend.js";
19
23
  import type { JobPublishBlockedInfo, JobResult } from "./common/types.js";
@@ -1432,6 +1436,35 @@ export async function git(
1432
1436
  // ─── Git commit creation ─────────────────────────────────────────────────────
1433
1437
 
1434
1438
  /** Create commit for job result and return commit info */
1439
+ export type WorkerGitCommitIdentity = SourceControlCommitIdentity;
1440
+
1441
+ export const explicitWorkerCommitIdentityFromEnv = explicitSourceControlCommitIdentityFromEnv;
1442
+
1443
+ async function resolveGitConfigValue(repo: string, key: string): Promise<string> {
1444
+ const value = await git(repo, ["config", "--get", key]);
1445
+ return value.ok ? sanitizeSourceControlIdentityField(value.stdout) : "";
1446
+ }
1447
+
1448
+ export async function resolveWorkerCommitIdentity(
1449
+ repo: string,
1450
+ _runtimeConfig: WorkerpalsRuntimeConfig = DEFAULT_CONFIG,
1451
+ ): Promise<WorkerGitCommitIdentity | null> {
1452
+ const fallbackEmail = await resolveGitConfigValue(repo, "user.email");
1453
+ const explicit = explicitWorkerCommitIdentityFromEnv(process.env, fallbackEmail);
1454
+ if (explicit) return explicit;
1455
+
1456
+ const name = await resolveGitConfigValue(repo, "user.name");
1457
+ if (name && fallbackEmail) return { name, email: fallbackEmail, source: "source-control-config" };
1458
+ return null;
1459
+ }
1460
+
1461
+ export function buildGitCommitArgs(
1462
+ commitMsg: string,
1463
+ identity: WorkerGitCommitIdentity | null,
1464
+ ): string[] {
1465
+ return buildSourceControlGitCommitArgs(commitMsg, identity);
1466
+ }
1467
+
1435
1468
  export interface CreateJobCommitResult {
1436
1469
  ok: boolean;
1437
1470
  branch?: string;
@@ -1574,8 +1607,10 @@ export async function createJobCommit(
1574
1607
  }
1575
1608
  const commitMsg = llmCommitMsg ?? buildWorkerCommitMessage(workerId, job, changedPaths);
1576
1609
 
1577
- // Commit changes
1578
- result = await git(repo, ["commit", "-m", commitMsg]);
1610
+ // Commit changes with a PushPals-resolved author so generated commits use
1611
+ // source-control identity instead of falling through to the host account.
1612
+ const commitIdentity = await resolveWorkerCommitIdentity(repo, runtimeConfig);
1613
+ result = await git(repo, buildGitCommitArgs(commitMsg, commitIdentity));
1579
1614
  if (!result.ok) {
1580
1615
  return { ok: false, error: `Failed to commit: ${result.stderr}` };
1581
1616
  }
@@ -2484,7 +2519,8 @@ async function createMergeConflictJobCommit(
2484
2519
  );
2485
2520
  }
2486
2521
  const commitMsg = llmCommitMsg ?? buildWorkerCommitMessage(workerId, job, changedPaths);
2487
- const commit = await git(repo, ["commit", "-m", commitMsg]);
2522
+ const commitIdentity = await resolveWorkerCommitIdentity(repo, runtimeConfig);
2523
+ const commit = await git(repo, buildGitCommitArgs(commitMsg, commitIdentity));
2488
2524
  if (!commit.ok) {
2489
2525
  return { ok: false, error: `Failed to commit merge-conflict resolution: ${commit.stderr}` };
2490
2526
  }
@@ -43,6 +43,17 @@ export {
43
43
  type GitTokenSource,
44
44
  type ResolveGitTokenOptions,
45
45
  } from "./git_backend.js";
46
+ export {
47
+ assertSupportedSourceControlProvider,
48
+ buildGitCommitArgs,
49
+ explicitSourceControlCommitIdentityFromEnv,
50
+ normalizeSourceControlProvider,
51
+ resolveSourceControlProvider,
52
+ sanitizeSourceControlIdentityField,
53
+ type SourceControlCommitIdentity,
54
+ type SourceControlCommitIdentitySource,
55
+ type SourceControlProvider,
56
+ } from "./source_control_api.js";
46
57
  export {
47
58
  DEFAULT_WORKERPALS_EXECUTOR,
48
59
  invalidatePushPalsConfigCache,
@@ -0,0 +1,117 @@
1
+ export type SourceControlProvider = "git" | "sapling" | "mercurial";
2
+
3
+ export type SourceControlCommitIdentitySource = "env" | "source-control-config";
4
+
5
+ export interface SourceControlCommitIdentity {
6
+ name: string;
7
+ email: string;
8
+ source: SourceControlCommitIdentitySource;
9
+ }
10
+
11
+ export function normalizeSourceControlProvider(value: unknown): SourceControlProvider | null {
12
+ const normalized = String(value ?? "")
13
+ .trim()
14
+ .toLowerCase()
15
+ .replace(/[_\s]+/g, "-");
16
+
17
+ if (!normalized) return null;
18
+ if (normalized === "auto") return "git";
19
+ if (normalized === "git") return "git";
20
+ if (normalized === "sapling" || normalized === "sl") return "sapling";
21
+ if (normalized === "mercurial" || normalized === "mercury" || normalized === "hg") {
22
+ return "mercurial";
23
+ }
24
+ return null;
25
+ }
26
+
27
+ function hasSourceControlProviderValue(value: unknown): boolean {
28
+ return String(value ?? "").trim().length > 0;
29
+ }
30
+
31
+ function formatUnknownSourceControlProvider(value: unknown): string {
32
+ return String(value ?? "").trim() || "(empty)";
33
+ }
34
+
35
+ export function resolveSourceControlProvider(
36
+ value?: unknown,
37
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
38
+ ): SourceControlProvider {
39
+ if (hasSourceControlProviderValue(value)) {
40
+ const explicit = normalizeSourceControlProvider(value);
41
+ if (explicit) return explicit;
42
+ throw new Error(`Unknown source control provider '${formatUnknownSourceControlProvider(value)}'.`);
43
+ }
44
+
45
+ const envValue = env.PUSHPALS_SOURCE_CONTROL_PROVIDER ?? env.SOURCE_CONTROL_PROVIDER;
46
+ if (hasSourceControlProviderValue(envValue)) {
47
+ const fromEnv = normalizeSourceControlProvider(envValue);
48
+ if (fromEnv) return fromEnv;
49
+ throw new Error(
50
+ `Unknown source control provider '${formatUnknownSourceControlProvider(envValue)}'.`,
51
+ );
52
+ }
53
+
54
+ return "git";
55
+ }
56
+
57
+ export function assertSupportedSourceControlProvider(provider: SourceControlProvider): "git" {
58
+ if (provider === "git") return "git";
59
+ throw new Error(
60
+ `Source control provider '${provider}' is recognized but not supported yet. PushPals currently supports git only.`,
61
+ );
62
+ }
63
+
64
+ function firstNonEmptyString(...values: Array<string | null | undefined>): string {
65
+ for (const value of values) {
66
+ const trimmed = String(value ?? "").trim();
67
+ if (trimmed) return trimmed;
68
+ }
69
+ return "";
70
+ }
71
+
72
+ export function sanitizeSourceControlIdentityField(value: unknown): string {
73
+ return String(value ?? "")
74
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
75
+ .replace(/[<>]/g, "")
76
+ .replace(/\s+/g, " ")
77
+ .trim();
78
+ }
79
+
80
+ export function explicitSourceControlCommitIdentityFromEnv(
81
+ env: Record<string, string | undefined>,
82
+ fallbackEmail = "",
83
+ ): SourceControlCommitIdentity | null {
84
+ const name = sanitizeSourceControlIdentityField(
85
+ firstNonEmptyString(
86
+ env.WORKERPALS_GIT_AUTHOR_NAME,
87
+ env.PUSHPALS_GIT_AUTHOR_NAME,
88
+ env.GIT_AUTHOR_NAME,
89
+ ),
90
+ );
91
+ const email = sanitizeSourceControlIdentityField(
92
+ firstNonEmptyString(
93
+ env.WORKERPALS_GIT_AUTHOR_EMAIL,
94
+ env.PUSHPALS_GIT_AUTHOR_EMAIL,
95
+ env.GIT_AUTHOR_EMAIL,
96
+ fallbackEmail,
97
+ ),
98
+ );
99
+ if (!name || !email) return null;
100
+ return { name, email, source: "env" };
101
+ }
102
+
103
+ export function buildGitCommitArgs(
104
+ commitMsg: string,
105
+ identity: SourceControlCommitIdentity | null,
106
+ ): string[] {
107
+ const args: string[] = [];
108
+ if (identity?.name && identity.email) {
109
+ args.push("-c", `user.name=${identity.name}`, "-c", `user.email=${identity.email}`);
110
+ }
111
+ args.push("commit");
112
+ if (identity?.name && identity.email) {
113
+ args.push("--author", `${identity.name} <${identity.email}>`);
114
+ }
115
+ args.push("-m", commitMsg);
116
+ return args;
117
+ }