@markjaquith/agency 0.7.2 → 0.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Manages personal agents files",
5
5
  "license": "MIT",
6
6
  "author": "Mark Jaquith",
@@ -8,7 +8,10 @@ import {
8
8
  initAgency,
9
9
  readFile,
10
10
  runTestEffect,
11
+ createFile,
12
+ runGitCommand,
11
13
  } from "../test-utils"
14
+ import { chmod } from "fs/promises"
12
15
 
13
16
  describe("edit command", () => {
14
17
  let tempDir: string
@@ -138,4 +141,95 @@ describe("edit command", () => {
138
141
  "Editor exited with code",
139
142
  )
140
143
  })
144
+
145
+ test("commits TASK.md with 'chore: agency edit' when file is modified", async () => {
146
+ await initGitRepo(tempDir)
147
+ process.chdir(tempDir)
148
+
149
+ // Initialize to create TASK.md
150
+ await initAgency(tempDir, "test-task")
151
+ await runTestEffect(task({ silent: true, emit: "test-feature" }))
152
+
153
+ // Get initial commit count
154
+ const result = Bun.spawnSync({
155
+ cmd: ["git", "rev-list", "--count", "HEAD"],
156
+ cwd: tempDir,
157
+ stdout: "pipe",
158
+ })
159
+ const initialCommits = new TextDecoder().decode(result.stdout).trim()
160
+
161
+ // Use a script that modifies TASK.md
162
+ const scriptPath = join(tempDir, "edit-script.sh")
163
+ await createFile(
164
+ tempDir,
165
+ "edit-script.sh",
166
+ '#!/bin/bash\necho "Updated task" >> "$1"\n',
167
+ )
168
+ await chmod(scriptPath, 0o755)
169
+ process.env.EDITOR = scriptPath
170
+
171
+ // Run edit command
172
+ await runTestEffect(taskEdit({ silent: true }))
173
+
174
+ // Check that a new commit was created
175
+ const finalResult = Bun.spawnSync({
176
+ cmd: ["git", "rev-list", "--count", "HEAD"],
177
+ cwd: tempDir,
178
+ stdout: "pipe",
179
+ })
180
+ const finalCommits = new TextDecoder().decode(finalResult.stdout).trim()
181
+ expect(Number.parseInt(finalCommits)).toBe(
182
+ Number.parseInt(initialCommits) + 1,
183
+ )
184
+
185
+ // Check the commit message
186
+ const msgResult = Bun.spawnSync({
187
+ cmd: ["git", "log", "-1", "--format=%s"],
188
+ cwd: tempDir,
189
+ stdout: "pipe",
190
+ })
191
+ const commitMessage = new TextDecoder().decode(msgResult.stdout).trim()
192
+ expect(commitMessage).toBe("chore: agency edit")
193
+
194
+ // Check that only TASK.md was committed
195
+ const filesResult = Bun.spawnSync({
196
+ cmd: ["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
197
+ cwd: tempDir,
198
+ stdout: "pipe",
199
+ })
200
+ const filesInCommit = new TextDecoder().decode(filesResult.stdout).trim()
201
+ expect(filesInCommit).toBe("TASK.md")
202
+ })
203
+
204
+ test("does not commit when TASK.md is not modified", async () => {
205
+ await initGitRepo(tempDir)
206
+ process.chdir(tempDir)
207
+
208
+ // Initialize to create TASK.md
209
+ await initAgency(tempDir, "test-task")
210
+ await runTestEffect(task({ silent: true, emit: "test-feature" }))
211
+
212
+ // Get initial commit count
213
+ const result = Bun.spawnSync({
214
+ cmd: ["git", "rev-list", "--count", "HEAD"],
215
+ cwd: tempDir,
216
+ stdout: "pipe",
217
+ })
218
+ const initialCommits = new TextDecoder().decode(result.stdout).trim()
219
+
220
+ // Use a mock editor that doesn't modify the file
221
+ process.env.EDITOR = "true"
222
+
223
+ // Run edit command
224
+ await runTestEffect(taskEdit({ silent: true }))
225
+
226
+ // Check that no new commit was created
227
+ const finalResult = Bun.spawnSync({
228
+ cmd: ["git", "rev-list", "--count", "HEAD"],
229
+ cwd: tempDir,
230
+ stdout: "pipe",
231
+ })
232
+ const finalCommits = new TextDecoder().decode(finalResult.stdout).trim()
233
+ expect(Number.parseInt(finalCommits)).toBe(Number.parseInt(initialCommits))
234
+ })
141
235
  })
@@ -910,6 +910,7 @@ const taskEditEffect = (options: TaskEditOptions = {}) =>
910
910
  const { log, verboseLog } = createLoggers(options)
911
911
 
