@markjaquith/agency 3.9.2 → 3.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "3.9.2",
3
+ "version": "3.9.3",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -5,6 +5,7 @@ import { PhaseService } from "./PhaseService"
5
5
  import { TaskService } from "./TaskService"
6
6
  import { WorkbaseService } from "./WorkbaseService"
7
7
  import { parseGitCommits, type PushCommitMetadata } from "./push-validation"
8
+ import { isDirtyGitStatus } from "./VersionControlService"
8
9
 
9
10
  type PushCategory =
10
11
  | "precondition"
@@ -374,11 +375,11 @@ const publishGit = (
374
375
  const status = yield* git(
375
376
  fs,
376
377
  checkout,
377
- ["status", "--porcelain=v1"],
378
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all"],
378
379
  "Failed to inspect Git status",
379
380
  "inspect",
380
381
  )
381
- if (status.stdout.length > 0) {
382
+ if (isDirtyGitStatus(status.stdout)) {
382
383
  return yield* pushError(
383
384
  "Cannot publish a dirty Git worktree; commit or discard changes first",
384
385
  "precondition",
@@ -58,6 +58,17 @@ export interface VersionControlBackend {
58
58
  ) => Effect.Effect<void, unknown, any>
59
59
  }
60
60
 
61
+ const ignoredUntrackedGitPath = /^(?:.*\/)?\.DS_Store$/
62
+
63
+ export const isDirtyGitStatus = (output: string): boolean =>
64
+ (output.includes("\0") ? output.split("\0") : output.split(/\r?\n/)).some(
65
+ (line) => {
66
+ if (!line) return false
67
+ if (!line.startsWith("?? ")) return true
68
+ return !ignoredUntrackedGitPath.test(line.slice(3))
69
+ },
70
+ )
71
+
61
72
  class VersionControlError extends Data.TaggedError("VersionControlError")<{
62
73
  readonly message: string
63
74
  }> {}
@@ -267,10 +278,20 @@ export class GitVersionControlService extends Effect.Service<GitVersionControlSe
267
278
  Effect.gen(function* () {
268
279
  const fs = yield* FileSystemService
269
280
  const result = yield* fs.runCommand(
270
- ["git", "-C", workspacePath, "status", "--porcelain"],
281
+ [
282
+ "git",
283
+ "-C",
284
+ workspacePath,
285
+ "status",
286
+ "--porcelain=v1",
287
+ "-z",
288
+ "--untracked-files=all",
289
+ ],
271
290
  { captureOutput: true },
272
291
  )
273
- return result.exitCode === 0 ? result.stdout.length > 0 : null
292
+ return result.exitCode === 0
293
+ ? isDirtyGitStatus(result.stdout)
294
+ : null
274
295
  }),
275
296
  fetch: (repositoryPath, remote = "origin", branch) =>
276
297
  Effect.gen(function* () {
@@ -18,7 +18,10 @@ import type { BaseCommandOptions } from "../utils/command"
18
18
  import { createLoggers } from "../utils/effect"
19
19
  import { createProgress } from "../utils/progress"
20
20
  import { withWorktreeLocks } from "./WorktreeLock"
21
- import { VersionControlService } from "./VersionControlService"
21
+ import {
22
+ isDirtyGitStatus,
23
+ VersionControlService,
24
+ } from "./VersionControlService"
22
25
  import type {
23
26
  RegisteredWorkspace,
24
27
  VersionControlBackend,
@@ -75,7 +78,7 @@ interface GitWorktree {
75
78
  readonly branch?: string
76
79
  }
77
80
 
78
- const managedWorktreeIgnorePatterns = ["/.worktree.lock"] as const
81
+ const managedWorktreeIgnorePatterns = ["/.worktree.lock", ".DS_Store"] as const
79
82
  const vcs = "git" as const
80
83
 
81
84
  interface WorktreeOwner {
@@ -550,13 +553,21 @@ const inspectExecution = (
550
553
  const status =
551
554
  actual && actualExists
552
555
  ? yield* fs.runCommand(
553
- ["git", "-C", actual.path, "status", "--porcelain"],
556
+ [
557
+ "git",
558
+ "-C",
559
+ actual.path,
560
+ "status",
561
+ "--porcelain=v1",
562
+ "-z",
563
+ "--untracked-files=all",
564
+ ],
554
565
  { captureOutput: true },
555
566
  )
556
567
  : null
557
568
  const dirty = status
558
569
  ? status.exitCode === 0
559
- ? status.stdout.length > 0
570
+ ? isDirtyGitStatus(status.stdout)
560
571
  : null
561
572
  : null
562
573
  const expectedRef = "branch" in checkout ? checkout.branch : checkout.ref
@@ -2076,7 +2087,15 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
2076
2087
  }
2077
2088
  if (checkoutExists) {
2078
2089
  const status = yield* fs.runCommand(
2079
- ["git", "-C", checkoutPath, "status", "--porcelain"],
2090
+ [
2091
+ "git",
2092
+ "-C",
2093
+ checkoutPath,
2094
+ "status",
2095
+ "--porcelain=v1",
2096
+ "-z",
2097
+ "--untracked-files=all",
2098
+ ],
2080
2099
  { captureOutput: true },
2081
2100
  )
2082
2101
  if (status.exitCode !== 0) {
@@ -2084,7 +2103,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
2084
2103
  message: `Failed to remove worktree for '${alias}': checkout cleanliness could not be verified: ${status.stderr.trim() || `git status exited with code ${status.exitCode}`}`,
2085
2104
  })
2086
2105
  }
2087
- if (status.stdout.trim()) {
2106
+ if (isDirtyGitStatus(status.stdout)) {
2088
2107
  return yield* new WorktreeError({
2089
2108
  message: `Failed to remove worktree for '${alias}': checkout has uncommitted changes`,
2090
2109
  })