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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/assets/skills/projects/SKILL.md +0 -1
  3. package/assets/skills/telos/SKILL.md +7 -52
  4. package/assets/templates/PAL/ALGORITHM.md +28 -3
  5. package/assets/templates/PAL/PROJECT_LIFECYCLE.md +48 -0
  6. package/assets/templates/PAL/README.md +1 -1
  7. package/assets/templates/PAL/STEERING_RULES.md +4 -0
  8. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +32 -17
  9. package/assets/templates/PAL/WORK_TRACKING.md +1 -1
  10. package/assets/templates/pal-settings.json +1 -3
  11. package/assets/templates/settings.claude.json +2 -1
  12. package/package.json +1 -1
  13. package/src/cli/setup-telos.ts +12 -79
  14. package/src/hooks/LoadContext.ts +22 -10
  15. package/src/hooks/handlers/context-digests.ts +74 -0
  16. package/src/hooks/handlers/session-intelligence.ts +9 -86
  17. package/src/hooks/lib/claude-md.ts +69 -14
  18. package/src/hooks/lib/context.ts +57 -139
  19. package/src/hooks/lib/relationship.ts +3 -3
  20. package/src/hooks/lib/security.ts +2 -0
  21. package/src/hooks/lib/semi-static.ts +186 -0
  22. package/src/hooks/lib/setup.ts +0 -5
  23. package/src/hooks/lib/stop.ts +3 -0
  24. package/src/targets/claude/uninstall.ts +1 -1
  25. package/src/targets/copilot/install.ts +39 -8
  26. package/src/targets/copilot/uninstall.ts +58 -17
  27. package/src/targets/cursor/install.ts +8 -0
  28. package/src/targets/cursor/uninstall.ts +18 -1
  29. package/src/targets/lib.ts +26 -0
  30. package/src/targets/opencode/install.ts +29 -1
  31. package/src/targets/opencode/plugin.ts +1 -1
  32. package/src/targets/opencode/uninstall.ts +30 -3
  33. package/src/tools/agent/handoff-note.ts +116 -0
  34. package/src/tools/agent/relationship-note.ts +51 -0
  35. package/src/tools/relationship-reflect.ts +2 -2
  36. package/src/tools/self-model.ts +4 -4
  37. package/assets/templates/telos/PROJECTS.md +0 -7
package/README.md CHANGED
@@ -102,7 +102,7 @@ pal cli install # all available (default)
102
102
  | Claude Code | Full | Yes | Yes | Yes | Yes |
103
103
  | opencode | Full | Yes | Yes (plugin) | Yes | Yes |
104
104
  | Cursor | Full | Yes | Yes | Yes (injected via hook) | Yes |
105
- | GitHub Copilot | Full | Yes | Yes | Yes (via copilot-instructions.md) | Yes |
105
+ | GitHub Copilot | Full | Yes | Yes | Yes (via `~/.copilot/instructions/*.instructions.md`) | Yes |
106
106
  | Codex | Partial | Yes | No | Yes | No |
107
107
 
108
108
  ---
@@ -101,7 +101,6 @@ User: "mark <project> as complete"
101
101
  - **Don't dump the full JSON.** Summarize. The user can ask for the raw payload.
102
102
  - **Don't write without confirming the field choice on ambiguous "store" requests.** A "fact" sticks forever; a "next step" implies follow-up — these are different commitments.
103
103
  - **Don't edit the JSON files directly.** Always use the CLI — it timestamps `updated` and keeps the schema valid.
104
- - **Don't re-introduce `~/.pal/telos/PROJECTS.md`.** That file and its `update-projects.ts` tool are deprecated. The legacy `telos` skill carries a deprecation notice for this reason.
105
104
  - **Don't confuse `add-fact` with the `telos` skill's `LEARNED.md` or `IDEAS.md`.** Project facts are scoped to one project; TELOS lessons are cross-cutting.
106
105
 
107
106
  ## Rules
@@ -1,12 +1,9 @@
1
1
  ---
2
2
  name: telos
3
- description: Personal context management. Use when discussing goals, beliefs, challenges, identity, updating telos, life context, changing a goal, priorities, what do I believe, current obstacles, mission, or strategies.
3
+ description: Personal context management. Use when discussing goals, beliefs, challenges, identity, updating telos, life context, changing a goal, what do I believe, current obstacles, mission, or strategies.
4
4
  argument-hint: [area to view or update]
