pi-task-tracker 0.2.0 → 0.2.1
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/README.md +1 -1
- package/index.ts +20 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Optional `taskTracker` key in `~/.pi/agent/settings.json` (all default to `true`
|
|
|
75
75
|
- `todo` — maintain `TODO.md`
|
|
76
76
|
- `readme` — maintain the `README.md` project-structure section
|
|
77
77
|
- `autoCommit` — per-task commits; these are the `/rollback` checkpoints, so disabling this also disables rollback targets
|
|
78
|
-
- `externalWrite` — policy for tool calls that would create/modify files OUTSIDE the session directory: `"block"` (default, hard deny — external changes cannot be tracked or rolled back), `"ask"` (per-attempt confirmation), `"off"` (no enforcement; `pi-permission-system` rules still apply). Reads stay free. Copying files FROM outside INTO the session is allowed (`cp` checks only its destination); moving/deleting external paths is blocked. Git history protection is **inherited**: history-rewriting git (rebase/reset/amend/force-push/clean/...) prompts for confirmation right here, so `pi-permission-system` can be uninstalled without losing that layer; routine git passes untouched, an external `git init`/`clone` is allowed with a heads-up that the new repo is not tracked. Shell commands are analyzed statically (redirects, `rm/mv/cp/touch/...`, `cd` tracking, PowerShell equivalents); git protection is inherited from `pi-permission-system` — routine git passes untouched and history-rewriting git still prompts there; only redirect targets and `-C` destinations on git segments are checked.
|
|
78
|
+
- `externalWrite` — policy for tool calls that would create/modify files OUTSIDE the session directory: `"block"` (default, hard deny — external changes cannot be tracked or rolled back), `"ask"` (per-attempt confirmation), `"off"` (no enforcement; `pi-permission-system` rules still apply). Reads stay free. Copying files FROM outside INTO the session is allowed (`cp` checks only its destination); moving/deleting external paths is blocked. Git history protection is **inherited**: history-rewriting git (rebase/reset/amend/force-push/clean/...) prompts for confirmation right here, so `pi-permission-system` can be uninstalled without losing that layer; routine git passes untouched, an external `git init`/`clone` is allowed with a heads-up that the new repo is not tracked. Shell commands are analyzed statically (redirects, `rm/mv/cp/touch/...`, `cd` tracking, PowerShell equivalents); git protection is inherited from `pi-permission-system` — routine git passes untouched and history-rewriting git still prompts there; only redirect targets and `-C` destinations on git segments are checked. **`cd`/`Set-Location` are disabled outright** (sandbox design: pi's bash tool runs each command in a fresh child process pinned to the session directory anyway, so a `cd` can never move the session — denying it keeps the analysis simple and the writable scope exactly the trackable scope).
|
|
79
79
|
|
|
80
80
|
## Requirements
|
|
81
81
|
|
package/index.ts
CHANGED
|
@@ -144,15 +144,15 @@ const GUARD_PS_WRITE_CMDS = new Set([
|
|
|
144
144
|
|
|
145
145
|
// Resolve a shell token to an absolute path; unresolvable tokens (env vars,
|
|
146
146
|
// command substitution) are treated as external - conservative by design.
|
|
147
|
-
function guardResolve(
|
|
147
|
+
function guardResolve(sessionCwd: string, token: string): { abs: string; external: boolean } {
|
|
148
148
|
let t = token.replace(/^["']|["']$/g, "").trim();
|
|
149
|
-
if (!t) return { abs:
|
|
149
|
+
if (!t) return { abs: sessionCwd, external: false };
|
|
150
150
|
if (t.startsWith("~")) return { abs: t, external: true };
|
|
151
151
|
if (/[\$`]/.test(t)) return { abs: t, external: true };
|
|
152
152
|
if (/^[A-Za-z]:[\\/]/.test(t) || t.startsWith("/")) {
|
|
153
153
|
return { abs: t, external: isExternalAbs(t, sessionCwd) };
|
|
154
154
|
}
|
|
155
|
-
const abs = path.resolve(
|
|
155
|
+
const abs = path.resolve(sessionCwd, t);
|
|
156
156
|
return { abs, external: isExternalAbs(abs, sessionCwd) };
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -172,30 +172,31 @@ function guardIsDevNull(t: string): boolean {
|
|
|
172
172
|
// write-command arguments (copy-like: destination only), interpreter payloads
|
|
173
173
|
// mentioning external absolute paths, cd tracking. Git arguments are NOT
|
|
174
174
|
// analyzed (inherited protection: only history-rewriting git asks).
|
|
175
|
+
// Analyze one shell command. cd/Set-Location are DISABLED outright (sandbox
|
|
176
|
+
// design: the process cwd is pinned to the session directory, which also
|
|
177
|
+
// removes any need for cd-tracking in this analysis). Returns
|
|
178
|
+
// { block, ask, notify } hits. External-write analysis is best effort:
|
|
179
|
+
// redirect targets, write-command arguments (copy-like: destination only),
|
|
180
|
+
// interpreter payloads mentioning external absolute paths. Git arguments are
|
|
181
|
+
// NOT analyzed (inherited protection: only history-rewriting git asks).
|
|
175
182
|
function guardShellExternalWrite(cmd: string, sessionCwd: string): { block?: string; ask?: string; notify?: string } | null {
|
|
176
183
|
const segments = cmd.split(/\n|&&|\|\||;|\|/).map((s) => s.trim()).filter(Boolean);
|
|
177
|
-
let effCwd = sessionCwd;
|
|
178
184
|
const out: { block?: string; ask?: string; notify?: string } = {};
|
|
179
185
|
for (const seg of segments) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// switch to the real effective cwd even when external: only WRITE ops
|
|
184
|
-
// are analyzed below, so `cd elsewhere && cat f` still passes while
|
|
185
|
-
// `cd elsewhere && touch a.txt` is correctly judged external.
|
|
186
|
-
effCwd = r.abs;
|
|
187
|
-
continue;
|
|
186
|
+
if (/^(cd|sl|set-location)\s/i.test(seg)) {
|
|
187
|
+
out.block = "cd is disabled in this sandbox; run commands from the session directory";
|
|
188
|
+
return out;
|
|
188
189
|
}
|
|
189
190
|
// 2. redirects: > >> 2> 2>> &> &>> >| (not heredocs, not fd dups like 2>&1)
|
|
190
191
|
for (const m of seg.matchAll(/(?:^|[^<>&=(])(?:\d?>{1,2}|&>>?|>\|)\s*([^\s;&|]+)/g)) {
|
|
191
192
|
const tgt = m[1];
|
|
192
193
|
if (/^&?\d+$/.test(tgt) || guardIsDevNull(tgt)) continue;
|
|
193
|
-
const r = guardResolve(
|
|
194
|
+
const r = guardResolve(sessionCwd, tgt);
|
|
194
195
|
if (r.external) out.block = r.abs;
|
|
195
196
|
}
|
|
196
197
|
const isGit = /^git(\s|$)/.test(seg);
|
|
197
198
|
// 1. inherited git protection: history-rewriting git asks (matches
|
|
198
|
-
// pi-permissions.jsonc bash patterns; prefix match
|
|
199
|
+
// the old pi-permissions.jsonc bash patterns; prefix match)
|
|
199
200
|
if (isGit) {
|
|
200
201
|
const low = seg.toLowerCase();
|
|
201
202
|
if (GUARD_GIT_ASK.some((p) => low.startsWith(p))) {
|
|
@@ -203,7 +204,8 @@ function guardShellExternalWrite(cmd: string, sessionCwd: string): { block?: str
|
|
|
203
204
|
continue;
|
|
204
205
|
}
|
|
205
206
|
if (/^git\s+(init|clone)/.test(low)) {
|
|
206
|
-
const
|
|
207
|
+
const target = (seg.split(/\s+/).filter((x) => x && !x.startsWith("-")).pop()) || ".";
|
|
208
|
+
const r = guardResolve(sessionCwd, target);
|
|
207
209
|
if (r.external) {
|
|
208
210
|
out.notify = `git ${seg.startsWith("git clone") ? "clone" : "init"} outside the session directory (${r.abs}). pi-task-tracker does NOT track that directory; start a separate pi session there if you want it tracked.`;
|
|
209
211
|
}
|
|
@@ -211,7 +213,7 @@ function guardShellExternalWrite(cmd: string, sessionCwd: string): { block?: str
|
|
|
211
213
|
// external -C destination on any git command
|
|
212
214
|
const c = seg.match(/\s-C\s+(\S+)/);
|
|
213
215
|
if (c) {
|
|
214
|
-
const r = guardResolve(
|
|
216
|
+
const r = guardResolve(sessionCwd, c[1]);
|
|
215
217
|
if (r.external) out.block = r.abs;
|
|
216
218
|
}
|
|
217
219
|
continue;
|
|
@@ -241,13 +243,13 @@ function guardShellExternalWrite(cmd: string, sessionCwd: string): { block?: str
|
|
|
241
243
|
}
|
|
242
244
|
const checkList = isCopy ? pathArgs.slice(-1) : pathArgs;
|
|
243
245
|
for (const t of checkList) {
|
|
244
|
-
if (/^[A-Za-z]:[
|
|
246
|
+
if (/^[A-Za-z]:[\\{1,2}]/.test(t) || t.startsWith("/")) { // absolute
|
|
245
247
|
if (guardIsDevNull(t)) continue;
|
|
246
248
|
if (isExternalAbs(t, sessionCwd)) out.block = t;
|
|
247
249
|
} else if (/[\$`~]/.test(t)) {
|
|
248
250
|
out.block = t; // unresolvable -> conservative
|
|
249
251
|
} else {
|
|
250
|
-
const abs = path.resolve(
|
|
252
|
+
const abs = path.resolve(sessionCwd, t);
|
|
251
253
|
if (isExternalAbs(abs, sessionCwd)) out.block = t;
|
|
252
254
|
}
|
|
253
255
|
if (out.block) break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-task-tracker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Task workflow tracking for the pi coding agent: TODO/README maintenance, per-task git auto-commits as checkpoints, and an interactive /rollback. Hashline-aware, nested-repo aware, subagent-artifact-safe.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|