refactor: full rebrand ppg → pogu#50
Conversation
Rename PgError → PoguError, pgDir() → poguDir(), PG_DIR → POGU_DIR, .pg/ → .pogu/, ppg/ branch prefix → pogu/, session name ppg → pogu across src/lib/, src/core/, src/types/, and src/test-fixtures.ts
Rename all ppg → pogu in CLI entry point and command implementations: imports (PoguError, poguDir), user-facing strings, branch prefixes, session names, .pg/ → .pogu/ directory references
Rename package pure-point-guard → pointguard, bin ppg → pogu, move skills/ppg → skills/pogu, skills/ppg-conductor → skills/pogu-conductor, update all documentation, plugin config, and appcast
Rename PPGService → PoguService, runPPGCommand → runPoguCommand, all UI strings, notification names, UserDefaults keys, bundle IDs, path references .pg/ → .pogu/ across all Swift source and test files
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughThis pull request performs a comprehensive project rename from "ppg" (Pure Point Guard) to "pogu" across the entire codebase. Changes include renaming the npm package, Swift bundle identifiers, public API classes ( Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes This is a large-scope, homogeneous refactoring that renames the project consistently across ~90+ files. While individual changes follow a clear pattern (string substitutions and identifier renames), the volume and span across multiple ecosystems (Swift, TypeScript, configuration, documentation) requires verification that: (1) all rename targets are correct and complete, (2) public API changes are properly propagated, (3) test fixtures align with new naming, and (4) no unintended replacements occurred. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/commands/ui.ts (1)
40-41:⚠️ Potential issue | 🟠 MajorUpdate bundle identifier in mdfind query from
com.2wit.PPG-CLItocom.2wit.Pogu.The mdfind query on line 41 uses an outdated bundle identifier. The Swift app's actual bundle identifier is
com.2wit.Pogu(as defined in the Xcode project configuration), so the query will fail to locate the application. Update the query to match the current bundle ID.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/ui.ts` around lines 40 - 41, The mdfind call that builds the Spotlight query uses the outdated bundle identifier string "com.2wit.PPG-CLI"; update the query argument passed to execa('mdfind', [...]) so the predicate uses the current bundle identifier "com.2wit.Pogu" (i.e., replace "com.2wit.PPG-CLI" with "com.2wit.Pogu" in the array passed to execa) so the result variable receives the correct search output.PPG CLI/PPG CLITests/DashboardSessionTests.swift (4)
180-199:⚠️ Potential issue | 🔴 CriticalSame directory path mismatch as above.
Line 182 creates
.pgbut theDashboardSessionnow uses.pogupaths.🐛 Proposed fix
func testReloadFromDiskCancelsPendingWrite() { let dir = NSTemporaryDirectory() + "pogu-test-reload-\(UUID().uuidString)" - try? FileManager.default.createDirectory(atPath: dir + "/.pg", withIntermediateDirectories: true) + try? FileManager.default.createDirectory(atPath: dir + "/.pogu", withIntermediateDirectories: true) let s = DashboardSession(projectRoot: dir)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PPG` CLI/PPG CLITests/DashboardSessionTests.swift around lines 180 - 199, The test creates and cleans up the wrong hidden directory name (uses ".pg") but DashboardSession uses ".pogu"; update the test (testReloadFromDiskCancelsPendingWrite) to create the directory at dir + "/.pogu" (and remove that path at the end) so the session's flushToDisk/reloadFromDisk operate on the same on-disk path used by DashboardSession.
114-131:⚠️ Potential issue | 🔴 CriticalDirectory path mismatch will cause test failures.
The test creates the directory at
.pg(line 115) but then expects the file to be written to.pogu(line 122). TheflushToDisk()method will fail or write to a non-existent directory.🐛 Proposed fix
func testFlushWritesImmediately() { let dir = NSTemporaryDirectory() + "pogu-test-flush-\(UUID().uuidString)" - try? FileManager.default.createDirectory(atPath: dir + "/.pg", withIntermediateDirectories: true) + try? FileManager.default.createDirectory(atPath: dir + "/.pogu", withIntermediateDirectories: true) let s = DashboardSession(projectRoot: dir)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PPG` CLI/PPG CLITests/DashboardSessionTests.swift around lines 114 - 131, The test creates a temporary repository metadata dir at ".pg" but later asserts the file exists under ".pogu", causing a path mismatch; update the test to use the same directory name that DashboardSession.flushToDisk writes to (or vice versa). Locate the test in DashboardSessionTests.swift where dir is created and ensure the created directory (created via FileManager.createDirectory and the addTerminal call) and the expected filePath variable both reference the same folder name (either ".pg" or ".pogu") so flushToDisk writes to the directory the test checks; adjust the dir creation or filePath string accordingly around DashboardSession and flushToDisk usage.
163-178:⚠️ Potential issue | 🔴 CriticalSame directory path mismatch as above.
Line 165 creates
.pgbut line 172 expects.pogu.🐛 Proposed fix
func testFlushCancelsPendingDebouncedWrite() { let dir = NSTemporaryDirectory() + "pogu-test-cancel-\(UUID().uuidString)" - try? FileManager.default.createDirectory(atPath: dir + "/.pg", withIntermediateDirectories: true) + try? FileManager.default.createDirectory(atPath: dir + "/.pogu", withIntermediateDirectories: true) let s = DashboardSession(projectRoot: dir)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PPG` CLI/PPG CLITests/DashboardSessionTests.swift around lines 163 - 178, The test creates a temp worktree directory at dir + "/.pg" but later reads dir + "/.pogu/dashboard-sessions.json", causing a path mismatch; update the directory creation to use the same folder name as the read path (or change the read path to match) so both use the same dot-folder name (adjust the directory creation line that calls FileManager.default.createDirectory and/or the filePath variable used to load dashboard-sessions.json in testFlushCancelsPendingDebouncedWrite of DashboardSessionTests.swift, leaving the rest of the flow using s.addTerminal(...) and s.flushToDisk() unchanged).
133-161:⚠️ Potential issue | 🔴 CriticalSame directory path mismatch as above.
Line 135 creates
.pgbut line 143 expects.pogu.🐛 Proposed fix
func testDebouncedWriteCoalesces() { let dir = NSTemporaryDirectory() + "pogu-test-debounce-\(UUID().uuidString)" - try? FileManager.default.createDirectory(atPath: dir + "/.pg", withIntermediateDirectories: true) + try? FileManager.default.createDirectory(atPath: dir + "/.pogu", withIntermediateDirectories: true) let s = DashboardSession(projectRoot: dir)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PPG` CLI/PPG CLITests/DashboardSessionTests.swift around lines 133 - 161, The testDebouncedWriteCoalesces unit test creates a temporary directory at dir and currently creates a subdirectory ".pg" but later checks for a file under ".pogu" (filePath variable), causing a path mismatch; fix by making the directory creation and expected file path consistent—either create ".pogu" instead of ".pg" in the call within testDebouncedWriteCoalesces (the createDirectory(atPath: dir + "/.pg", ...) call) or change filePath to point at ".pg"; update the symbol references testDebouncedWriteCoalesces, filePath, and the createDirectory invocation so the directory name is identical in both places.
🧹 Nitpick comments (4)
PPG CLI/PPG CLI/SwarmsView.swift (1)
89-89: Extract the swarms subpath into a single constant.The same literal appears at Line 89 and Line 733. Centralizing it avoids drift in future renames.
♻️ Proposed refactor
class SwarmsView: NSView, NSTableViewDataSource, NSTableViewDelegate { + private static let swarmsRelativePath = ".pogu/swarms" @@ - let folder = (ctx.projectRoot as NSString).appendingPathComponent(".pogu/swarms") + let folder = (ctx.projectRoot as NSString).appendingPathComponent(Self.swarmsRelativePath) @@ - let folder = (ctx.projectRoot as NSString).appendingPathComponent(".pogu/swarms") + let folder = (ctx.projectRoot as NSString).appendingPathComponent(Self.swarmsRelativePath)Also applies to: 733-733
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PPG` CLI/PPG CLI/SwarmsView.swift at line 89, Extract the literal ".pogu/swarms" into a single shared constant and use that constant wherever the path is built (e.g., replace occurrences that form folder with (ctx.projectRoot as NSString).appendingPathComponent(SWARMS_SUBPATH)). Create the constant near the top of SwarmsView.swift (file-scope or inside the SwarmsView type) named something like SWARMS_SUBPATH and update both the usage that assigns folder and the other occurrence at the later location (the second appearance referenced in the review) to reference this constant so future renames only require one edit.src/commands/init.ts (1)
143-143: Move conductor-context path construction intolib/paths.ts.
path.join(poguDir(projectRoot), 'conductor-context.md')duplicates path composition logic inside a command module; add a helper (e.g.,conductorContextPath(projectRoot)).As per coding guidelines,
**/*.ts: “Use path helper functions fromlib/paths.tsfor all path computation... Never duplicate path logic”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/init.ts` at line 143, Move the path construction out of the command and into lib/paths.ts: add a helper function named conductorContextPath(projectRoot) in lib/paths.ts that returns path.join(poguDir(projectRoot), 'conductor-context.md'), export it, then update src/commands/init.ts to import and use conductorContextPath(projectRoot) instead of computing conductorPath inline; ensure you reuse the existing poguDir symbol inside lib/paths.ts and update any imports/exports accordingly.package.json (1)
7-8: Consider a temporaryppgbin alias for smoother migration.Dropping the old executable name immediately can break existing automation. A one-release alias reduces upgrade friction.
Suggested diff
"bin": { + "ppg": "./dist/cli.js", "pogu": "./dist/cli.js" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 7 - 8, Add a temporary alias in package.json's "bin" mapping so callers of the old executable keep working: keep the existing "pogu": "./dist/cli.js" entry and add "ppg": "./dist/cli.js" (the old name) pointing to the same CLI entry; ship a single release with both names and remove the "ppg" alias in a subsequent major release after notifying users.src/lib/errors.ts (1)
1-12: Export aPgErrorcompatibility alias during the transition.Keeping a
PgErrorexport (aliasingPoguError) preserves compatibility while allowing the new naming to roll out safely.As per coding guidelines `src/lib/errors.ts`: “Use `PgError` hierarchy with typed error codes (`TMUX_NOT_FOUND`, `NOT_GIT_REPO`, `NOT_INITIALIZED`, `MANIFEST_LOCK`, `WORKTREE_NOT_FOUND`, `AGENT_NOT_FOUND`, `MERGE_FAILED`, `INVALID_ARGS`, `AGENTS_RUNNING`, `WAIT_TIMEOUT`, `AGENTS_FAILED`, `NO_SESSION_ID`)”.Suggested diff
export class PoguError extends Error { constructor( message: string, public readonly code: string, public readonly exitCode: number = 1, ) { super(message); this.name = 'PoguError'; } } + +// Backward-compatibility alias for migration period +export { PoguError as PgError };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/errors.ts` around lines 1 - 12, Export a compatibility alias named PgError that points to the new PoguError class so existing consumers keep working; add an export (e.g. export { PoguError as PgError } or equivalent) alongside the PoguError declaration in the same module (where PoguError and TmuxNotFoundError are defined) so the PgError symbol is available and preserves typings during the transition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@PPG` CLI/PPG CLI/AppSettingsManager.swift:
- Around line 33-38: Add a one-time migration that copies legacy UserDefaults
keys named with the "PPG" prefix to the new "Pogu" keys before the app reads or
writes settings: implement a migrateLegacyKeys() method in AppSettingsManager
that checks for each legacy key (e.g. "PPGRefreshInterval", "PPGTerminalFont",
"PPGTerminalFontSize", "PPGShell", "PPGHistoryLimit", "PPGAppearanceMode"), and
if the corresponding new key (static lets refreshInterval, terminalFont,
terminalFontSize, shell, historyLimit, appearanceMode) is not already set, copy
the legacy value to the new key and remove the legacy key; call
migrateLegacyKeys() once during startup or in AppSettingsManager initialization
so existing user preferences are preserved on upgrade.
In `@PPG` CLI/PPG CLI/Info.plist:
- Line 7: The Sparkle feed URL in Info.plist currently points to
"https://2witstudios.github.io/pogu/appcast.xml" which returns 404; update the
value of that <string> entry in Info.plist to a valid, published appcast URL (or
remove/disable the NSSparkleFeedURL entry) before release so Sparkle can fetch
updates successfully; ensure the new URL is reachable and serves a valid
appcast.xml.
In `@PPG` CLI/PPG CLI/KeybindingManager.swift:
- Line 104: The new defaultsKey ("PoguCustomKeybindings") will drop existing
users’ bindings because there is no fallback read from the legacy key
"PPGCustomKeybindings"; update KeybindingManager to attempt a read from the
legacy key when loading (e.g., in the initializer or loadBindings method) and if
found migrate that data into the new defaultsKey and remove the old key so
future reads use defaultsKey; reference the private let defaultsKey and the
class KeybindingManager and ensure the migration uses UserDefaults.standard (or
your existing defaults instance) to read legacy data, write it under
defaultsKey, and delete the legacy key.
In `@PPG` CLI/PPG CLI/Models.swift:
- Around line 215-216: Add a one-time UserDefaults migration that runs when this
model is initialized: check for the old keys "PPGRecentProjects",
"PPGLastOpenedProject", and "PPGOpenProjects" and if any exist copy their values
into the new keys (key, lastOpenedKey, and the openProjectsKey used at the code
around line 284) using UserDefaults.standard.set(...), then remove the old keys
with removeObject(forKey:). Ensure the migration is guarded so it only runs once
(e.g., detect presence of any old key or set a migrated flag) and preserve the
original value types (arrays/strings) when copying.
In `@PPG` CLI/PPG CLI/SwarmsView.swift:
- Around line 89-90: Update SwarmsView.swift to add backward-compatible fallback
handling for the swarm folder: replace the hardcoded ".pogu/swarms" occurrences
with a single constant (e.g., swarmsDirectoryName or SwarmsView.swarmsDirectory)
and use that constant wherever the path is constructed; when reading contents
(the code using folder and fm.contentsOfDirectory) if the new path returns empty
or fails, attempt the legacy ".pg/swarms" path and read from it (or perform a
one-time migration by moving files from ".pg/swarms" to the new constant path),
ensuring the code uses the same constant for both reads and writes so future
path changes are centralized.
In `@PPG` CLI/PPG CLITests/PPG_CLITests.swift:
- Around line 9-19: The test fixture mixes sessionName "pogu-test" with tmux
values "ppg-test:1"; update the fixture so tmuxWindow and tmuxTarget use the
session name (e.g., "pogu-test:1") to match sessionName and avoid naming
regressions—specifically change the tmuxWindow in the worktree object
(wt-abc123) and the tmuxTarget in the agent object (ag-def456) to use
"pogu-test:1" and verify other fixture entries use the same sessionName
consistently.
In `@src/commands/init.ts`:
- Around line 88-89: The initCommand currently emits unconditional info(...)
logs and uses console.log which breaks --json mode; update initCommand to stop
emitting plain info/console output when options.json is true and instead call
output(data, options.json) for successful results and outputError(err,
options.json) for failures; replace each unconditional info(...) and console.log
in initCommand (and the other noted message sites inside the same file) with the
appropriate output(...) call, and wrap any human-readable info(...) calls behind
if (!options.json) so interactive output remains available when JSON mode is
off.
- Around line 17-21: Update the user-facing text in src/commands/init.ts that
currently hardcodes the string "master" (look for messages or help text
referencing "master branch") to say "default branch" instead and remove any
implication that the repo's default branch is named master; if the code
programmatically uses the literal "master" in prompts or logging, replace that
with a dynamic reference to the repository's default branch (or a neutral phrase
"default branch") and ensure any guidance still instructs users to use `pogu
spawn` to create worktrees rather than modifying the default branch directly.
---
Outside diff comments:
In `@PPG` CLI/PPG CLITests/DashboardSessionTests.swift:
- Around line 180-199: The test creates and cleans up the wrong hidden directory
name (uses ".pg") but DashboardSession uses ".pogu"; update the test
(testReloadFromDiskCancelsPendingWrite) to create the directory at dir +
"/.pogu" (and remove that path at the end) so the session's
flushToDisk/reloadFromDisk operate on the same on-disk path used by
DashboardSession.
- Around line 114-131: The test creates a temporary repository metadata dir at
".pg" but later asserts the file exists under ".pogu", causing a path mismatch;
update the test to use the same directory name that DashboardSession.flushToDisk
writes to (or vice versa). Locate the test in DashboardSessionTests.swift where
dir is created and ensure the created directory (created via
FileManager.createDirectory and the addTerminal call) and the expected filePath
variable both reference the same folder name (either ".pg" or ".pogu") so
flushToDisk writes to the directory the test checks; adjust the dir creation or
filePath string accordingly around DashboardSession and flushToDisk usage.
- Around line 163-178: The test creates a temp worktree directory at dir +
"/.pg" but later reads dir + "/.pogu/dashboard-sessions.json", causing a path
mismatch; update the directory creation to use the same folder name as the read
path (or change the read path to match) so both use the same dot-folder name
(adjust the directory creation line that calls
FileManager.default.createDirectory and/or the filePath variable used to load
dashboard-sessions.json in testFlushCancelsPendingDebouncedWrite of
DashboardSessionTests.swift, leaving the rest of the flow using
s.addTerminal(...) and s.flushToDisk() unchanged).
- Around line 133-161: The testDebouncedWriteCoalesces unit test creates a
temporary directory at dir and currently creates a subdirectory ".pg" but later
checks for a file under ".pogu" (filePath variable), causing a path mismatch;
fix by making the directory creation and expected file path consistent—either
create ".pogu" instead of ".pg" in the call within testDebouncedWriteCoalesces
(the createDirectory(atPath: dir + "/.pg", ...) call) or change filePath to
point at ".pg"; update the symbol references testDebouncedWriteCoalesces,
filePath, and the createDirectory invocation so the directory name is identical
in both places.
In `@src/commands/ui.ts`:
- Around line 40-41: The mdfind call that builds the Spotlight query uses the
outdated bundle identifier string "com.2wit.PPG-CLI"; update the query argument
passed to execa('mdfind', [...]) so the predicate uses the current bundle
identifier "com.2wit.Pogu" (i.e., replace "com.2wit.PPG-CLI" with
"com.2wit.Pogu" in the array passed to execa) so the result variable receives
the correct search output.
---
Nitpick comments:
In `@package.json`:
- Around line 7-8: Add a temporary alias in package.json's "bin" mapping so
callers of the old executable keep working: keep the existing "pogu":
"./dist/cli.js" entry and add "ppg": "./dist/cli.js" (the old name) pointing to
the same CLI entry; ship a single release with both names and remove the "ppg"
alias in a subsequent major release after notifying users.
In `@PPG` CLI/PPG CLI/SwarmsView.swift:
- Line 89: Extract the literal ".pogu/swarms" into a single shared constant and
use that constant wherever the path is built (e.g., replace occurrences that
form folder with (ctx.projectRoot as
NSString).appendingPathComponent(SWARMS_SUBPATH)). Create the constant near the
top of SwarmsView.swift (file-scope or inside the SwarmsView type) named
something like SWARMS_SUBPATH and update both the usage that assigns folder and
the other occurrence at the later location (the second appearance referenced in
the review) to reference this constant so future renames only require one edit.
In `@src/commands/init.ts`:
- Line 143: Move the path construction out of the command and into lib/paths.ts:
add a helper function named conductorContextPath(projectRoot) in lib/paths.ts
that returns path.join(poguDir(projectRoot), 'conductor-context.md'), export it,
then update src/commands/init.ts to import and use
conductorContextPath(projectRoot) instead of computing conductorPath inline;
ensure you reuse the existing poguDir symbol inside lib/paths.ts and update any
imports/exports accordingly.
In `@src/lib/errors.ts`:
- Around line 1-12: Export a compatibility alias named PgError that points to
the new PoguError class so existing consumers keep working; add an export (e.g.
export { PoguError as PgError } or equivalent) alongside the PoguError
declaration in the same module (where PoguError and TmuxNotFoundError are
defined) so the PgError symbol is available and preserves typings during the
transition.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (76)
.claude-plugin/marketplace.json.claude-plugin/plugin.jsonCHANGELOG.mdCLAUDE.mdCONTRIBUTING.mdPPG CLI/PPG CLI.xcodeproj/project.pbxprojPPG CLI/PPG CLI/AppDelegate.swiftPPG CLI/PPG CLI/AppSettingsManager.swiftPPG CLI/PPG CLI/ContentTabViewController.swiftPPG CLI/PPG CLI/DashboardSession.swiftPPG CLI/PPG CLI/DashboardSplitViewController.swiftPPG CLI/PPG CLI/HomeDashboardView.swiftPPG CLI/PPG CLI/Info.plistPPG CLI/PPG CLI/KeybindingManager.swiftPPG CLI/PPG CLI/Models.swiftPPG CLI/PPG CLI/PPGService.swiftPPG CLI/PPG CLI/ProjectPickerViewController.swiftPPG CLI/PPG CLI/PromptsView.swiftPPG CLI/PPG CLI/SetupViewController.swiftPPG CLI/PPG CLI/ShellUtils.swiftPPG CLI/PPG CLI/SidebarViewController.swiftPPG CLI/PPG CLI/SwarmsView.swiftPPG CLI/PPG CLITests/DashboardSessionTests.swiftPPG CLI/PPG CLITests/PPGServiceTests.swiftPPG CLI/PPG CLITests/PPG_CLITests.swiftPPG CLI/PPG CLITests/SidebarViewControllerTests.swiftREADME.mdSECURITY.mdappcast.xmlpackage.jsonskills/pogu-conductor/SKILL.mdskills/pogu-conductor/references/commands.mdskills/pogu-conductor/references/conductor.mdskills/pogu-conductor/references/modes.mdskills/pogu/SKILL.mdsrc/cli.tssrc/commands/attach.tssrc/commands/clean.tssrc/commands/init.tssrc/commands/install-dashboard.tssrc/commands/kill.tssrc/commands/list.tssrc/commands/logs.tssrc/commands/merge.tssrc/commands/pr.test.tssrc/commands/pr.tssrc/commands/reset.test.tssrc/commands/reset.tssrc/commands/restart.tssrc/commands/spawn.tssrc/commands/status.tssrc/commands/swarm.tssrc/commands/ui.test.tssrc/commands/ui.tssrc/commands/wait.tssrc/commands/worktree.tssrc/core/agent.tssrc/core/cleanup.test.tssrc/core/config.test.tssrc/core/config.tssrc/core/manifest.test.tssrc/core/pr.test.tssrc/core/self.test.tssrc/core/swarm.test.tssrc/core/swarm.tssrc/core/template.test.tssrc/core/tmux.tssrc/lib/errors.test.tssrc/lib/errors.tssrc/lib/name.tssrc/lib/paths.test.tssrc/lib/paths.tssrc/lib/vars.test.tssrc/lib/vars.tssrc/test-fixtures.tsvision.md
| static let refreshInterval = "PoguRefreshInterval" | ||
| static let terminalFont = "PoguTerminalFont" | ||
| static let terminalFontSize = "PoguTerminalFontSize" | ||
| static let shell = "PoguShell" | ||
| static let historyLimit = "PoguHistoryLimit" | ||
| static let appearanceMode = "PoguAppearanceMode" |
There was a problem hiding this comment.
UserDefaults key rename will reset existing user settings without migration.
Switching persisted keys from PPG* to Pogu* drops previously saved preferences (font, shell, interval, history, appearance) on upgrade. Add a one-time migration path from legacy keys to new keys.
🛠️ Proposed migration pattern
final class AppSettingsManager {
static let shared = AppSettingsManager()
@@
private enum Key {
static let refreshInterval = "PoguRefreshInterval"
static let terminalFont = "PoguTerminalFont"
static let terminalFontSize = "PoguTerminalFontSize"
static let shell = "PoguShell"
static let historyLimit = "PoguHistoryLimit"
static let appearanceMode = "PoguAppearanceMode"
}
+
+ private enum LegacyKey {
+ static let refreshInterval = "PPGRefreshInterval"
+ static let terminalFont = "PPGTerminalFont"
+ static let terminalFontSize = "PPGTerminalFontSize"
+ static let shell = "PPGShell"
+ static let historyLimit = "PPGHistoryLimit"
+ static let appearanceMode = "PPGAppearanceMode"
+ }
@@
- private init() {}
+ private init() {
+ migrateLegacyKeysIfNeeded()
+ }
+
+ private func migrateLegacyKeysIfNeeded() {
+ let pairs: [(old: String, new: String)] = [
+ (LegacyKey.refreshInterval, Key.refreshInterval),
+ (LegacyKey.terminalFont, Key.terminalFont),
+ (LegacyKey.terminalFontSize, Key.terminalFontSize),
+ (LegacyKey.shell, Key.shell),
+ (LegacyKey.historyLimit, Key.historyLimit),
+ (LegacyKey.appearanceMode, Key.appearanceMode)
+ ]
+ for pair in pairs where defaults.object(forKey: pair.new) == nil {
+ if let value = defaults.object(forKey: pair.old) {
+ defaults.set(value, forKey: pair.new)
+ }
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLI/AppSettingsManager.swift around lines 33 - 38, Add a
one-time migration that copies legacy UserDefaults keys named with the "PPG"
prefix to the new "Pogu" keys before the app reads or writes settings: implement
a migrateLegacyKeys() method in AppSettingsManager that checks for each legacy
key (e.g. "PPGRefreshInterval", "PPGTerminalFont", "PPGTerminalFontSize",
"PPGShell", "PPGHistoryLimit", "PPGAppearanceMode"), and if the corresponding
new key (static lets refreshInterval, terminalFont, terminalFontSize, shell,
historyLimit, appearanceMode) is not already set, copy the legacy value to the
new key and remove the legacy key; call migrateLegacyKeys() once during startup
or in AppSettingsManager initialization so existing user preferences are
preserved on upgrade.
| <dict> | ||
| <key>SUFeedURL</key> | ||
| <string>https://2witstudios.github.io/ppg-cli/appcast.xml</string> | ||
| <string>https://2witstudios.github.io/pogu/appcast.xml</string> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
url="https://2witstudios.github.io/pogu/appcast.xml"
echo "Checking HTTP headers..."
curl -fsSLI "$url" 2>&1 | head -20
echo
echo "Checking appcast payload prefix..."
curl -fsSL "$url" 2>&1 | head -40Repository: 2witstudios/ppg-cli
Length of output: 702
Publish the Sparkle feed URL before release. The appcast.xml endpoint currently returns a 404 error. Releasing with this URL will cause silent update failures for users.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLI/Info.plist at line 7, The Sparkle feed URL in Info.plist
currently points to "https://2witstudios.github.io/pogu/appcast.xml" which
returns 404; update the value of that <string> entry in Info.plist to a valid,
published appcast URL (or remove/disable the NSSparkleFeedURL entry) before
release so Sparkle can fetch updates successfully; ensure the new URL is
reachable and serves a valid appcast.xml.
| static let shared = KeybindingManager() | ||
|
|
||
| private let defaultsKey = "PPGCustomKeybindings" | ||
| private let defaultsKey = "PoguCustomKeybindings" |
There was a problem hiding this comment.
Add migration fallback for legacy UserDefaults key.
Line 104 changes the persistence key, but there’s no read fallback for "PPGCustomKeybindings". Existing users will silently lose custom shortcuts after upgrade.
💡 Proposed migration-safe fix
final class KeybindingManager {
static let shared = KeybindingManager()
private let defaultsKey = "PoguCustomKeybindings"
+ private let legacyDefaultsKey = "PPGCustomKeybindings"
private var customBindings: [String: StoredBinding] = [:]
@@
private func loadBindings() {
- guard let data = UserDefaults.standard.data(forKey: defaultsKey),
- let decoded = try? JSONDecoder().decode([String: StoredBinding].self, from: data) else {
- return
+ let defaults = UserDefaults.standard
+
+ if let data = defaults.data(forKey: defaultsKey),
+ let decoded = try? JSONDecoder().decode([String: StoredBinding].self, from: data) {
+ customBindings = decoded
+ return
+ }
+
+ // One-time migration from old branding key.
+ if let legacyData = defaults.data(forKey: legacyDefaultsKey),
+ let decoded = try? JSONDecoder().decode([String: StoredBinding].self, from: legacyData) {
+ customBindings = decoded
+ saveBindings()
+ defaults.removeObject(forKey: legacyDefaultsKey)
}
- customBindings = decoded
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLI/KeybindingManager.swift at line 104, The new defaultsKey
("PoguCustomKeybindings") will drop existing users’ bindings because there is no
fallback read from the legacy key "PPGCustomKeybindings"; update
KeybindingManager to attempt a read from the legacy key when loading (e.g., in
the initializer or loadBindings method) and if found migrate that data into the
new defaultsKey and remove the old key so future reads use defaultsKey;
reference the private let defaultsKey and the class KeybindingManager and ensure
the migration uses UserDefaults.standard (or your existing defaults instance) to
read legacy data, write it under defaultsKey, and delete the legacy key.
| private let key = "PoguRecentProjects" | ||
| private let lastOpenedKey = "PoguLastOpenedProject" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if there's any migration logic for UserDefaults keys in the codebase
rg -n "PPGRecentProjects|PPGLastOpenedProject|PPGOpenProjects" --type swiftRepository: 2witstudios/ppg-cli
Length of output: 45
🏁 Script executed:
# Search for migration-related code
rg -n "migrat|UserDefaults" --type swift | head -50Repository: 2witstudios/ppg-cli
Length of output: 1322
🏁 Script executed:
# Search for any init methods or app startup code that might handle data migration
rg -n "func.*init|applicationDidFinishLaunching|application.*didFinishLaunchingWithOptions" --type swift -A 5Repository: 2witstudios/ppg-cli
Length of output: 1686
🏁 Script executed:
# Examine AppDelegate's applicationDidFinishLaunching method more thoroughly
sed -n '6,50p' 'PPG CLI/PPG CLI/AppDelegate.swift'Repository: 2witstudios/ppg-cli
Length of output: 1771
🏁 Script executed:
# Find the RecentProjects class and check its initialization
rg -n "class RecentProjects|struct RecentProjects" --type swift -A 20Repository: 2witstudios/ppg-cli
Length of output: 1390
🏁 Script executed:
# Check if RecentProjects has an init method
rg -n "class RecentProjects" --type swift -A 50 | grep -E "init|private|func"Repository: 2witstudios/ppg-cli
Length of output: 596
Add UserDefaults migration logic for existing users.
The UserDefaults keys have been renamed from PPGRecentProjects and PPGLastOpenedProject to PoguRecentProjects and PoguLastOpenedProject (also affects PPGOpenProjects → PoguOpenProjects at line 284). Existing users upgrading will lose their recent projects history since the app reads from the new keys which are empty.
Add a one-time migration on first launch that copies data from old keys to new keys, then removes the old keys to prevent stale data.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLI/Models.swift around lines 215 - 216, Add a one-time
UserDefaults migration that runs when this model is initialized: check for the
old keys "PPGRecentProjects", "PPGLastOpenedProject", and "PPGOpenProjects" and
if any exist copy their values into the new keys (key, lastOpenedKey, and the
openProjectsKey used at the code around line 284) using
UserDefaults.standard.set(...), then remove the old keys with
removeObject(forKey:). Ensure the migration is guarded so it only runs once
(e.g., detect presence of any old key or set a migrated flag) and preserve the
original value types (arrays/strings) when copying.
| let folder = (ctx.projectRoot as NSString).appendingPathComponent(".pogu/swarms") | ||
| guard let files = try? fm.contentsOfDirectory(atPath: folder) else { continue } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, locate the SwarmsView.swift file
find . -name "SwarmsView.swift" -type fRepository: 2witstudios/ppg-cli
Length of output: 97
🏁 Script executed:
# Get file size to determine reading strategy
wc -l "PPG CLI/PPG CLI/SwarmsView.swift" 2>/dev/null || wc -l $(find . -name "SwarmsView.swift" -type f)Repository: 2witstudios/ppg-cli
Length of output: 99
🏁 Script executed:
# Run the provided verification script to search for legacy/migration handling
rg -n --type=swift '\.pg/swarms|\.pogu/swarms' -C2Repository: 2witstudios/ppg-cli
Length of output: 946
🏁 Script executed:
# Search for migration, legacy, or copy/move operations across the codebase
rg -n --type=swift 'migrat|legacy|moveItem|copyItem' -C2Repository: 2witstudios/ppg-cli
Length of output: 4728
🏁 Script executed:
# Also check for any constants or string literals that define paths
rg -n --type=swift '(let|var|static).*swarms|\.pogu|\.pg' -B1 -A1Repository: 2witstudios/ppg-cli
Length of output: 11898
🏁 Script executed:
# Get context around line 89 to see if there's fallback logic
sed -n '85,95p' "PPG CLI/PPG CLI/SwarmsView.swift"Repository: 2witstudios/ppg-cli
Length of output: 682
🏁 Script executed:
# Get context around line 733 to see the write operation
sed -n '730,745p' "PPG CLI/PPG CLI/SwarmsView.swift"Repository: 2witstudios/ppg-cli
Length of output: 788
🏁 Script executed:
# Search for any reference to old .pg directory or migration
rg -n '\.pg' --type=swiftRepository: 2witstudios/ppg-cli
Length of output: 698
Add backward-compatibility fallback for .pg/swarms location.
Line 89 reads only from .pogu/swarms. Users upgrading with existing swarm files in .pg/swarms will see no swarms. Add fallback scanning for the legacy path when the new path is empty, or implement a one-time migration to avoid data loss.
Additionally, the path .pogu/swarms is hardcoded identically at lines 89 and 733. Extract this to a constant to simplify future path changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLI/SwarmsView.swift around lines 89 - 90, Update
SwarmsView.swift to add backward-compatible fallback handling for the swarm
folder: replace the hardcoded ".pogu/swarms" occurrences with a single constant
(e.g., swarmsDirectoryName or SwarmsView.swarmsDirectory) and use that constant
wherever the path is constructed; when reading contents (the code using folder
and fm.contentsOfDirectory) if the new path returns empty or fails, attempt the
legacy ".pg/swarms" path and read from it (or perform a one-time migration by
moving files from ".pg/swarms" to the new constant path), ensuring the code uses
the same constant for both reads and writes so future path changes are
centralized.
| "sessionName": "pogu-test", | ||
| "worktrees": { | ||
| "wt-abc123": { | ||
| "id": "wt-abc123", "name": "feature-x", "path": "/tmp/test/.worktrees/wt-abc123", | ||
| "branch": "ppg/feature-x", "baseBranch": "main", "status": "active", | ||
| "branch": "pogu/feature-x", "baseBranch": "main", "status": "active", | ||
| "tmuxWindow": "ppg-test:1", | ||
| "agents": { | ||
| "ag-def456": { | ||
| "id": "ag-def456", "name": "claude", "agentType": "claude", | ||
| "status": "running", "tmuxTarget": "ppg-test:1", | ||
| "prompt": "Do something", "resultFile": "/tmp/test/.pg/results/ag-def456.md", | ||
| "prompt": "Do something", "resultFile": "/tmp/test/.pogu/results/ag-def456.md", |
There was a problem hiding this comment.
Complete the fixture rebrand for tmux targets.
The fixture now mixes pogu-test (session name) with ppg-test:1 (tmuxWindow/tmuxTarget). This can hide naming regressions in tests.
Suggested fix
- "tmuxWindow": "ppg-test:1",
+ "tmuxWindow": "pogu-test:1",
...
- "status": "running", "tmuxTarget": "ppg-test:1",
+ "status": "running", "tmuxTarget": "pogu-test:1",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "sessionName": "pogu-test", | |
| "worktrees": { | |
| "wt-abc123": { | |
| "id": "wt-abc123", "name": "feature-x", "path": "/tmp/test/.worktrees/wt-abc123", | |
| "branch": "ppg/feature-x", "baseBranch": "main", "status": "active", | |
| "branch": "pogu/feature-x", "baseBranch": "main", "status": "active", | |
| "tmuxWindow": "ppg-test:1", | |
| "agents": { | |
| "ag-def456": { | |
| "id": "ag-def456", "name": "claude", "agentType": "claude", | |
| "status": "running", "tmuxTarget": "ppg-test:1", | |
| "prompt": "Do something", "resultFile": "/tmp/test/.pg/results/ag-def456.md", | |
| "prompt": "Do something", "resultFile": "/tmp/test/.pogu/results/ag-def456.md", | |
| "sessionName": "pogu-test", | |
| "worktrees": { | |
| "wt-abc123": { | |
| "id": "wt-abc123", "name": "feature-x", "path": "/tmp/test/.worktrees/wt-abc123", | |
| "branch": "pogu/feature-x", "baseBranch": "main", "status": "active", | |
| "tmuxWindow": "pogu-test:1", | |
| "agents": { | |
| "ag-def456": { | |
| "id": "ag-def456", "name": "claude", "agentType": "claude", | |
| "status": "running", "tmuxTarget": "pogu-test:1", | |
| "prompt": "Do something", "resultFile": "/tmp/test/.pogu/results/ag-def456.md", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@PPG` CLI/PPG CLITests/PPG_CLITests.swift around lines 9 - 19, The test
fixture mixes sessionName "pogu-test" with tmux values "ppg-test:1"; update the
fixture so tmuxWindow and tmuxTarget use the session name (e.g., "pogu-test:1")
to match sessionName and avoid naming regressions—specifically change the
tmuxWindow in the worktree object (wt-abc123) and the tmuxTarget in the agent
object (ag-def456) to use "pogu-test:1" and verify other fixture entries use the
same sessionName consistently.
| You are operating on the master branch of a pogu-managed project. | ||
|
|
||
| ## Critical Rule | ||
| **NEVER make code changes directly on the master branch.** Use \`ppg spawn\` to create worktrees. | ||
| **NEVER make code changes directly on the master branch.** Use \`pogu spawn\` to create worktrees. | ||
|
|
There was a problem hiding this comment.
Generated conductor context hardcodes master branch.
This can mislead users/automation in repositories using main (or another default branch). Prefer “default branch” wording.
✏️ Suggested wording update
-You are operating on the master branch of a pogu-managed project.
+You are operating on the default branch of a pogu-managed project (usually `main`).
@@
-**NEVER make code changes directly on the master branch.** Use `pogu spawn` to create worktrees.
+**NEVER make code changes directly on the default branch.** Use `pogu spawn` to create worktrees.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| You are operating on the master branch of a pogu-managed project. | |
| ## Critical Rule | |
| **NEVER make code changes directly on the master branch.** Use \`ppg spawn\` to create worktrees. | |
| **NEVER make code changes directly on the master branch.** Use \`pogu spawn\` to create worktrees. | |
| You are operating on the default branch of a pogu-managed project (usually `main`). | |
| ## Critical Rule | |
| **NEVER make code changes directly on the default branch.** Use `pogu spawn` to create worktrees. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/init.ts` around lines 17 - 21, Update the user-facing text in
src/commands/init.ts that currently hardcodes the string "master" (look for
messages or help text referencing "master branch") to say "default branch"
instead and remove any implication that the repo's default branch is named
master; if the code programmatically uses the literal "master" in prompts or
logging, replace that with a dynamic reference to the repository's default
branch (or a neutral phrase "default branch") and ensure any guidance still
instructs users to use `pogu spawn` to create worktrees rather than modifying
the default branch directly.
| info('Created .pogu/ directory structure'); | ||
|
|
There was a problem hiding this comment.
--json mode is not machine-readable due to unconditional logs and direct console.log.
initCommand emits info(...) messages regardless of options.json, then prints JSON manually. This breaks parsable JSON output for automation.
✅ Proposed fix
-import { success, info } from '../lib/output.js';
+import { success, info, output } from '../lib/output.js';
@@
export async function initCommand(options: { json?: boolean }): Promise<void> {
+ const json = options.json ?? false;
+ const logInfo = (message: string): void => {
+ if (!json) info(message);
+ };
const cwd = process.cwd();
@@
- info('Created .pogu/ directory structure');
+ logInfo('Created .pogu/ directory structure');
@@
- info('config.yaml already exists, skipping');
+ logInfo('config.yaml already exists, skipping');
@@
- info('Wrote default config.yaml');
+ logInfo('Wrote default config.yaml');
@@
- info('Wrote empty manifest.json');
+ logInfo('Wrote empty manifest.json');
@@
- info('Updated .gitignore');
+ logInfo('Updated .gitignore');
@@
- info('Wrote sample template: default.md');
+ logInfo('Wrote sample template: default.md');
@@
- info(`Wrote prompt: ${name}.md`);
+ logInfo(`Wrote prompt: ${name}.md`);
@@
- info(`Wrote swarm template: ${name}.yaml`);
+ logInfo(`Wrote swarm template: ${name}.yaml`);
@@
- info('Wrote conductor-context.md');
+ logInfo('Wrote conductor-context.md');
@@
- info('Registered pogu Claude Code plugin');
+ logInfo('Registered pogu Claude Code plugin');
@@
- if (options.json) {
- console.log(JSON.stringify({
+ if (json) {
+ output({
success: true,
projectRoot,
sessionName,
poguDir: poguDir(projectRoot),
pluginRegistered,
- }));
+ }, true);
} else {
success(`pogu initialized in ${projectRoot}`);
}
}Also applies to: 94-98, 105-109, 117-118, 127-128, 138-139, 145-145, 150-150, 153-163
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/init.ts` around lines 88 - 89, The initCommand currently emits
unconditional info(...) logs and uses console.log which breaks --json mode;
update initCommand to stop emitting plain info/console output when options.json
is true and instead call output(data, options.json) for successful results and
outputError(err, options.json) for failures; replace each unconditional
info(...) and console.log in initCommand (and the other noted message sites
inside the same file) with the appropriate output(...) call, and wrap any
human-readable info(...) calls behind if (!options.json) so interactive output
remains available when JSON mode is off.
Summary
ppg(Pure Point Guard) topogu(pointguard) across the entire codebasepure-point-guard→pointguard, CLI binary:ppg→pogu.pg/→.pogu/, branch prefix:ppg/→pogu/PgError→PoguError, path helper:pgDir()→poguDir()Changes (643 lines across 76 files)
Core layer (src/lib, src/types, src/core)
PG_DIR→POGU_DIR,pgDir()→poguDir(),PgError→PoguErrorCommands layer (src/commands, src/cli.ts)
.name('pogu'), all user-facing strings, imports, branch prefixesDocs, skills, config
package.json: name/bin/description/URLs updatedCLAUDE.md,README.md,vision.md,CONTRIBUTING.md,SECURITY.md,CHANGELOG.mdskills/ppg→skills/pogu,skills/ppg-conductor→skills/pogu-conductorSwift macOS app
PPGService→PoguService,runPPGCommand→runPoguCommandPPG CLI/) intentionally preserved (requires Xcode refactoring)Test plan
npm run typecheckpassesnpm run buildsucceeds — CLI binary responds topogu --versiongrep -rn 'PgError\|pgDir\|PG_DIR' src/returns zero hitsnpm linkand verifypogu spawn/status/resetwork end-to-end🤖 Generated with Claude Code
Summary by CodeRabbit
ppgtopogupure-point-guardtopointguardon npm.pgto.pogufor configuration, templates, and resultsppg/...topogu/...