fix: compare canonical active plan pointer roots
This commit is contained in:
committed by
Ahmad Othman Ammar Adi
parent
a82c5a4588
commit
10c64d464e
@@ -372,6 +372,41 @@ describe("init and status", () => {
|
||||
expect(buildContext(root, dir)).toContain("[planning-with-files] plan: ")
|
||||
})
|
||||
|
||||
it("updates a regular active plan pointer through a directory junction project root", () => {
|
||||
const linkedRoot = `${root}-junction`
|
||||
fs.symlinkSync(root, linkedRoot, "junction")
|
||||
try {
|
||||
const first = initPlan(linkedRoot, { name: "First" }, env)
|
||||
expect(first.ok).toBe(true)
|
||||
const second = initPlan(linkedRoot, { name: "Second" }, env)
|
||||
expect(second.ok).toBe(true)
|
||||
const pointer = path.join(root, ".planning", ".active_plan")
|
||||
expect(fs.lstatSync(pointer).isFile()).toBe(true)
|
||||
expect(fs.readFileSync(pointer, "utf8")).toBe(`${second.plan_id}\n`)
|
||||
} finally {
|
||||
fs.rmSync(linkedRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("refuses a linked planning directory without changing its external pointer", () => {
|
||||
const outside = `${root}-outside`
|
||||
const planning = path.join(root, ".planning")
|
||||
fs.mkdirSync(outside)
|
||||
const pointer = path.join(outside, ".active_plan")
|
||||
fs.writeFileSync(pointer, "external-plan\n")
|
||||
fs.symlinkSync(outside, planning, "junction")
|
||||
try {
|
||||
const result = initPlan(root, { name: "Blocked" }, env)
|
||||
expect(result.ok).toBe(false)
|
||||
expect(result.error).toContain("unsafe active plan pointer")
|
||||
expect(fs.readFileSync(pointer, "utf8")).toBe("external-plan\n")
|
||||
expect(fs.readdirSync(outside)).toEqual([".active_plan"])
|
||||
} finally {
|
||||
fs.rmSync(planning, { recursive: true, force: true })
|
||||
fs.rmSync(outside, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("uses a real skill template when one is discoverable, and rejects unknown modes", () => {
|
||||
const skill = path.join(root, ".agents", "skills", "planning-with-files", "templates")
|
||||
fs.mkdirSync(skill, { recursive: true })
|
||||
|
||||
@@ -88,10 +88,12 @@ function writeText(target: string, text: string): void {
|
||||
|
||||
function activePlanPointerError(planningRoot: string): string | null {
|
||||
const pointer = path.join(planningRoot, ".active_plan")
|
||||
const realRoot = realpathOrNull(planningRoot)
|
||||
if (!isRealDir(planningRoot) || !realRoot) return `refusing to replace unsafe active plan pointer: ${pointer}`
|
||||
if (!fs.existsSync(pointer)) return null
|
||||
const st = lstatSafe(pointer)
|
||||
const real = realpathOrNull(pointer)
|
||||
return !st || !st.isFile() || st.isSymbolicLink() || st.nlink > 1 || !real || !isInside(real, planningRoot)
|
||||
return !st || !st.isFile() || st.isSymbolicLink() || st.nlink > 1 || !real || !isInside(real, realRoot)
|
||||
? `refusing to replace unsafe active plan pointer: ${pointer}`
|
||||
: null
|
||||
}
|
||||
|
||||
@@ -372,6 +372,41 @@ describe("init and status", () => {
|
||||
expect(buildContext(root, dir)).toContain("[planning-with-files] plan: ")
|
||||
})
|
||||
|
||||
it("updates a regular active plan pointer through a directory junction project root", () => {
|
||||
const linkedRoot = `${root}-junction`
|
||||
fs.symlinkSync(root, linkedRoot, "junction")
|
||||
try {
|
||||
const first = initPlan(linkedRoot, { name: "First" }, env)
|
||||
expect(first.ok).toBe(true)
|
||||
const second = initPlan(linkedRoot, { name: "Second" }, env)
|
||||
expect(second.ok).toBe(true)
|
||||
const pointer = path.join(root, ".planning", ".active_plan")
|
||||
expect(fs.lstatSync(pointer).isFile()).toBe(true)
|
||||
expect(fs.readFileSync(pointer, "utf8")).toBe(`${second.plan_id}\n`)
|
||||
} finally {
|
||||
fs.rmSync(linkedRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("refuses a linked planning directory without changing its external pointer", () => {
|
||||
const outside = `${root}-outside`
|
||||
const planning = path.join(root, ".planning")
|
||||
fs.mkdirSync(outside)
|
||||
const pointer = path.join(outside, ".active_plan")
|
||||
fs.writeFileSync(pointer, "external-plan\n")
|
||||
fs.symlinkSync(outside, planning, "junction")
|
||||
try {
|
||||
const result = initPlan(root, { name: "Blocked" }, env)
|
||||
expect(result.ok).toBe(false)
|
||||
expect(result.error).toContain("unsafe active plan pointer")
|
||||
expect(fs.readFileSync(pointer, "utf8")).toBe("external-plan\n")
|
||||
expect(fs.readdirSync(outside)).toEqual([".active_plan"])
|
||||
} finally {
|
||||
fs.rmSync(planning, { recursive: true, force: true })
|
||||
fs.rmSync(outside, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("refuses a hard-linked active plan pointer without modifying its sibling", () => {
|
||||
const planning = path.join(root, ".planning")
|
||||
fs.mkdirSync(planning, { recursive: true })
|
||||
|
||||
@@ -88,10 +88,12 @@ function writeText(target: string, text: string): void {
|
||||
|
||||
function activePlanPointerError(planningRoot: string): string | null {
|
||||
const pointer = path.join(planningRoot, ".active_plan")
|
||||
const realRoot = realpathOrNull(planningRoot)
|
||||
if (!isRealDir(planningRoot) || !realRoot) return `refusing to replace unsafe active plan pointer: ${pointer}`
|
||||
if (!fs.existsSync(pointer)) return null
|
||||
const st = lstatSafe(pointer)
|
||||
const real = realpathOrNull(pointer)
|
||||
return !st || !st.isFile() || st.isSymbolicLink() || st.nlink > 1 || !real || !isInside(real, planningRoot)
|
||||
return !st || !st.isFile() || st.isSymbolicLink() || st.nlink > 1 || !real || !isInside(real, realRoot)
|
||||
? `refusing to replace unsafe active plan pointer: ${pointer}`
|
||||
: null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user