5
5
  ---
6
6
 
7
- > ⚠️ **DEPRECATION NOTICE — Project management has moved.**
8
- > Project tracking is now handled by the `projects` skill, backed by `~/.pal/tools/project.ts` and per-project state in `~/.pal/memory/state/progress/`. **Do not use this skill for projects.** The `PROJECTS.md` references and `update-projects.ts` tool below are legacy and slated for removal — they remain only because the initial setup wizard (`src/cli/setup-telos.ts`) still depends on them. For anything project-related, invoke the `projects` skill.
9
-
10
7
  Manage the user's TELOS files — the persistent personal context that drives PAL.
11
8
 
12
9
  ## TELOS Files
@@ -16,7 +13,6 @@ All files live in `~/.pal/telos/`:
16
13
  | File | Contains |
17
14
  |------|----------|
18
15
  | `GOALS.md` | Short/medium/long-term goals |
19
- | `PROJECTS.md` | Active projects, status, priority |
20
16
  | `BELIEFS.md` | Core principles and values |
21
17
  | `CHALLENGES.md` | Current obstacles |
22
18
  | `MISSION.md` | Purpose and direction |
@@ -32,33 +28,18 @@ Read the file directly from `~/.pal/telos/` when the user asks about any area. S
32
28
 
33
29
  ## Updating
34
30
 
35
- ### General TELOS files (append)
36
-
37
- For all files except PROJECTS.md — appends content, creates backup, logs the change:
31
+ Appends content, creates backup, logs the change:
38
32
 
39
33
  ```bash
40
34
  bun ~/.pal/skills/telos/tools/update-telos.ts <FILE> "<content>" "<description>"
41
35
  ```
42
36
 
43
- ### Projects (upsert by ID)
44
-
45
- For PROJECTS.md — upserts a row by the ID column. Replaces if the ID exists, appends if new:
46
-
47
- ```bash
48
- bun ~/.pal/skills/telos/tools/update-projects.ts <id> "<row>" "<description>"
49
- ```
50
-
51
- The ID is the first column of the table. Use short, lowercase, kebab-case slugs (e.g., `my-project`, `side-gig`).
52
-
53
37
  ## Routing
54
38
 
55
39
  | Intent | Action |
56
40
  |--------|--------|
57
- | "what am I working on", "my projects", "priorities" | Read `PROJECTS.md`, summarize active work |
58
41
  | "my goals", "what are my goals" | Read `GOALS.md`, present current state |
59
- | "update goals/projects/beliefs/challenges" | Read the target file, discuss changes with user, then run update tool |
60
- | "add a project", "new project" | Read `PROJECTS.md`, confirm with user, run update tool |
61
- | "complete/remove a project" | Read `PROJECTS.md`, confirm with user, update status via tool |
42
+ | "update goals/beliefs/challenges" | Read the target file, discuss changes with user, then run update tool |
62
43
  | "what do I believe", "my principles" | Read `BELIEFS.md` |
63
44
  | "current obstacles", "challenges" | Read `CHALLENGES.md` |
64
45
  | "I learned something", "lesson" | Discuss, then append to `LEARNED.md` via tool |
@@ -67,38 +48,12 @@ The ID is the first column of the table. Use short, lowercase, kebab-case slugs
67
48
 
68
49
  ## Examples
69
50
 
70
- **Example 1: Checking projects**
71
- ```
72
- User: "what am I working on?"
73
- → Read PROJECTS.md
74
- → Summarize active work by priority — don't list every column
75
- → Highlight status changes, blockers, what needs attention
76
- ```
77
-
78
- **Example 2: Adding a project**
79
- ```
80
- User: "add my new side project"
81
- → Ask: "What's the project name, status, and priority?"
82
- → User provides details
83
- → Show the row you'll add, confirm
84
- → Run: bun ~/.pal/skills/telos/tools/update-projects.ts side-project "| side-project | Side Project | In progress | Medium | Description |" "Added Side Project"
85
- ```
86
-
87
- **Example 3: Updating a project**
88
- ```
89
- User: "mark X as complete"
90
- → Read PROJECTS.md, find the entry and its ID
91
- → Show updated row, confirm
92
- → Run: bun ~/.pal/skills/telos/tools/update-projects.ts some-id "| some-id | Project Name | Complete | High | ... |" "Marked project as complete"
93
- → The existing row is replaced, not duplicated
94
- ```
95
-
96
- **Example 4: Updating goals**
51
+ **Updating goals**
97
52
  ```
98
53
  User: "I finished the migration, update my goals"
99
54
  → Read GOALS.md to see current state
100
55
  → Discuss what changed — what's done, what's next
101
- → Run tool with --id to update existing goal entry
56
+ → Run tool with updated content
102
57
  → Remind: CLAUDE.md regenerates next session
103
58
  ```
