Repository navigation
feat(cli): scope protected-file warnings to a project - #440
NoRiceToday wants to merge 10 commits into
Conversation
df3b043 to
6f84638
Compare
Detections from one scan arrive as one decision dialog instead of one popup per file. The dialog offers, narrow to broad: ignore this exact file, ignore all files in this batch, or ignore every file with that basename in all projects. "Keep warning" is the default. Exceptions are stored in ~/.config/omac/prefs.yaml; files stay protected at launch, only the repeated notice is skipped. The session-end report collapses a burst to one summary line, --verbose lists every path, and the audit log keeps one event per path. Closes #438 🤖 Generated with DeepSeek V4.1 Flash Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
Captures the original noise requirements, the exact-path and batch baseline from #438, the rejected global basename option, and a design for a git-root-scoped name exception. No implementation yet; the tasks list the follow-up work. Refs #438 🤖 Generated with DeepSeek V4.1 Flash Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
A worktree created under .worktrees/<name> brings new .env files with a new path, so an exact-path exception never covers the next worktree. Add one broader dialog choice: ignore a basename inside the repository. The root comes from git common dir, which is shared by every worktree of the repo, and is accepted only when it contains the workdir. That keeps the scope off other projects and off a forged .git. Global basename exceptions stay out: .env.local often holds real secrets. The file stays protected at launch and every detection is still audited. Exact-path and batch choices are unchanged. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
core.worktree lives in a config file the sandboxed agent can write, and git rev-parse --show-toplevel honours it. A forged .git pointer could therefore scope a project exception to a parent tree that holds other checkouts. Discover the root by walking up to the first .git entry instead. A .git directory roots at its parent. A .git file roots at the main repository only when sandboxrun.ResolveWorktreeCommonDir validates the worktree back-pointer and layout; a submodule, separate git dir, or forged gitfile stays at the directory that holds the file. The containment check plus the root/home/shallow-ancestor gates reject every broader scope. Lift the duplicated canonicalisation and containment helpers into internal/pathutil. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
Three related defects in the mid-session watch: - A broken prefs.yaml was silently replaced with an empty store, dropping every stored preference on the next save. Keep the load error, run the watch with an in-memory-only store, and record one notice. - "Ignore all" rewrote prefs.yaml once per path. Add AddProtectedWatchIgnores, which appends the missing paths and saves once. - Exact-path matching used filepath.Clean, so a symlinked spelling re-prompted. Canonicalise both sides through pathutil. Also document that w.ignored is owned by the watch goroutine, use slices.DeleteFunc so removePath/removePaths stop writing through the caller's slice header, note the scan pause in askExceptions, and show "(8 listed)" when the batch covers more files than the dialog lists. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
The protected-file dialog can offer fourteen long options. The generic zenity/kdialog path passed no width or height, so the batch and project choices could land below the fold. Pass dialogDimensions() into both backends, add --no-markup to zenity, and stop Pango-escaping the plain-text kdialog labels. An option that contains &, <, or > now returns the same string the caller looks up. Delete the unused Choose helper and its duplicated five-minute timeout; ChooseContext already has a caller-owned context. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
The design and tasks still described the rev-parse invocation that trusted core.worktree. Rewrite the resolution section around the filesystem walk: a .git directory roots at its parent, a validated linked worktree at the main repository, and anything else at the directory holding the .git file. State that core.worktree is ignored and that the filesystem root, $HOME, and too-shallow ancestors are refused. Also correct the dialog wording: "Ignore all N" covers every file in the batch (showing "(8 listed)"), --verbose lists the warnings the user kept, and the 10-second scan pauses while a dialog is open. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
The depth gate refused a strict ancestor with fewer than two path components. That hit legitimate layouts such as /repo or /app: the main checkout got the project option, its worktree did not, even though both resolve to the same git root. Discovery already anchors the root at the directory that owns .git, so the depth heuristic only added a false negative. Keep the two concrete refusals, / and $HOME. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
"Keep warning" read like "warn me again soon", but the watch reports each path once per session. The choice saves no exception and keeps the file in the audit log and the session-end report. Label it "Keep warning for N files (report at exit)" and say in the body and docs what keeping it means. Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
6f84638 to
88a74a7
Compare
|
Thanks for this, the batching, the exact-path exceptions and the one-line summary fix the noise in #438 well. I'd like to suggest that we simplify how the project scope is chosen before we merge: Problem: the git-based root can be wider than the project
Both pass the Proposal: let the current omac workspace folder define the scopeThe case in #438 is a worktree created under So instead of deriving a root from git metadata:
This removes Why I'm suggesting thisIt's a simpler solution, and I'm not aware of a relevant case it doesn't cover. If there is a reason to derive the common folder from git metadata, it isn't obvious to me, so please tell me if I'm missing one. Otherwise I'd go with the simpler design. It's easier to reason about, and it leaves fewer chances for edge cases nobody anticipated. The two cases above would be two examples of non-obvious behavior. Other findings – Independent of the scope question
|
Agent-writable Git metadata must not select a warning exception scope. Offer the launch folder and explicit parent choices with full paths. Merge concurrent preference updates under a shared file lock and return context errors when a macOS decision dialog times out. Refs #438 Assisted-by: OpenAI GPT-6.1 Sol Signed-off-by: Sajjad Ahmad <sajjad.ahmad@tngtech.com>
| workdir string | ||
| projectRoot string | ||
| projectRootTried bool | ||
| // workdir is fixed at launch. Folder choices come only from this path, |
There was a problem hiding this comment.
Nitpick: This comment doesn't really make sense without the context of the discussion on this PR.
|
After the latest commit, the flowchart in the PR description doesn't match the implemented behavior anymore. The same is true for the openspec files, which is actually more important (since these might influence future agents). |

Issue: Closes #438
What
Why
Creating a worktree can generate a burst of protected-pattern files such as
.envand.env.local. omac cannot tell whether they are local defaults or real secrets, so it warns. The old watch produced one popup per file; an exact path does not cover the next worktree, and a global basename exception would silence that name in every project. A project-scoped exception is the narrowest scope that covers future worktrees of the same repository.How
.gitentry; a validated linked worktree maps to the main repository.core.worktreeis never read.{root, name}in~/.config/omac/prefs.yaml; match only below the canonical root./and$HOMEas roots. Non-git projects and worktrees outside the repository keep exact-path and batch choices.Diagram
flowchart TD W[workdir] --> G{first .git entry} G -- directory --> R[parent = repo root] G -- file --> V{validated linked worktree?} V -- yes --> M[parent of common dir = main repo] V -- no --> L[directory holding .git = repo root] R --> C{contains workdir and<br/>not / or $HOME?} M --> C L --> C C -- yes --> P[project option offered] C -- no --> N[no project option]Verification
go build ./...— passgo vet ./internal/cli ./internal/prefs ./internal/netprompt ./internal/sandboxrun— passgo test ./internal/prefs ./internal/netprompt ./internal/pathutil— passgo test -race ./internal/cli -run 'Protected|GitProjectRoot|PathUnder|AskExceptions'— passgo test ./internal/sandboxrun -run 'Worktree|Grant' -skip 'Integration'— passinternal/clistill fails only on pre-existing sandbox limits (/tmp,~/.config/omac).Follow-up
Revoking a project exception still means editing
prefs.yaml.