portable-agent-layer 0.35.0 → 0.36.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/README.md +1 -1
- package/assets/skills/projects/SKILL.md +0 -1
- package/assets/skills/telos/SKILL.md +7 -52
- package/assets/templates/PAL/ALGORITHM.md +28 -3
- package/assets/templates/PAL/PROJECT_LIFECYCLE.md +48 -0
- package/assets/templates/PAL/README.md +1 -1
- package/assets/templates/PAL/STEERING_RULES.md +4 -0
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +32 -17
- package/assets/templates/PAL/WORK_TRACKING.md +1 -1
- package/assets/templates/pal-settings.json +1 -3
- package/assets/templates/settings.claude.json +2 -1
- package/package.json +1 -1
- package/src/cli/setup-telos.ts +12 -79
- package/src/hooks/LoadContext.ts +22 -10
- package/src/hooks/handlers/context-digests.ts +74 -0
- package/src/hooks/handlers/session-intelligence.ts +9 -86
- package/src/hooks/lib/claude-md.ts +69 -14
- package/src/hooks/lib/context.ts +57 -139
- package/src/hooks/lib/relationship.ts +3 -3
- package/src/hooks/lib/security.ts +2 -0
- package/src/hooks/lib/semi-static.ts +186 -0
- package/src/hooks/lib/setup.ts +0 -5
- package/src/hooks/lib/stop.ts +3 -0
- package/src/targets/claude/uninstall.ts +1 -1
- package/src/targets/copilot/install.ts +39 -8
- package/src/targets/copilot/uninstall.ts +58 -17
- package/src/targets/cursor/install.ts +8 -0
- package/src/targets/cursor/uninstall.ts +18 -1
- package/src/targets/lib.ts +26 -0
- package/src/targets/opencode/install.ts +29 -1
- package/src/targets/opencode/plugin.ts +1 -1
- package/src/targets/opencode/uninstall.ts +30 -3
- package/src/tools/agent/handoff-note.ts +116 -0
- package/src/tools/agent/relationship-note.ts +51 -0
- package/src/tools/relationship-reflect.ts +2 -2
- package/src/tools/self-model.ts +4 -4
- package/assets/templates/telos/PROJECTS.md +0 -7
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semi-static context source registry.
|
|
3
|
+
*
|
|
4
|
+
* One entry here = the only change needed to add a new source across all consumers:
|
|
5
|
+
* CLAUDE.md @imports, opencode instructions[], Cursor .mdc, Copilot .instructions.md,
|
|
6
|
+
* and the session-stop digest writer.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { parse } from "./frontmatter";
|
|
12
|
+
import { readFailures } from "./learning-store";
|
|
13
|
+
import { loadOpinionContext } from "./opinions";
|
|
14
|
+
import { palHome, paths } from "./paths";
|
|
15
|
+
import { readFramePrinciples } from "./wisdom";
|
|
16
|
+
|
|
17
|
+
/** A single semi-static context source — built at session stop, loaded natively at session start. */
|
|
18
|
+
export interface SemiStaticSource {
|
|
19
|
+
/** Absolute path used in @imports (CLAUDE.md), instructions[] (opencode), and digest writes. */
|
|
20
|
+
readonly path: string;
|
|
21
|
+
/** When true, session-stop handler writes build result to path. */
|
|
22
|
+
readonly writesDigest: boolean;
|
|
23
|
+
/** Returns current content — builds fresh when writesDigest is true, reads the file otherwise. */
|
|
24
|
+
load(): string;
|
|
25
|
+
/** Slug for ~/.cursor/rules/pal-${slug}.mdc and ~/.copilot/instructions/pal-${slug}.instructions.md */
|
|
26
|
+
readonly slug: string;
|
|
27
|
+
/** Human-readable description for Cursor .mdc frontmatter. */
|
|
28
|
+
readonly description: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns the Cursor rules filename for a source. */
|
|
32
|
+
export function cursorFilename(src: SemiStaticSource): string {
|
|
33
|
+
return `pal-${src.slug}.mdc`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Returns the Copilot instructions filename for a source. */
|
|
37
|
+
export function copilotFilename(src: SemiStaticSource): string {
|
|
38
|
+
return `pal-${src.slug}.instructions.md`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readFileSafe(path: string): string {
|
|
42
|
+
try {
|
|
43
|
+
if (!existsSync(path)) return "";
|
|
44
|
+
return readFileSync(path, "utf-8").trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Build recommendations from the most recent synthesis report. */
|
|
51
|
+
export function loadSynthesisRecommendations(): string {
|
|
52
|
+
try {
|
|
53
|
+
const synthDir = paths.synthesis();
|
|
54
|
+
if (!existsSync(synthDir)) return "";
|
|
55
|
+
|
|
56
|
+
const months = readdirSync(synthDir).sort().reverse();
|
|
57
|
+
for (const month of months) {
|
|
58
|
+
const monthDir = resolve(synthDir, month);
|
|
59
|
+
try {
|
|
60
|
+
const files = readdirSync(monthDir)
|
|
61
|
+
.filter((f) => f.endsWith(".md"))
|
|
62
|
+
.sort()
|
|
63
|
+
.reverse();
|
|
64
|
+
if (files.length === 0) continue;
|
|
65
|
+
|
|
66
|
+
const content = readFileSync(resolve(monthDir, files[0]), "utf-8");
|
|
67
|
+
|
|
68
|
+
const recMatch = content.match(/## Recommendations\n\n([\s\S]*?)(?:\n##|\n$|$)/);
|
|
69
|
+
if (!recMatch?.[1]?.trim()) continue;
|
|
70
|
+
|
|
71
|
+
const recs = recMatch[1]
|
|
72
|
+
.trim()
|
|
73
|
+
.split("\n")
|
|
74
|
+
.filter((l) => l.trim())
|
|
75
|
+
.slice(0, 4);
|
|
76
|
+
|
|
77
|
+
if (recs.length === 0) continue;
|
|
78
|
+
|
|
79
|
+
const { meta } = parse<{ period?: string; average_rating?: string }>(content);
|
|
80
|
+
const period = meta.period ?? "";
|
|
81
|
+
const avgRating = meta.average_rating ? `${meta.average_rating}/10` : "";
|
|
82
|
+
|
|
83
|
+
const header = [
|
|
84
|
+
"## Pattern Synthesis",
|
|
85
|
+
period ? `*${period} — ${avgRating}*` : "",
|
|
86
|
+
]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join("\n");
|
|
89
|
+
|
|
90
|
+
return [header, ...recs].join("\n");
|
|
91
|
+
} catch {
|
|
92
|
+
/* try next month */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return "";
|
|
96
|
+
} catch {
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Build the 5 most recent failure lessons as an avoid-list. */
|
|
102
|
+
export function loadFailurePatterns(): string {
|
|
103
|
+
try {
|
|
104
|
+
const entries = readFailures(paths.failures(), 5);
|
|
105
|
+
if (entries.length === 0) return "";
|
|
106
|
+
|
|
107
|
+
const lines = entries.map((e) => {
|
|
108
|
+
const label = e.rating ? `[${e.rating}/10]` : "";
|
|
109
|
+
const text = e.principle || e.context;
|
|
110
|
+
return `- ${label} ${text}`.trim();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return ["## Lessons from Recent Failures — Apply These Now", ...lines].join("\n");
|
|
114
|
+
} catch {
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* All semi-static context sources in load order.
|
|
121
|
+
* Adding one entry here is the only change needed to extend coverage to all consumers.
|
|
122
|
+
*/
|
|
123
|
+
export function getSemiStaticSources(): SemiStaticSource[] {
|
|
124
|
+
const memory = paths.memory();
|
|
125
|
+
const home = palHome();
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
path: resolve(memory, "self-model", "current.md"),
|
|
129
|
+
writesDigest: false,
|
|
130
|
+
load: () => readFileSafe(resolve(memory, "self-model", "current.md")),
|
|
131
|
+
slug: "self-model",
|
|
132
|
+
description: "PAL self-model",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
path: resolve(memory, "wisdom", "context.md"),
|
|
136
|
+
writesDigest: true,
|
|
137
|
+
load: () => {
|
|
138
|
+
try {
|
|
139
|
+
const principles = readFramePrinciples();
|
|
140
|
+
if (principles.length === 0) return "";
|
|
141
|
+
return ["## Crystallized Principles", ...principles.map((p) => `- ${p}`)].join(
|
|
142
|
+
"\n"
|
|
143
|
+
);
|
|
144
|
+
} catch {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
slug: "wisdom",
|
|
149
|
+
description: "PAL wisdom",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
path: resolve(memory, "relationship", "opinions-context.md"),
|
|
153
|
+
writesDigest: true,
|
|
154
|
+
load: () => {
|
|
155
|
+
try {
|
|
156
|
+
return loadOpinionContext();
|
|
157
|
+
} catch {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
slug: "opinions",
|
|
162
|
+
description: "PAL opinions",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
path: resolve(memory, "learning", "synthesis-digest.md"),
|
|
166
|
+
writesDigest: true,
|
|
167
|
+
load: loadSynthesisRecommendations,
|
|
168
|
+
slug: "synthesis",
|
|
169
|
+
description: "PAL pattern synthesis",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: resolve(memory, "learning", "failures-digest.md"),
|
|
173
|
+
writesDigest: true,
|
|
174
|
+
load: loadFailurePatterns,
|
|
175
|
+
slug: "failures",
|
|
176
|
+
description: "PAL recent failure lessons",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: resolve(home, "docs", "STEERING_RULES.md"),
|
|
180
|
+
writesDigest: false,
|
|
181
|
+
load: () => readFileSafe(resolve(home, "docs", "STEERING_RULES.md")),
|
|
182
|
+
slug: "steering",
|
|
183
|
+
description: "PAL steering rules",
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
}
|
package/src/hooks/lib/setup.ts
CHANGED
|
@@ -36,11 +36,6 @@ export const SETUP_STEPS: Record<string, Omit<SetupStep, "done">> = {
|
|
|
36
36
|
"What are your current goals? (short-term, medium-term, long-term) (~/.pal/telos/GOALS.md)",
|
|
37
37
|
hint: "e.g. Ship v2 by Q3, learn Rust, get promoted to staff engineer",
|
|
38
38
|
},
|
|
39
|
-
projects: {
|
|
40
|
-
file: "telos/PROJECTS.md",
|
|
41
|
-
question: "What projects are you currently working on? (~/.pal/telos/PROJECTS.md)",
|
|
42
|
-
hint: "e.g. PAL (active, high priority), personal blog (paused), side SaaS (early stage)",
|
|
43
|
-
},
|
|
44
39
|
beliefs: {
|
|
45
40
|
file: "telos/BELIEFS.md",
|
|
46
41
|
question: "What principles or values guide your work? (~/.pal/telos/BELIEFS.md)",
|
package/src/hooks/lib/stop.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { autoGraduate } from "../handlers/auto-graduate";
|
|
9
9
|
import { autoBackup } from "../handlers/backup";
|
|
10
|
+
import { writeContextDigests } from "../handlers/context-digests";
|
|
10
11
|
import { notifyDesktop } from "../handlers/desktop-notify";
|
|
11
12
|
import { captureFailure } from "../handlers/failure";
|
|
12
13
|
import { projectTouch } from "../handlers/project-touch";
|
|
@@ -56,6 +57,7 @@ export async function runStopHandlers(
|
|
|
56
57
|
autoGraduate(),
|
|
57
58
|
projectTouch(options.lastAssistantMessage),
|
|
58
59
|
notifyDesktop(options.sessionId),
|
|
60
|
+
Promise.resolve(writeContextDigests()),
|
|
59
61
|
]);
|
|
60
62
|
|
|
61
63
|
const handlerNames = [
|
|
@@ -71,6 +73,7 @@ export async function runStopHandlers(
|
|
|
71
73
|
"auto-graduate",
|
|
72
74
|
"project-touch",
|
|
73
75
|
"desktop-notify",
|
|
76
|
+
"context-digests",
|
|
74
77
|
];
|
|
75
78
|
for (let i = 0; i < results.length; i++) {
|
|
76
79
|
const r = results[i];
|
|
@@ -57,7 +57,7 @@ if (removedAgents.length > 0) {
|
|
|
57
57
|
// --- Remove PAL system docs ---
|
|
58
58
|
removePalDocs();
|
|
59
59
|
|
|
60
|
-
// --- Remove AGENTS.md and CLAUDE.md
|
|
60
|
+
// --- Remove AGENTS.md and CLAUDE.md ---
|
|
61
61
|
const agentsMd = resolve(platform.opencodeDir(), "AGENTS.md");
|
|
62
62
|
const claudeMd = resolve(CLAUDE_DIR, "CLAUDE.md");
|
|
63
63
|
try {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PAL — Copilot target installer
|
|
3
3
|
* Writes hooks to ~/.copilot/hooks/pal-hooks.json.
|
|
4
|
-
* Copies skills and agents.
|
|
4
|
+
* Copies skills and agents. Writes ~/.copilot/instructions/pal-*.instructions.md.
|
|
5
|
+
* Enables ~/.copilot/instructions in VS Code chat.instructionsFilesLocations.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { resolve } from "node:path";
|
|
10
|
+
import { writeContextDigests } from "../../hooks/handlers/context-digests";
|
|
9
11
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
10
12
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
11
13
|
import {
|
|
@@ -16,7 +18,10 @@ import {
|
|
|
16
18
|
generateSkillIndex,
|
|
17
19
|
loadCopilotHooksTemplate,
|
|
18
20
|
log,
|
|
21
|
+
readJson,
|
|
19
22
|
scaffoldPalSettings,
|
|
23
|
+
vscodeSettingsFile,
|
|
24
|
+
writeJson,
|
|
20
25
|
} from "../lib";
|
|
21
26
|
|
|
22
27
|
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
@@ -50,16 +55,42 @@ log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
|
50
55
|
// --- Scaffold PAL settings ---
|
|
51
56
|
scaffoldPalSettings();
|
|
52
57
|
|
|
53
|
-
// --- Generate AGENTS.md
|
|
54
|
-
// ensureSymlinks() inside regenerateIfNeeded() handles the symlink once ~/.copilot/ exists
|
|
58
|
+
// --- Generate AGENTS.md ---
|
|
55
59
|
regenerateIfNeeded();
|
|
56
|
-
|
|
60
|
+
log.success("Generated AGENTS.md");
|
|
61
|
+
|
|
62
|
+
// --- Write ~/.copilot/instructions/pal-*.instructions.md ---
|
|
63
|
+
mkdirSync(resolve(COPILOT_DIR, "instructions"), { recursive: true });
|
|
64
|
+
writeContextDigests();
|
|
57
65
|
log.success(
|
|
58
|
-
|
|
59
|
-
? "copilot-instructions.md symlink present"
|
|
60
|
-
: "Generated AGENTS.md (copilot-instructions.md symlink will be created on next session)"
|
|
66
|
+
"Written ~/.copilot/instructions/pal-self-model + pal-wisdom + pal-opinions.instructions.md"
|
|
61
67
|
);
|
|
62
68
|
|
|
69
|
+
// --- Enable ~/.copilot/instructions in VS Code settings ---
|
|
70
|
+
const vsSettingsPath = vscodeSettingsFile();
|
|
71
|
+
const manualHint =
|
|
72
|
+
'Add manually: { "chat.instructionsFilesLocations": { "~/.copilot/instructions": true } }';
|
|
73
|
+
if (vsSettingsPath) {
|
|
74
|
+
try {
|
|
75
|
+
const settings = readJson<Record<string, unknown>>(vsSettingsPath, {});
|
|
76
|
+
const existing =
|
|
77
|
+
typeof settings["chat.instructionsFilesLocations"] === "object" &&
|
|
78
|
+
settings["chat.instructionsFilesLocations"] !== null
|
|
79
|
+
? (settings["chat.instructionsFilesLocations"] as Record<string, unknown>)
|
|
80
|
+
: {};
|
|
81
|
+
settings["chat.instructionsFilesLocations"] = {
|
|
82
|
+
...existing,
|
|
83
|
+
"~/.copilot/instructions": true,
|
|
84
|
+
};
|
|
85
|
+
writeJson(vsSettingsPath, settings);
|
|
86
|
+
log.success("Enabled ~/.copilot/instructions in VS Code settings");
|
|
87
|
+
} catch {
|
|
88
|
+
log.warn(`Could not update VS Code settings — ${manualHint}`);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
log.warn(`Could not detect VS Code settings path — ${manualHint}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
log.success("Copilot installation complete");
|
|
64
95
|
console.log("");
|
|
65
96
|
log.info(`Skills: ${countSkills()}`);
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PAL — Copilot uninstaller
|
|
3
|
-
* Removes pal-hooks.json, skill symlinks, agents,
|
|
3
|
+
* Removes pal-hooks.json, skill symlinks, agents, instruction files,
|
|
4
|
+
* and the VS Code chat.instructionsFilesLocations entry.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { copyFileSync, existsSync, lstatSync, readlinkSync, unlinkSync } from "node:fs";
|
|
7
8
|
import { resolve } from "node:path";
|
|
8
9
|
import { platform } from "../../hooks/lib/paths";
|
|
9
|
-
import {
|
|
10
|
+
import { copilotFilename, getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
11
|
+
import {
|
|
12
|
+
log,
|
|
13
|
+
readJson,
|
|
14
|
+
removeAgentsFromCopilot,
|
|
15
|
+
removePalDocs,
|
|
16
|
+
removeSkills,
|
|
17
|
+
vscodeSettingsFile,
|
|
18
|
+
writeJson,
|
|
19
|
+
} from "../lib";
|
|
10
20
|
|
|
11
21
|
const COPILOT_DIR = platform.copilotDir();
|
|
12
22
|
const HOOKS_FILE = resolve(COPILOT_DIR, "hooks", "pal-hooks.json");
|
|
@@ -35,26 +45,57 @@ if (removedAgents.length > 0) {
|
|
|
35
45
|
log.success(`Removed ${removedAgents.length} agent(s): ${removedAgents.join(", ")}`);
|
|
36
46
|
}
|
|
37
47
|
|
|
38
|
-
// --- Remove
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
// --- Remove PAL docs ---
|
|
49
|
+
removePalDocs();
|
|
50
|
+
|
|
51
|
+
// --- Remove ~/.copilot/instructions/pal-*.instructions.md ---
|
|
52
|
+
for (const src of getSemiStaticSources()) {
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(resolve(COPILOT_DIR, "instructions", copilotFilename(src)));
|
|
55
|
+
} catch {
|
|
56
|
+
/* gone */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// pal-session.instructions.md is written by LoadContext (not a semi-static source)
|
|
60
|
+
try {
|
|
61
|
+
unlinkSync(resolve(COPILOT_DIR, "instructions", "pal-session.instructions.md"));
|
|
62
|
+
} catch {
|
|
63
|
+
/* gone */
|
|
64
|
+
}
|
|
65
|
+
log.success("Removed ~/.copilot/instructions/pal-*.instructions.md");
|
|
66
|
+
|
|
67
|
+
// --- Backward compat: remove old copilot-instructions.md symlink if present ---
|
|
68
|
+
const legacyPath = resolve(COPILOT_DIR, "copilot-instructions.md");
|
|
69
|
+
if (existsSync(legacyPath)) {
|
|
41
70
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
log.info("copilot-instructions.md is not a PAL symlink — leaving it");
|
|
50
|
-
}
|
|
71
|
+
if (
|
|
72
|
+
lstatSync(legacyPath).isSymbolicLink() &&
|
|
73
|
+
readlinkSync(legacyPath).includes("AGENTS.md")
|
|
74
|
+
) {
|
|
75
|
+
unlinkSync(legacyPath);
|
|
76
|
+
log.success("Removed legacy copilot-instructions.md symlink");
|
|
51
77
|
}
|
|
52
78
|
} catch {
|
|
53
|
-
|
|
79
|
+
/* ignore */
|
|
54
80
|
}
|
|
55
81
|
}
|
|
56
82
|
|
|
57
|
-
// --- Remove
|
|
58
|
-
|
|
83
|
+
// --- Remove ~/.copilot/instructions entry from VS Code settings ---
|
|
84
|
+
const vsSettingsPath = vscodeSettingsFile();
|
|
85
|
+
if (vsSettingsPath && existsSync(vsSettingsPath)) {
|
|
86
|
+
const settings = readJson<Record<string, unknown>>(vsSettingsPath, {});
|
|
87
|
+
const locs = settings["chat.instructionsFilesLocations"];
|
|
88
|
+
if (typeof locs === "object" && locs !== null) {
|
|
89
|
+
const obj = locs as Record<string, unknown>;
|
|
90
|
+
delete obj["~/.copilot/instructions"];
|
|
91
|
+
if (Object.keys(obj).length === 0) {
|
|
92
|
+
delete settings["chat.instructionsFilesLocations"];
|
|
93
|
+
} else {
|
|
94
|
+
settings["chat.instructionsFilesLocations"] = obj;
|
|
95
|
+
}
|
|
96
|
+
writeJson(vsSettingsPath, settings);
|
|
97
|
+
log.success("Removed ~/.copilot/instructions from VS Code settings");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
59
100
|
|
|
60
101
|
log.success("Copilot uninstall complete");
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
+
import { writeContextDigests } from "../../hooks/handlers/context-digests";
|
|
9
10
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
10
11
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
11
12
|
import {
|
|
@@ -64,6 +65,13 @@ scaffoldPalSettings();
|
|
|
64
65
|
regenerateIfNeeded();
|
|
65
66
|
log.success("Generated AGENTS.md");
|
|
66
67
|
|
|
68
|
+
// --- Write ~/.cursor/rules/pal-*.mdc ---
|
|
69
|
+
mkdirSync(resolve(CURSOR_DIR, "rules"), { recursive: true });
|
|
70
|
+
writeContextDigests();
|
|
71
|
+
log.success(
|
|
72
|
+
"Written ~/.cursor/rules/pal-self-model.mdc + pal-wisdom.mdc + pal-opinions.mdc"
|
|
73
|
+
);
|
|
74
|
+
|
|
67
75
|
log.success("Cursor installation complete");
|
|
68
76
|
console.log("");
|
|
69
77
|
log.info(`Skills: ${countSkills()}`);
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Removes PAL skill symlinks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { copyFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { copyFileSync, existsSync, unlinkSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
10
|
+
import { cursorFilename, getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
10
11
|
import {
|
|
11
12
|
loadCursorHooksTemplate,
|
|
12
13
|
log,
|
|
@@ -56,4 +57,20 @@ if (removedAgents.length > 0) {
|
|
|
56
57
|
// --- Remove PAL system docs ---
|
|
57
58
|
removePalDocs();
|
|
58
59
|
|
|
60
|
+
// --- Remove ~/.cursor/rules/pal-*.mdc ---
|
|
61
|
+
for (const src of getSemiStaticSources()) {
|
|
62
|
+
try {
|
|
63
|
+
unlinkSync(resolve(CURSOR_DIR, "rules", cursorFilename(src)));
|
|
64
|
+
} catch {
|
|
65
|
+
/* gone */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Backward compat: remove legacy merged file if present
|
|
69
|
+
try {
|
|
70
|
+
unlinkSync(resolve(CURSOR_DIR, "rules", "pal-context.mdc"));
|
|
71
|
+
} catch {
|
|
72
|
+
/* gone */
|
|
73
|
+
}
|
|
74
|
+
log.success("Removed ~/.cursor/rules/pal-*.mdc");
|
|
75
|
+
|
|
59
76
|
log.success("Cursor uninstall complete");
|
package/src/targets/lib.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
unlinkSync,
|
|
15
15
|
writeFileSync,
|
|
16
16
|
} from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
17
18
|
import { resolve } from "node:path";
|
|
18
19
|
import { assets, palHome, platform } from "../hooks/lib/paths";
|
|
19
20
|
|
|
@@ -41,6 +42,31 @@ export function writeJson(path: string, data: unknown): void {
|
|
|
41
42
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/** Resolve the VS Code user settings.json path cross-platform. Returns null on unknown platforms. */
|
|
46
|
+
export function vscodeSettingsFile(): string | null {
|
|
47
|
+
const h = homedir();
|
|
48
|
+
if (process.platform === "darwin") {
|
|
49
|
+
return resolve(h, "Library", "Application Support", "Code", "User", "settings.json");
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === "linux") {
|
|
52
|
+
return resolve(
|
|
53
|
+
process.env.XDG_CONFIG_HOME ?? resolve(h, ".config"),
|
|
54
|
+
"Code",
|
|
55
|
+
"User",
|
|
56
|
+
"settings.json"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (process.platform === "win32") {
|
|
60
|
+
return resolve(
|
|
61
|
+
process.env.APPDATA ?? resolve(h, "AppData", "Roaming"),
|
|
62
|
+
"Code",
|
|
63
|
+
"User",
|
|
64
|
+
"settings.json"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
// --- Settings template merge/unmerge ---
|
|
45
71
|
|
|
46
72
|
type HookEntry = { matcher?: string; hooks?: Array<{ type: string; command: string }> };
|
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
* Deploys plugin, installs skills, generates AGENTS.md.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
statSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
7
14
|
import { resolve } from "node:path";
|
|
8
15
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
9
16
|
import { palPkg, platform } from "../../hooks/lib/paths";
|
|
17
|
+
import { getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
10
18
|
import {
|
|
11
19
|
copyAgentsForOpencode,
|
|
12
20
|
copyPalDocs,
|
|
@@ -70,6 +78,26 @@ log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
|
70
78
|
regenerateIfNeeded();
|
|
71
79
|
log.success("Generated ~/.config/opencode/AGENTS.md");
|
|
72
80
|
|
|
81
|
+
// --- 7. Add semi-static digest files to instructions[] in config.json ---
|
|
82
|
+
const configPath = resolve(OC_GLOBAL_DIR, "config.json");
|
|
83
|
+
const staticFiles = getSemiStaticSources().map((s) => s.path);
|
|
84
|
+
let ocConfig: Record<string, unknown> = {};
|
|
85
|
+
if (existsSync(configPath) && statSync(configPath).size > 0) {
|
|
86
|
+
try {
|
|
87
|
+
ocConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
88
|
+
} catch {
|
|
89
|
+
/* start fresh */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const existingInstructions = Array.isArray(ocConfig.instructions)
|
|
93
|
+
? (ocConfig.instructions as string[])
|
|
94
|
+
: [];
|
|
95
|
+
ocConfig.instructions = [...new Set([...existingInstructions, ...staticFiles])];
|
|
96
|
+
writeFileSync(configPath, `${JSON.stringify(ocConfig, null, 2)}\n`, "utf-8");
|
|
97
|
+
log.success(
|
|
98
|
+
`Updated config.json: ${(ocConfig.instructions as string[]).length} instructions`
|
|
99
|
+
);
|
|
100
|
+
|
|
73
101
|
log.success("opencode installation complete");
|
|
74
102
|
console.log("");
|
|
75
103
|
log.info(`Plugin: ${pluginDst}`);
|
|
@@ -74,7 +74,7 @@ const PALPlugin: Plugin = async ({ directory, client }: PluginInput) => {
|
|
|
74
74
|
return {
|
|
75
75
|
// --- Per-message: Inject dynamic system reminder ---
|
|
76
76
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
77
|
-
const reminder = buildSystemReminder();
|
|
77
|
+
const reminder = buildSystemReminder({ agent: "opencode" });
|
|
78
78
|
if (reminder) output.system.push(reminder);
|
|
79
79
|
},
|
|
80
80
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PAL — opencode uninstaller (TypeScript)
|
|
3
|
-
* Removes plugin
|
|
3
|
+
* Removes plugin, AGENTS.md, and PAL entries from config.json.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { unlinkSync } from "node:fs";
|
|
6
|
+
import { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { platform } from "../../hooks/lib/paths";
|
|
9
|
+
import { getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
9
10
|
import { log, removeAgentsFromOpencode, removePalDocs, removeSkills } from "../lib";
|
|
10
11
|
|
|
11
12
|
const OC_GLOBAL_DIR = platform.opencodeDir() || "";
|
|
@@ -41,7 +42,7 @@ if (removedAgents.length > 0)
|
|
|
41
42
|
// --- Remove PAL system docs ---
|
|
42
43
|
removePalDocs();
|
|
43
44
|
|
|
44
|
-
// --- Remove AGENTS.md and CLAUDE.md
|
|
45
|
+
// --- Remove AGENTS.md and CLAUDE.md ---
|
|
45
46
|
const agentsMd = resolve(OC_GLOBAL_DIR, "AGENTS.md");
|
|
46
47
|
const claudeMd = resolve(PAL_CLAUDE_DIR, "CLAUDE.md");
|
|
47
48
|
try {
|
|
@@ -57,4 +58,30 @@ try {
|
|
|
57
58
|
/* gone */
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// --- Remove PAL entries from config.json instructions[] ---
|
|
62
|
+
const configPath = resolve(OC_GLOBAL_DIR, "config.json");
|
|
63
|
+
if (existsSync(configPath) && statSync(configPath).size > 0) {
|
|
64
|
+
try {
|
|
65
|
+
const ocConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
66
|
+
string,
|
|
67
|
+
unknown
|
|
68
|
+
>;
|
|
69
|
+
if (Array.isArray(ocConfig.instructions)) {
|
|
70
|
+
const palFiles = new Set(getSemiStaticSources().map((s) => s.path));
|
|
71
|
+
const filtered = (ocConfig.instructions as string[]).filter(
|
|
72
|
+
(p) => !palFiles.has(p)
|
|
73
|
+
);
|
|
74
|
+
if (filtered.length === 0) {
|
|
75
|
+
delete ocConfig.instructions;
|
|
76
|
+
} else {
|
|
77
|
+
ocConfig.instructions = filtered;
|
|
78
|
+
}
|
|
79
|
+
writeFileSync(configPath, `${JSON.stringify(ocConfig, null, 2)}\n`, "utf-8");
|
|
80
|
+
log.success("Removed PAL entries from config.json instructions[]");
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
log.warn("Could not clean config.json instructions[] — check manually");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
log.success("opencode uninstall complete");
|