104
59
 
@@ -106,8 +61,8 @@ User: "I finished the migration, update my goals"
106
61
 
107
62
  - **Don't dump raw file contents.** Summarize what's relevant to the user's question. They can ask for the full file if needed.
108
63
  - **Don't update without confirming.** Always show what you'll change and get a "yes" before running the tool.
109
- - **Don't create new TELOS files.** Only the 10 listed files are valid. If something doesn't fit, suggest the closest match.
110
- - **Don't mix TELOS with identity.** AI/principal identity lives in `pal-settings.json`, not TELOS. TELOS is personal context — goals, beliefs, projects.
64
+ - **Don't create new TELOS files.** Only the 9 listed files are valid. If something doesn't fit, suggest the closest match.
65
+ - **Don't mix TELOS with identity.** AI/principal identity lives in `pal-settings.json`, not TELOS. TELOS is personal context — goals, beliefs, challenges.
111
66
  - **Don't reference stale data.** If TELOS was loaded earlier in the session via context routing, re-read the file before updating — it may have changed.
112
67
 
113
68
  ## Rules
@@ -206,7 +206,32 @@ bun ~/.pal/tools/algorithm-reflect.ts --task "description" --criteria N --passed
206
206
  --q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
207
207
  ```
208
208
 
209
- **3. Open Threads** — for each unresolved question, decision, or follow-up that came up during this session:
209
+ **3. Relationship note** — write one Session entry capturing what was done this session:
210
+
211
+ ```bash
212
+ bun ~/.pal/tools/relationship-note.ts --b "description"
213
+ ```
214
+
215
+ - 1-2 sentences, first-person, specific — name the actual system/file/concept worked on
216
+ - ✓ "Debugged the React Query cache split logic and resolved stuck message state in the onFinish callback"
217
+ - ✗ "Helped with memory improvements" — too vague, no system named
218
+ - Skip only if the session was a trivial lookup or typo fix (same rule as step 2)
219
+
220
+ **4. Handoff note** — if work is unfinished, write what remains so the next session can pick up immediately:
221
+
222
+ ```bash
223
+ # Work still in progress:
224
+ bun ~/.pal/tools/handoff-note.ts --title "what we were doing" --text "what remains, decisions made, next steps"
225
+
226
+ # Work finished — clear any previous in-progress handoff:
227
+ bun ~/.pal/tools/handoff-note.ts --done --title "what we completed"
228
+ ```
229
+
230
+ - Write if anything is left mid-flight: unfinished implementation, open decision, partially debugged issue
231
+ - Skip if the session fully resolved everything it set out to do
232
+ - `--text` should answer: what's next, what was decided, what to watch out for
233
+
234
+ **5. Open Threads** — for each unresolved question, decision, or follow-up that came up during this session:
210
235
 
211
236
  ```bash
212
237
  bun ~/.pal/tools/thread.ts --add --title "brief title" --context "why it matters, what needs to happen"
@@ -218,7 +243,7 @@ Only add threads that genuinely need follow-up. Resolve existing threads if this
218
243
  bun ~/.pal/tools/thread.ts --resolve --id <id>
219
244
  ```
220
245
 
221
- **4. Opinion capture** — scan the conversation for moments where the user:
246
+ **6. Opinion capture** — scan the conversation for moments where the user:
222
247
  - Confirmed something you did: "yes exactly", "keep doing that", "10 rated", accepted without pushback
223
248
  - Corrected something you did: "no", "don't do that", "stop", "that's not what I meant"
224
249
  - Revealed a preference by repeating a pattern (asked for concise answers twice, always checked PAI first, etc.)
@@ -237,7 +262,7 @@ bun ~/.pal/skills/opinion/tools/opinion.ts add "the preference" --category commu
237
262
 
238
263
  Skip if nothing in the conversation touched preferences or working style.
239
264
 
240
- **5. Wisdom Frame** (Extended+ only) — if the session produced a genuine, reusable insight:
265
+ **7. Wisdom Frame** (Extended+ only) — if the session produced a genuine, reusable insight:
241
266
 
242
267
  ```bash
