template-jj/cmd/planctl/proc_linux.go
sid 935b86b791 feat(planctl): context-window token awareness — core (parent 1.0)
- TokenCtx + Classify + formatTokenCount (R2.2, R3.1-3.5, R4.4)
- ReadTokenCtx orchestrator: env > PID walk > transcript parse (R1.1-1.6, R2.1-2.6)
- parseLastUsage: 10MB scanner, JSONL last-assistant-wins, zero-usage qualifies (R2.1-2.3)
- parsePIDFile: strict two-line layout; rejects partial/corrupted writes (R6.2-6.3)
- resolveViaPIDWalk: 8-hop ancestor walk, liveness + start-stamp check (R1.2, R6.1, R6.3)
- proc_darwin.go (x/sys/unix kinfo_proc) + proc_linux.go (/proc/stat field 22)
- parseStatStartTime: platform-agnostic, handles comm with parens/spaces

Parent 1.0 from dev/plans/26174-planctl-context-tokens/tasks.md
2026-04-23 17:38:37 -06:00

61 lines
1.7 KiB
Go

//go:build linux
package main
import (
"fmt"
"os"
"strconv"
"strings"
"syscall"
)
// spec:26174-planctl-context-tokens/R1.2+R6.3+D§3.3
// ppidOf returns the parent PID of pid by reading /proc/<pid>/status.
// Returns 0 on error.
func ppidOf(pid int) int {
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
if err != nil {
return 0
}
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "PPid:") {
s := strings.TrimSpace(strings.TrimPrefix(line, "PPid:"))
n, err := strconv.Atoi(s)
if err != nil {
return 0
}
return n
}
}
return 0
}
// spec:26174-planctl-context-tokens/R6.3+D§3.3
// processAlive reports whether a process with pid exists.
func processAlive(pid int) bool {
if pid <= 0 {
return false
}
return syscall.Kill(pid, 0) == nil
}
// spec:26174-planctl-context-tokens/R6.3+D§3.3+D§4.1
// processStartTimeSec returns the process start time as raw clock ticks since boot,
// read from /proc/<pid>/stat field 22 (starttime). The hook writes the same units
// (clock ticks) on Linux per §5.5/§4.1, so comparison units match.
//
// The name retains "Sec" for API symmetry with the macOS implementation (which
// returns epoch seconds). Within a platform both sides match; cross-platform
// comparison never happens.
//
// /proc/<pid>/stat is a single line: "pid (comm) state ppid ... starttime ...".
// `comm` can contain spaces and parens. We find the LAST ')' and tokenize the
// remainder so comm noise can't shift field indices.
func processStartTimeSec(pid int) (int64, error) {
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil {
return 0, err
}
return parseStatStartTime(string(data))
}