912
912
  const fs = yield* FileSystemService
913
+ const git = yield* GitService
913
914
 
914
915
  const gitRoot = yield* ensureGitRepo()
915
916
 
@@ -943,6 +944,26 @@ const taskEditEffect = (options: TaskEditOptions = {}) =>
943
944
  }
944
945
 
945
946
  log(done("TASK.md edited"))
947
+
948
+ // Check if TASK.md has uncommitted changes
949
+ const hasChanges = yield* git.hasUncommittedChanges(gitRoot, "TASK.md")
950
+ verboseLog(`TASK.md has uncommitted changes: ${hasChanges}`)
951
+
952
+ if (hasChanges) {
953
+ // Commit the changes
954
+ yield* Effect.gen(function* () {
955
+ yield* git.gitAdd(["TASK.md"], gitRoot)
956
+ yield* git.gitCommit("chore: agency edit", gitRoot, {
957
+ noVerify: true,
958
+ })
959
+ log(done("Committed TASK.md changes"))
960
+ }).pipe(
961
+ Effect.catchAll((err) => {
962
+ verboseLog(`Failed to commit TASK.md: ${err}`)
963
+ return Effect.void
964
+ }),
965
+ )
966
+ }
946
967
  })
947
968
 
948
969
  export const editHelp = `
@@ -141,7 +141,7 @@ describe("work command", () => {
141
141
  restore()
142
142
 
143
143
  expect(spawnCalled).toBe(true)
144
- expect(spawnArgs).toEqual(["opencode", "-p", "Start the task"])
144
+ expect(spawnArgs).toEqual(["opencode", "--prompt", "Start the task"])
145
145
  })
146
146
  })
147
147
 
@@ -171,7 +171,7 @@ describe("work command", () => {
171
171
 
172
172
  restore()
173
173
 
174
- expect(capturedArgs).toEqual(["opencode", "-p", "Start the task"])
174
+ expect(capturedArgs).toEqual(["opencode", "--prompt", "Start the task"])
175
175
  // On macOS, temp directories can have /private prefix
176
176
  expect(
177
177
  capturedOptions.cwd === tempDir ||
@@ -278,7 +278,7 @@ describe("work command", () => {
278
278
 
279
279
  restore()
280
280
 
281
- expect(capturedArgs).toEqual(["opencode", "-p", "Start the task"])
281
+ expect(capturedArgs).toEqual(["opencode", "--prompt", "Start the task"])
282
282
  })
283
283
 
284
284
  test("--claude flag forces use of Claude Code", async () => {
@@ -387,7 +387,7 @@ describe("work command", () => {
387
387
 
388
388
  expect(capturedArgs).toEqual([
389
389
  "opencode",
390
- "-p",
390
+ "--prompt",
391
391
  "Start the task",
392
392
  "--model",
393
393
  "claude-sonnet-4-20250514",
@@ -456,7 +456,7 @@ describe("work command", () => {
456
456
 
457
457
  restore()
458
458
 
459
- expect(capturedArgs).toEqual(["opencode", "-p", "Start the task"])
459
+ expect(capturedArgs).toEqual(["opencode", "--prompt", "Start the task"])
460
460
  })
461
461
  })
462
462
  })
@@ -117,7 +117,7 @@ export const work = (options: WorkOptions = {}) =>
117
117
 
118
118
  const cliName = useOpencode ? "opencode" : "claude"
119
119
  const baseArgs = useOpencode
120
- ? [cliName, "-p", "Start the task"]
120
+ ? [cliName, "--prompt", "Start the task"]
121
121
  : [cliName, "Start the task"]
122
122
 
123
123
  // Append extra args if provided
@@ -850,5 +850,21 @@ export class GitService extends Effect.Service<GitService>()("GitService", {
850
850
  Effect.map((result) => (result.exitCode === 0 ? result.stdout : null)),
851
851
  Effect.catchAll(() => Effect.succeed(null)),
852
852
  ),
853
+
854
+ /**
855
+ * Check if a file has uncommitted changes (staged or unstaged).
856
+ * Uses `git diff HEAD` to check for any changes to the file.
857
+ * @param gitRoot - The git repository root
858
+ * @param filePath - Path to the file relative to git root
859
+ * @returns true if the file has changes, false otherwise
860
+ */
861
+ hasUncommittedChanges: (gitRoot: string, filePath: string) =>
862
+ pipe(
863
+ runGitCommand(["git", "diff", "HEAD", "--", filePath], gitRoot),
864
+ Effect.map(
865
+ (result) => result.exitCode === 0 && result.stdout.length > 0,
866
+ ),
867
+ Effect.catchAll(() => Effect.succeed(false)),
868
+ ),
853
869
  }),
854
870
  }) {}