243
268
  bun ~/.pal/tools/wisdom-frame.ts --domain <domain> --observation "insight" [--type principle|contextual-rule|anti-pattern|evolution]
@@ -0,0 +1,48 @@
1
+ # Project Lifecycle
2
+
3
+ You (the AI) own the project lifecycle. Project state lives in `~/.pal/memory/state/progress/{slug}.json`, one file per project, managed via `bun ~/.pal/tools/project.ts`. Active projects are auto-injected into every SessionStart context regardless of cwd.
4
+
5
+ ## When to invoke the CLI
6
+
7
+ **Proactive registration.** When SessionStart context says `💡 cwd <path> is not yet registered; suggest registering if substantive work begins`, AND the user starts substantive work (not just "hi"), surface the suggestion conversationally before the second tool call: *"I see we're in `<basename>` and it's not registered yet — want me to add it as a project?"*
8
+
9
+ - **Default name** = the FULL last path segment of `cwd`, lowercased. For `/repos/portable-agent-layer` the default is `portable-agent-layer`. Never split on `-`.
10
+ - **Confirm before creating.** Never auto-create without explicit user approval ("yes", "do it", "register").
11
+ - **Capture objectives in conversation.** If the user accepts but doesn't volunteer objectives, ask one short question; or infer from the last few messages and confirm.
12
+
13
+ **Append as you go.** When the user describes plans, blockers, or decisions during normal work, invoke the relevant subcommand to keep state current — that's the dynamism this system is built for.
14
+
15
+ | user says | you call |
16
+ |---|---|
17
+ | "let's also add X" / "we should handle Y next" | `add-next <name> "..."` |
18
+ | "we're blocked on Z" / "Z is blocking this" | `add-blocker <name> "..."` |
19
+ | "the objective here is to ship X by Y" | `add-objective <name> "..."` |
20
+ | "the API base is at Z" / "tech stack is Bun + TS" — stable, reference-flavored facts | `add-fact <name> "..."` |
21
+ | "we decided to use A because B" | `add-decision <name> "A" "B"` |
22
+ | "let's pause this" / "shelve it" | `pause <name>` |
23
+ | "we shipped" / "this is done" | `complete <name>` |
24
+
25
+ **Don't** invoke for fleeting comments, hypotheticals, or things the user is just thinking through. Wait for a clear declarative ("let's add X", "Z is blocking us"), not a question or musing.
26
+
27
+ ## When NOT to suggest registration
28
+
29
+ - cwd has no project marker (`.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, etc.) — it's a notes folder, not a project.
30
+ - The user is clearly browsing or doing a one-off task.
31
+ - An ancestor of multiple registered projects is the cwd (e.g. `~/Development/git/`) — that's browse mode by design.
32
+ - You're unsure. Err toward not registering.
33
+
34
+ ## CLI cheat sheet
35
+
36
+ ```
37
+ list show all projects
38
+ create [name] [--path PATH] [--objectives X] register (defaults: name=basename(cwd), path=cwd)
39
+ resume <name> print full project JSON
40
+ complete | archive | pause | unpause <name> change status
41
+ add-fact | add-objective | add-next | add-blocker <name> "text"
42
+ add-decision <name> "decision" "rationale"
43
+ add-handoff <name> "text"
44
+ rm-fact | rm-objective | rm-next | rm-blocker <name> <index>
45
+ rm <name> delete the project file (rare; prefer archive)
46
+ ```
47
+
48
+ The Stop hook auto-touches `updated` (and optionally writes a `handoff`) whenever cwd resolves to an active project — you don't need to refresh timestamps manually.
@@ -21,7 +21,7 @@ PAL is a persistent, cross-platform, cross-agent layer for portable AI workflows
21
21
  tools/ # Agent CLI tools (symlink → repo src/tools/agent/)
22
22
  skills/ # Installed skills (symlinks → assets/skills/)
23
23
  telos/ # User life context (TELOS)
24
- MISSION.md, GOALS.md, PROJECTS.md, BELIEFS.md,
24
+ MISSION.md, GOALS.md, BELIEFS.md,
25
25
  CHALLENGES.md, STRATEGIES.md, IDEAS.md, LEARNED.md,
26
26
  MODELS.md, NARRATIVES.md
27
27
 
@@ -45,3 +45,7 @@ Correct: Review your recent actions → find the mistake → fix it → explain
45
45
  **Act on what you know.** When tracked opinions or relationship notes reveal user preferences, apply them to your behavior. If you know the user prefers concise responses, be concise. If they prefer manual commits, never offer to commit.
46
46
  Bad: Memory says user dislikes verbose summaries → you write a 3-paragraph recap after every change.
47
47
  Correct: Memory says user dislikes verbose summaries → you keep the summary to one line.
48
+
49
+ **Don't trust, verify.** When adding a test or asserting a change works, prove it by making it fail first. A test that passes without ever being broken demonstrates nothing — it may be testing the wrong thing or nothing at all. Break it intentionally, confirm it fails for the right reason, then restore it.
50
+ Bad: Add a test, see it green, move on. The test may pass vacuously.
51
+ Correct: Add the test → run it green → break the code it covers → confirm it goes red → restore → now it's a real test.
@@ -233,6 +233,7 @@ Brief description.
233
233
  │ │ - Work learning capture
234
234
  │ │ - Failure logging
235
235
  │ │ - Reflect trigger check
236
+ │ │ - Write context digests (semi-static sources)
236
237
  │ │ - Auto-backup
237
238
  │ │ - Count updates
238
239
  │ │ - Tab reset
@@ -357,34 +358,44 @@ Relationship notes (O/B types)
357
358
 
358
359
  ## Context Loading Architecture
359
360
 
360
- ### Two-Layer Design
361
+ ### Three-Tier Design
361
362
 
362
- **Static context** (loaded natively by the agent):
363
- - CLAUDE.md — identity, modes, context routing table
364
- - Loaded once at session start, always available
363
+ **Tier 1 — Operational** (loaded natively at agent startup):
364
+ - CLAUDE.md / AGENTS.md — identity, modes, context routing table
365
+ - Loaded once per session, always available, never re-fetched
365
366
 
366
- **Dynamic context** (injected by LoadContext hook):
367
- - Changes per-session, can't live in a static file
368
- - Injected as `<system-reminder>` block to stdout
367
+ **Tier 2 — Semi-static** (pre-compiled at previous session stop, loaded natively):
368
+ - Self-model, wisdom, opinions, synthesis, failures, steering rules
369
+ - Written to disk by `writeContextDigests()` at Stop time
370
+ - Loaded natively per-agent: `@imports` in CLAUDE.md (Claude Code), `instructions[]` in opencode config, `.mdc` rules in `~/.cursor/rules/` (Cursor), `.instructions.md` in `~/.copilot/instructions/` (Copilot)
371
+ - Content is global/user-level — safe to pre-compile (not project-scoped)
372
+
373
+ **Tier 3 — Dynamic** (injected fresh each session by LoadContext hook):
374
+ - Handoff notes, session intelligence, open threads, relationship notes, project history
375
+ - Changes per-session or is project-scoped — can't be pre-compiled
376
+ - Injected as `<system-reminder>` block via stdout
369
377
  - Each section independently toggleable in `pal-settings.json → dynamicContext`
370
378
 
371
- ### Injection Order
379
+ ### Semi-Static Registry
380
+
381
+ All semi-static sources are defined in `src/hooks/lib/semi-static.ts` via `getSemiStaticSources()`. Adding one entry there propagates automatically to every consumer — no other files need to change.
382
+
383
+ ### Dynamic Injection Order
372
384
 
373
385
  ```
374
386
  LoadContext.ts
375
387
 
376
388
  ├─► Regenerate CLAUDE.md if template/telos changed
377
389
 
378
- └─► Build system-reminder:
390
+ └─► Build system-reminder (dynamic sections only):
379
391
  1. loadAtStartup files (user-configured)
380
- 2. Crystallized principles (wisdom frames)
381
- 3. Tracked opinions (≥85% confidence)
382
- 4. Recent interaction notes (last 2 days)
383
- 5. Learning digest (this project + other recent)
384
- 6. Pattern synthesis recommendations
385
- 7. Signal trends (today/week/trend)
386
- 8. Failure patterns (last 5 low-rating contexts)
387
- 9. Active work summary (sessions + projects)
392
+ 2. Handoff note (in-progress work from last session)
393
+ 3. Session intelligence (rating trend, algorithm performance)
394
+ 4. Open threads (current project only)
395
+ 5. Recent interaction notes (last 2 days)
396
+ 6. Active projects
397
+ 7. Project session history (this project)
398
+ 8. Signal trends (today/week/trend)
388
399
  ```
389
400
 
390
401
  ### On-Demand Context
@@ -436,6 +447,9 @@ src/targets/
436
447
  ├── cursor/ # Cursor specific
437
448
  │ ├── install.ts # Register hooks + skills in ~/.cursor/
438
449
  │ └── uninstall.ts
450
+ ├── copilot/ # GitHub Copilot specific
451
+ │ ├── install.ts # Write instruction files + update VS Code settings
452
+ │ └── uninstall.ts
439
453
  └── lib.ts # Shared: JSON read/write, settings merge, TELOS scaffold
440
454
  ```
441
455
 
@@ -452,6 +466,7 @@ All paths resolve through `src/hooks/lib/paths.ts`:
452
466
  | Claude config | `~/.claude` | `PAL_CLAUDE_DIR` |
453
467
  | opencode config | `~/.config/opencode` | `PAL_OPENCODE_DIR` |
454
468
  | Cursor config | `~/.cursor` | `PAL_CURSOR_DIR` |
469
+ | Copilot config | `~/.copilot` | `PAL_COPILOT_DIR` |
455
470
  | Codex config | `~/.codex` | `PAL_CODEX_DIR` |
456
471
  | Agents dir | `~/.agents` | `PAL_AGENTS_DIR` |
457
472
 
@@ -4,4 +4,4 @@ PAL tracks your work across sessions in `memory/state/sessions.json` (auto-captu
4
4
 
5
5
  ## Projects
6
6
 
7
- Projects are managed in `telos/PROJECTS.md` and force-loaded at session startup via `pal-settings.json loadAtStartup.files`.
7
+ Projects are managed via the `/projects` skill. State lives in `~/.pal/memory/state/progress/{slug}.json`, one file per project. Inspect with `bun ~/.pal/tools/project.ts list`; manage via `~/.pal/docs/PROJECT_LIFECYCLE.md`.
@@ -13,9 +13,7 @@
13
13
  },
14
14
  "loadAtStartup": {
15
15
  "_docs": "Files force-loaded into session context at startup. Injected as <system-reminder> blocks.",
16
- "files": [
17
- "~/.pal/docs/STEERING_RULES.md"
18
- ]
16
+ "files": []
19
17
  },
20
18
  "dynamicContext": {
21
19
  "_docs": "Dynamic context sections injected at session start. Set to false to disable.",
@@ -97,5 +97,6 @@
97
97
  ]
98
98
  }
