@cairnkeep/cli 2.9.0 → 2.12.0
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/CHANGELOG.md +66 -1
- package/README.md +83 -29
- package/bin/cairn +6 -165
- package/bin/cairn-container +8 -0
- package/docs/building-an-overlay.md +42 -5
- package/docs/containers.md +10 -0
- package/docs/harness-compatibility.md +64 -3
- package/docs/learning/CURRICULUM-MAP.md +13 -6
- package/docs/learning/FEATURE-GUIDE.md +14 -3
- package/docs/learning/README.md +5 -2
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +24 -8
- package/docs/learning/lessons/L03-first-project.md +34 -15
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +1 -1
- package/docs/learning/lessons/L22-native-windows.md +83 -0
- package/docs/learning/lessons/L23-guided-setup.md +142 -0
- package/docs/learning/tracks/operator.md +8 -3
- package/docs/learning/tracks/quickstart.md +9 -4
- package/docs/native-windows.md +136 -0
- package/docs/operating.md +142 -20
- package/docs/privacy-and-data-flow.md +41 -1
- package/docs/quickstart.md +86 -0
- package/docs/storage.md +14 -2
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/artifact-store-windows-worker.js +74 -0
- package/mcp-memory-server/dist/artifact-store.js +196 -55
- package/mcp-memory-server/dist/capability-cli.js +2 -1
- package/mcp-memory-server/dist/capability-config.js +4 -3
- package/mcp-memory-server/dist/capability-harness.js +34 -7
- package/mcp-memory-server/dist/context-pack.js +84 -25
- package/mcp-memory-server/dist/eval-cli.js +6 -5
- package/mcp-memory-server/dist/eval-plan.js +35 -13
- package/mcp-memory-server/dist/eval-process.js +11 -4
- package/mcp-memory-server/dist/eval-report.js +7 -5
- package/mcp-memory-server/dist/graph-cli.js +7 -1
- package/mcp-memory-server/dist/index.js +19 -20
- package/mcp-memory-server/dist/mcp-tool-profile.js +6 -4
- package/mcp-memory-server/dist/note-store.js +12 -3
- package/mcp-memory-server/dist/pi-mcp-bridge.js +185 -0
- package/mcp-memory-server/dist/platform-security.js +102 -0
- package/mcp-memory-server/dist/skill-store.js +27 -12
- package/mcp-memory-server/package.json +2 -1
- package/package.json +6 -4
- package/pi/extensions/cairnkeep-memory.ts +61 -0
- package/schemas/cairnkeep-setup-policy.schema.json +55 -0
- package/schemas/cairnkeep-setup.schema.json +36 -0
- package/scripts/bootstrap.sh +7 -15
- package/scripts/cairn-cli.mjs +119 -0
- package/scripts/cairn-container-cli.mjs +121 -0
- package/scripts/cairn-hook.mjs +199 -0
- package/scripts/completion.sh +17 -6
- package/scripts/doctor.sh +58 -0
- package/scripts/fix-permissions.mjs +12 -0
- package/scripts/harness-registry.mjs +126 -0
- package/scripts/memory-wiki-audit.mjs +95 -0
- package/scripts/phase26-test-manifest.mjs +26 -0
- package/scripts/run-repository-tests.mjs +76 -0
- package/scripts/setup-core.mjs +312 -0
- package/scripts/setup-reconcile.mjs +332 -0
- package/scripts/setup.mjs +342 -0
- package/scripts/sync-pi-assets.sh +19 -4
- package/scripts/test-bootstrap-untracked.sh +2 -1
- package/scripts/test-completion.sh +41 -0
- package/scripts/test-doctor.sh +66 -0
- package/scripts/test-harness-registry.sh +94 -0
- package/scripts/test-learning-docs.sh +52 -3
- package/scripts/test-package-install.sh +69 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +1 -1
- package/scripts/test-phase18-capability-lifecycle.sh +1 -1
- package/scripts/test-phase19-eval-lifecycle.sh +12 -2
- package/scripts/test-pi-lifecycle.mjs +190 -0
- package/scripts/test-setup-compatibility.mjs +187 -0
- package/scripts/test-setup-output.sh +131 -0
- package/scripts/test-setup-overlay.sh +113 -0
- package/scripts/test-setup-preflight.mjs +281 -0
- package/scripts/test-setup-reconcile.mjs +257 -0
- package/scripts/test-uninstall.sh +50 -0
- package/scripts/test-windows-native.mjs +159 -0
- package/scripts/uninstall.sh +73 -6
- package/scripts/verify-pi-mcp-bridge.mjs +588 -0
- package/scripts/windows-platform.mjs +733 -0
- package/templates/codex-config.toml.template +5 -0
- package/templates/start-codex.sh.template +30 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { gzipSync } from "node:zlib";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import {
|
|
10
|
+
bootstrapWindows,
|
|
11
|
+
createTar,
|
|
12
|
+
extractTar,
|
|
13
|
+
launcherModule,
|
|
14
|
+
launcherPowerShell,
|
|
15
|
+
powershellCompletion,
|
|
16
|
+
runWindowsCommand,
|
|
17
|
+
} from "./windows-platform.mjs";
|
|
18
|
+
import { runNativeContainer } from "./cairn-container-cli.mjs";
|
|
19
|
+
import { HARNESS_IDS } from "./harness-registry.mjs";
|
|
20
|
+
|
|
21
|
+
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
22
|
+
const sandbox = mkdtempSync(join(tmpdir(), "cairn-windows-native-"));
|
|
23
|
+
const originalBase = process.env.CAIRN_AGENTFS_BASE_DIR;
|
|
24
|
+
const originalHome = process.env.HOME;
|
|
25
|
+
const originalProfile = process.env.USERPROFILE;
|
|
26
|
+
try {
|
|
27
|
+
process.env.HOME = sandbox;
|
|
28
|
+
process.env.USERPROFILE = sandbox;
|
|
29
|
+
const project = join(sandbox, "Project with spaces – Unicode");
|
|
30
|
+
mkdirSync(project, { recursive: true });
|
|
31
|
+
bootstrapWindows(root, [project]);
|
|
32
|
+
for (const harness of HARNESS_IDS) {
|
|
33
|
+
assert.ok(existsSync(join(project, ".ai", `start-${harness}.cmd`)), `${harness} cmd launcher`);
|
|
34
|
+
}
|
|
35
|
+
assert.match(readFileSync(join(project, ".ai", "start-claude.cmd"), "utf8"), /start-harness\.ps1/);
|
|
36
|
+
assert.match(launcherModule(), /spawnSync\(command/);
|
|
37
|
+
assert.match(launcherPowerShell(), /pre-launch\.ps1/);
|
|
38
|
+
assert.match(launcherPowerShell(), /post-exit\.ps1/);
|
|
39
|
+
|
|
40
|
+
const claudeRoot = join(sandbox, "Claude Config");
|
|
41
|
+
await runWindowsCommand({ command: "sync", args: ["--apply", "--live-root", claudeRoot], root });
|
|
42
|
+
assert.ok(existsSync(join(claudeRoot, "commands", "remember.md")));
|
|
43
|
+
assert.ok(existsSync(join(claudeRoot, "hooks", "memory-wakeup.cmd")));
|
|
44
|
+
const settings = JSON.parse(readFileSync(join(claudeRoot, "settings.json"), "utf8"));
|
|
45
|
+
assert.ok(settings.hooks.SessionStart.some((entry) => entry.hooks.some((hook) => /memory-wakeup/.test(hook.command))));
|
|
46
|
+
await runWindowsCommand({ command: "sync", args: ["--check", "--live-root", claudeRoot], root });
|
|
47
|
+
|
|
48
|
+
const payloads = [["project.db", Buffer.from("sqlite-a")], ["team.db", Buffer.from("sqlite-b")]];
|
|
49
|
+
const tar = createTar(payloads);
|
|
50
|
+
assert.deepEqual(extractTar(tar), payloads);
|
|
51
|
+
assert.throws(() => extractTar(Buffer.from(tar).fill(0x2e, 0, 12)), /checksum|unsafe|unsupported/);
|
|
52
|
+
const archive = join(sandbox, "memory.tgz");
|
|
53
|
+
writeFileSync(archive, gzipSync(tar));
|
|
54
|
+
const memory = join(sandbox, "Memory Store");
|
|
55
|
+
process.env.CAIRN_AGENTFS_BASE_DIR = memory;
|
|
56
|
+
await runWindowsCommand({ command: "memory", args: ["import", archive], root });
|
|
57
|
+
assert.equal(readFileSync(join(memory, "project.db"), "utf8"), "sqlite-a");
|
|
58
|
+
assert.match(powershellCompletion(), /Register-ArgumentCompleter/);
|
|
59
|
+
|
|
60
|
+
let containerArgs = [];
|
|
61
|
+
runNativeContainer(["stdio", "--image", "example/windows:1", "--volume", "windows-data"], root, (_engine, args) => { containerArgs = args; });
|
|
62
|
+
assert.ok(containerArgs.includes("--read-only"));
|
|
63
|
+
assert.ok(containerArgs.includes("windows-data:/data:Z,U"));
|
|
64
|
+
assert.ok(containerArgs.includes("example/windows:1"));
|
|
65
|
+
|
|
66
|
+
await runWindowsCommand({ command: "uninstall", args: ["--yes", "--live-root", claudeRoot, project], root });
|
|
67
|
+
assert.ok(!existsSync(join(project, ".ai", "start-claude.cmd")));
|
|
68
|
+
assert.ok(!existsSync(join(claudeRoot, "commands", "remember.md")));
|
|
69
|
+
const cleanedSettings = JSON.parse(readFileSync(join(claudeRoot, "settings.json"), "utf8"));
|
|
70
|
+
assert.ok(!JSON.stringify(cleanedSettings).includes("memory-wakeup"));
|
|
71
|
+
|
|
72
|
+
const setupSurfaceComplete = [
|
|
73
|
+
join(root, "scripts", "setup.mjs"),
|
|
74
|
+
join(root, "scripts", "setup-core.mjs"),
|
|
75
|
+
join(root, "scripts", "setup-reconcile.mjs"),
|
|
76
|
+
join(root, "schemas", "cairnkeep-setup.schema.json"),
|
|
77
|
+
join(root, "schemas", "cairnkeep-setup-policy.schema.json"),
|
|
78
|
+
].every((candidate) => existsSync(candidate))
|
|
79
|
+
&& /case\s+["']setup["']/.test(readFileSync(join(root, "scripts", "windows-platform.mjs"), "utf8"))
|
|
80
|
+
&& /["']setup["']/.test(powershellCompletion());
|
|
81
|
+
|
|
82
|
+
if (!setupSurfaceComplete) {
|
|
83
|
+
if (process.env.CAIRN_PHASE26_RED === "1") {
|
|
84
|
+
console.log("PHASE26_RED:WINDOWS_SETUP_MISSING");
|
|
85
|
+
process.exitCode = 86;
|
|
86
|
+
} else {
|
|
87
|
+
console.log("SKIP: simulated Windows guided setup surface is not complete");
|
|
88
|
+
console.log("PASS: native Windows CLI lifecycle, paths, archive safety, hooks, and PowerShell completion");
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
const setupProject = join(sandbox, "Guided Project with spaces – Unicode");
|
|
92
|
+
let output = "";
|
|
93
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
94
|
+
process.stdout.write = ((chunk, ...args) => {
|
|
95
|
+
output += String(chunk);
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const handled = await runWindowsCommand({
|
|
100
|
+
command: "setup",
|
|
101
|
+
args: [setupProject, "--git", "none", "--harness", "claude,pi", "--memory", "local", "--yes", "--json"],
|
|
102
|
+
root,
|
|
103
|
+
});
|
|
104
|
+
assert.equal(handled, true);
|
|
105
|
+
} finally {
|
|
106
|
+
process.stdout.write = originalWrite;
|
|
107
|
+
}
|
|
108
|
+
const setupResult = JSON.parse(output);
|
|
109
|
+
assert.equal(setupResult.schema_version, 1);
|
|
110
|
+
assert.equal(setupResult.status, "limited");
|
|
111
|
+
assert.equal(setupResult.git, "none");
|
|
112
|
+
assert.equal(setupResult.memory, "local");
|
|
113
|
+
assert.deepEqual(setupResult.harnesses, ["claude", "pi"]);
|
|
114
|
+
assert.equal(Array.isArray(setupResult.recovery), true);
|
|
115
|
+
assert.equal(setupResult.recovery.some((line) => /cairn (?:setup|sync)/.test(line)), true);
|
|
116
|
+
|
|
117
|
+
const setupStatePath = join(setupProject, ".ai", "cairnkeep.json");
|
|
118
|
+
assert.ok(existsSync(setupStatePath));
|
|
119
|
+
const setupState = JSON.parse(readFileSync(setupStatePath, "utf8"));
|
|
120
|
+
const serializedState = JSON.stringify(setupState);
|
|
121
|
+
assert.equal(serializedState.includes(setupProject), false);
|
|
122
|
+
assert.doesNotMatch(serializedState, /(?:token|secret|credential|endpoint|username|environment|https?:)/i);
|
|
123
|
+
assert.match(powershellCompletion(), /--git/);
|
|
124
|
+
assert.match(powershellCompletion(), /--harness/);
|
|
125
|
+
assert.match(powershellCompletion(), /--memory/);
|
|
126
|
+
assert.match(powershellCompletion(), /--policy/);
|
|
127
|
+
assert.match(powershellCompletion(), /--yes/);
|
|
128
|
+
assert.match(powershellCompletion(), /--json/);
|
|
129
|
+
assert.match(powershellCompletion(), /codex/);
|
|
130
|
+
|
|
131
|
+
const codexProject = join(sandbox, "Guided Codex Project");
|
|
132
|
+
let codexOutput = "";
|
|
133
|
+
process.stdout.write = ((chunk) => {
|
|
134
|
+
codexOutput += String(chunk);
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
await runWindowsCommand({
|
|
139
|
+
command: "setup",
|
|
140
|
+
args: [codexProject, "--git", "none", "--harness", "codex", "--memory", "local", "--yes", "--json"],
|
|
141
|
+
root,
|
|
142
|
+
});
|
|
143
|
+
} finally {
|
|
144
|
+
process.stdout.write = originalWrite;
|
|
145
|
+
}
|
|
146
|
+
const codexResult = JSON.parse(codexOutput);
|
|
147
|
+
assert.deepEqual(codexResult.harnesses, ["codex"]);
|
|
148
|
+
assert.equal(codexResult.machine_sync.command, null);
|
|
149
|
+
assert.ok(existsSync(join(codexProject, ".ai", "start-codex.cmd")));
|
|
150
|
+
assert.match(readFileSync(join(codexProject, ".codex", "config.toml"), "utf8"), /mcp_servers\.cairn-memory/);
|
|
151
|
+
console.log("PASS: simulated Windows setup parity, private state, recovery, and completion contract");
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
if (originalBase === undefined) delete process.env.CAIRN_AGENTFS_BASE_DIR;
|
|
155
|
+
else process.env.CAIRN_AGENTFS_BASE_DIR = originalBase;
|
|
156
|
+
if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome;
|
|
157
|
+
if (originalProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = originalProfile;
|
|
158
|
+
rmSync(sandbox, { recursive: true, force: true });
|
|
159
|
+
}
|
package/scripts/uninstall.sh
CHANGED
|
@@ -45,6 +45,7 @@ PROJECT_AI_FILES=(
|
|
|
45
45
|
start-pi.sh
|
|
46
46
|
start-kimi.sh
|
|
47
47
|
start-qwen.sh
|
|
48
|
+
start-codex.sh
|
|
48
49
|
env.example
|
|
49
50
|
trajectory-redaction.json
|
|
50
51
|
capabilities.json
|
|
@@ -125,6 +126,51 @@ remove_path() {
|
|
|
125
126
|
echo " removed: $abs"
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
# Print only package-known relative paths from a valid guided-setup ownership
|
|
130
|
+
# record. The record identifies candidates, but never grants authority to an
|
|
131
|
+
# arbitrary path. Modified candidates are still backed up before removal.
|
|
132
|
+
setup_owned_paths() {
|
|
133
|
+
node --input-type=module - "$1" "$CAIRN_ROOT/scripts/harness-registry.mjs" <<'NODE'
|
|
134
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
135
|
+
import { pathToFileURL } from "node:url";
|
|
136
|
+
const path = process.argv[2];
|
|
137
|
+
const registry = await import(pathToFileURL(process.argv[3]).href);
|
|
138
|
+
const info = lstatSync(path);
|
|
139
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) process.exit(1);
|
|
140
|
+
if (process.platform !== "win32" && (info.mode & 0o777) !== 0o600) process.exit(1);
|
|
141
|
+
const state = JSON.parse(readFileSync(path, "utf8"));
|
|
142
|
+
const exact = (value, keys) => value && typeof value === "object" && !Array.isArray(value)
|
|
143
|
+
&& Object.keys(value).sort().join(",") === [...keys].sort().join(",");
|
|
144
|
+
if (!exact(state, ["schema_version", "cairnkeep_version", "git", "memory", "harnesses", "assets"])
|
|
145
|
+
|| state.schema_version !== 1
|
|
146
|
+
|| !["init", "existing", "none"].includes(state.git)
|
|
147
|
+
|| !["local", "none"].includes(state.memory)
|
|
148
|
+
|| !Array.isArray(state.harnesses)
|
|
149
|
+
|| !state.assets
|
|
150
|
+
|| typeof state.assets !== "object"
|
|
151
|
+
|| Array.isArray(state.assets)) process.exit(1);
|
|
152
|
+
const allowed = new Set([
|
|
153
|
+
".ai/env.example", ".ai/trajectory-redaction.json", ".ai/capabilities.json",
|
|
154
|
+
".agentfs/.gitignore", ".planning/config.json", ".planning/PROJECT-BRIEF.md",
|
|
155
|
+
".planning/wiki/index.md", ".planning/wiki/policy.md", ".planning/wiki/CONTRADICTIONS.md",
|
|
156
|
+
".planning/wiki/LOG.md", ".planning/alignment/policy.md", ".planning/alignment/gap-register.yaml",
|
|
157
|
+
".planning/graphs/policy.md", ".planning/graphs/.gitignore", ".planning/security/policy.md",
|
|
158
|
+
".ai/start-harness.mjs", ".ai/start-harness.ps1",
|
|
159
|
+
...registry.HARNESS_IDS.flatMap((name) => [`.ai/start-${name}.sh`, `.ai/start-${name}.cmd`]),
|
|
160
|
+
...registry.HARNESS_IDS.flatMap((name) => registry.harnessProjectAssets(name, "local").map(({ path }) => path)),
|
|
161
|
+
]);
|
|
162
|
+
for (const [asset, record] of Object.entries(state.assets)) {
|
|
163
|
+
if (!allowed.has(asset)
|
|
164
|
+
|| !exact(record, ["digest", "mode", "template"])
|
|
165
|
+
|| !/^[a-f0-9]{64}$/.test(record.digest)
|
|
166
|
+
|| !Number.isInteger(record.mode)
|
|
167
|
+
|| record.mode < 0 || record.mode > 0o777
|
|
168
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(record.template)) process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
process.stdout.write(`${Object.keys(state.assets).sort().join("\n")}\n`);
|
|
171
|
+
NODE
|
|
172
|
+
}
|
|
173
|
+
|
|
128
174
|
note() { [[ $DRY_RUN -eq 1 ]] || { ensure_bundle; printf '%s\n' "$1" >>"$NOTES"; }; }
|
|
129
175
|
|
|
130
176
|
# ---- what will be removed ----------------------------------------------------
|
|
@@ -164,6 +210,7 @@ while IFS= read -r dst; do remove_path "$dst"; done < <(asset_dests)
|
|
|
164
210
|
# These are precisely owned adapter paths; do not touch neighboring harness
|
|
165
211
|
# configuration in either user root.
|
|
166
212
|
echo "Pi adapters:"
|
|
213
|
+
remove_path "$PI_LIVE_ROOT/extensions/cairnkeep-memory.ts"
|
|
167
214
|
remove_path "$PI_LIVE_ROOT/extensions/cairnkeep-trajectory.ts"
|
|
168
215
|
remove_path "$PI_LIVE_ROOT/prompts/graphify.md"
|
|
169
216
|
echo "Kimi adapter:"
|
|
@@ -268,13 +315,33 @@ for proj in "${PROJECTS[@]:-}"; do
|
|
|
268
315
|
# hook registrations and OpenCode plugin bytes restore together exactly.
|
|
269
316
|
# Normal and master-off installations never create it, so absence is inert.
|
|
270
317
|
remove_path "$proj/.ai/capability-contract"
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
318
|
+
setup_state="$proj/.ai/cairnkeep.json"
|
|
319
|
+
if [[ -e "$setup_state" ]]; then
|
|
320
|
+
if owned_paths=$(setup_owned_paths "$setup_state" 2>/dev/null); then
|
|
321
|
+
while IFS= read -r rel; do
|
|
322
|
+
[[ -n "$rel" ]] && remove_path "$proj/$rel"
|
|
323
|
+
done <<< "$owned_paths"
|
|
324
|
+
remove_path "$setup_state"
|
|
325
|
+
if [[ $DRY_RUN -eq 0 ]]; then
|
|
326
|
+
for managed_dir in \
|
|
327
|
+
"$proj/.planning/wiki" "$proj/.planning/alignment" "$proj/.planning/graphs" \
|
|
328
|
+
"$proj/.planning/security" "$proj/.planning" "$proj/.codex" "$proj/.ai" "$proj/.agentfs"; do
|
|
329
|
+
rmdir "$managed_dir" 2>/dev/null || true
|
|
330
|
+
done
|
|
331
|
+
fi
|
|
332
|
+
else
|
|
333
|
+
echo " kept guided setup scaffold: ownership state is invalid or unsafe"
|
|
334
|
+
fi
|
|
335
|
+
else
|
|
336
|
+
# Compatibility path for projects created by the legacy bootstrap command.
|
|
337
|
+
for rel in "${PROJECT_AI_FILES[@]}"; do
|
|
338
|
+
remove_path "$proj/.ai/$rel"
|
|
339
|
+
done
|
|
340
|
+
if [[ $DRY_RUN -eq 0 && -d "$proj/.ai" ]]; then
|
|
341
|
+
rmdir "$proj/.ai" 2>/dev/null || true
|
|
342
|
+
fi
|
|
343
|
+
remove_path "$proj/.planning"
|
|
276
344
|
fi
|
|
277
|
-
remove_path "$proj/.planning"
|
|
278
345
|
if [[ $PURGE_MEMORY -eq 1 ]]; then
|
|
279
346
|
remove_path "$proj/.agentfs"
|
|
280
347
|
elif [[ -d "$proj/.agentfs" ]]; then
|