99
99
  ]
100
- }
100
+ },
101
+ "autoMemoryEnabled": false
101
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,72 +7,9 @@
7
7
  import { writeFileSync } from "node:fs";
8
8
  import { resolve } from "node:path";
9
9
  import * as clack from "@clack/prompts";
10
- import { upsertProject } from "../../assets/skills/telos/tools/update-projects";
11
10
  import { palHome } from "../hooks/lib/paths";
12
11
  import { hasRealContent, SETUP_STEPS, STEP_ORDER } from "../hooks/lib/setup";
13
12
 
14
- function toKebabCase(name: string): string {
15
- return name
16
- .toLowerCase()
17
- .replace(/[^a-z0-9]+/g, "-")
18
- .replace(/^-|-$/g, "");
19
- }
20
-
21
- async function promptProjectsLoop(): Promise<void> {
22
- const addFirst = await clack.confirm({
23
- message: "Do you want to add any projects now?",
24
- initialValue: true,
25
- });
26
- if (clack.isCancel(addFirst) || !addFirst) return;
27
-
28
- let addMore = true;
29
- while (addMore) {
30
- const name = await clack.text({
31
- message: "Project name?",
32
- placeholder: "e.g. PAL, My SaaS, Work Dashboard",
33
- });
34
- if (clack.isCancel(name)) return;
35
-
36
- const status = await clack.select({
37
- message: "Status?",
38
- options: [
39
- { value: "Active", label: "Active" },
40
- { value: "Planning", label: "Planning" },
41
- { value: "Paused", label: "Paused" },
42
- { value: "Complete", label: "Complete" },
43
- ],
44
- });
45
- if (clack.isCancel(status)) return;
46
-
47
- const priority = await clack.select({
48
- message: "Priority?",
49
- options: [
50
- { value: "High", label: "High" },
51
- { value: "Medium", label: "Medium" },
52
- { value: "Low", label: "Low" },
53
- ],
54
- });
55
- if (clack.isCancel(priority)) return;
56
-
57
- const notes = await clack.text({
58
- message: "Notes? (optional — leave blank to skip)",
59
- placeholder: "e.g. Building the v2 API, blocked on design review",
60
- });
61
- if (clack.isCancel(notes)) return;
62
-
63
- const id = toKebabCase(name as string);
64
- const row = `| ${id} | ${name} | ${status} | ${priority} | ${notes || ""} |`;
65
- upsertProject(id, row, `Added ${name} during PAL setup`);
66
- clack.log.success(`Added: ${name}`);
67
-
68
- const again = await clack.confirm({
69
- message: "Add another project?",
70
- initialValue: false,
71
- });
72
- if (clack.isCancel(again) || !again) addMore = false;
73
- }
74
- }
75
-
76
13
  /** Prompt for missing TELOS context. Skips any step whose file already has real content. */
77
14
  export async function promptTelos(): Promise<void> {
78
15
  // Skip interactive prompts in non-TTY environments (tests, CI)
@@ -95,25 +32,21 @@ export async function promptTelos(): Promise<void> {
95
32
  );
96
33
 
97
34
  for (const key of pending) {
98
- if (key === "projects") {
99
- await promptProjectsLoop();
100
- } else {
101
- const step = SETUP_STEPS[key];
102
- const title = key.charAt(0).toUpperCase() + key.slice(1);
103
-
104
- const answer = await clack.text({
105
- message: step.question,
106
- placeholder: step.hint,
107
- });
35
+ const step = SETUP_STEPS[key];
36
+ const title = key.charAt(0).toUpperCase() + key.slice(1);
108
37
 
109
- if (clack.isCancel(answer)) {
110
- clack.cancel("Setup cancelled");
111
- return;
112
- }
38
+ const answer = await clack.text({
39
+ message: step.question,
40
+ placeholder: step.hint,
41
+ });
113
42
 
114
- const filePath = resolve(home, step.file);
115
- writeFileSync(filePath, `# ${title}\n\n${answer}\n`, "utf-8");
43
+ if (clack.isCancel(answer)) {
44
+ clack.cancel("Setup cancelled");
45
+ return;
116
46
  }
47
+
48
+ const filePath = resolve(home, step.file);
49
+ writeFileSync(filePath, `# ${title}\n\n${answer}\n`, "utf-8");
117
50
  }
118
51
 
119
52
  clack.outro("Personal context saved ✓");
@@ -9,10 +9,10 @@
9
9
  * context directly to ~/.copilot/copilot-instructions.md so it is picked up on load.
10
10
  */
11
11
 
12
- import { existsSync, lstatSync, unlinkSync, writeFileSync } from "node:fs";
12
+ import { mkdirSync, writeFileSync } from "node:fs";
13
13
  import { resolve } from "node:path";
14
14
  import { buildClaudeMd, regenerateIfNeeded } from "./lib/claude-md";
15
- import { buildSystemReminder } from "./lib/context";
15
+ import { type AgentTarget, buildSystemReminder } from "./lib/context";
16
16
  import { logDebug, logError } from "./lib/log";
17
17
  import { platform } from "./lib/paths";
18
18
 
@@ -36,21 +36,33 @@ try {
36
36
 
37
37
  // --- Context to stdout (or file for Copilot) ---
38
38
  try {
39
- const reminder = buildSystemReminder();
39
+ // Determine agent target — controls which sections are skipped (loaded natively instead).
40
+ let agent: AgentTarget = "claude";
41
+ if (process.env.PAL_AGENT === "copilot") agent = "copilot";
42
+ else if (process.env.CURSOR_VERSION) agent = "cursor";
43
+ const reminder = buildSystemReminder({ agent });
40
44
  if (!reminder) process.exit(0);
41
45
 
42
46
  if (process.env.PAL_AGENT === "copilot") {
43
- // Copilot: sessionStart output is ignored write merged context to copilot-instructions.md
44
- const instructionsPath = resolve(platform.copilotDir(), "copilot-instructions.md");
47
+ // Copilot: semi-static in ~/.copilot/instructions/pal-*.instructions.md (written at stop).
48
+ // Write AGENTS.md + dynamic context to pal-session.instructions.md on each session start.
49
+ const instructionsDir = resolve(platform.copilotDir(), "instructions");
50
+ mkdirSync(instructionsDir, { recursive: true });
45
51
  const agentsMd = buildClaudeMd();
46
52
  const context = [agentsMd, reminder].filter(Boolean).join("\n\n");
47
- if (existsSync(instructionsPath) && lstatSync(instructionsPath).isSymbolicLink()) {
48
- unlinkSync(instructionsPath);
53
+ if (context) {
54
+ writeFileSync(
55
+ resolve(instructionsDir, "pal-session.instructions.md"),
56
+ `---\napplyTo: "**"\n---\n\n${context}`,
57
+ "utf-8"
58
+ );
49
59
  }
50
- writeFileSync(instructionsPath, context, "utf-8");
51
- logDebug("LoadContext", `Copilot instructions written: ${context.length} chars`);
60
+ logDebug(
61
+ "LoadContext",
62
+ `Copilot session instructions written: ${context.length} chars`
63
+ );
52
64
  } else if (process.env.CURSOR_VERSION) {
53
- // Cursor: no native user-level rules — inject AGENTS.md + dynamic context
65
+ // Cursor: semi-static in ~/.cursor/rules/pal-context.mdc; inject AGENTS.md + dynamic here
54
66
  const agentsMd = buildClaudeMd();
55
67
  const context = [agentsMd, reminder].filter(Boolean).join("\n\n");
56
68
  process.stdout.write(JSON.stringify({ additional_context: context }));
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Handler: write pre-compiled context digest files for @import / instructions[].
3
+ *
4
+ * Runs at session stop so that CLAUDE.md can @import these files natively
5
+ * at the next session start, keeping hook stdout small.
6
+ *
7
+ * Sources are defined in src/hooks/lib/semi-static.ts — add one entry there
8
+ * to extend coverage to all consumers (CLAUDE.md, opencode, Cursor, Copilot).
9
+ */
10
+
11
+ import { existsSync, writeFileSync } from "node:fs";
12
+ import { dirname, resolve } from "node:path";
13
+ import { ensureDir, platform } from "../lib/paths";
14
+ import {
15
+ copilotFilename,
16
+ cursorFilename,
17
+ getSemiStaticSources,
18
+ } from "../lib/semi-static";
19
+
20
+ export function writeContextDigests(): void {
21
+ const sources = getSemiStaticSources();
22
+
23
+ // Resolve Cursor/Copilot destination dirs once (null if agent not installed)
24
+ let rulesDir: string | null = null;
25
+ let instructionsDir: string | null = null;
26
+
27
+ try {
28
+ const cursorDir = platform.cursorDir();
29
+ if (existsSync(cursorDir)) {
30
+ rulesDir = ensureDir(resolve(cursorDir, "rules"));
31
+ }
32
+ } catch {
33
+ /* non-fatal */
34
+ }
35
+
36
+ try {
37
+ const copilotDir = platform.copilotDir();
38
+ if (existsSync(copilotDir)) {
39
+ instructionsDir = ensureDir(resolve(copilotDir, "instructions"));
40
+ }
41
+ } catch {
42
+ /* non-fatal */
43
+ }
44
+
45
+ for (const src of sources) {
46
+ try {
47
+ const content = src.load();
48
+ if (!content) continue;
49
+
50
+ if (src.writesDigest) {
51
+ ensureDir(dirname(src.path));
52
+ writeFileSync(src.path, content, "utf-8");
53
+ }
54
+
55
+ if (rulesDir) {
56
+ writeFileSync(
57
+ resolve(rulesDir, cursorFilename(src)),
58
+ `---\ndescription: ${src.description}\nalwaysApply: true\n---\n\n${content}`,
59
+ "utf-8"
60
+ );
61
+ }
62
+
63
+ if (instructionsDir) {
64
+ writeFileSync(
65
+ resolve(instructionsDir, copilotFilename(src)),
66
+ `---\napplyTo: "**"\n---\n\n${content}`,
67
+ "utf-8"
68
+ );
69
+ }
70
+ } catch {
71
+ /* non-fatal */
72
+ }
73
+ }
74
+ }