portable-agent-layer 0.55.0 → 0.55.2

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.
@@ -132,3 +132,4 @@ User: "mark <project> as complete"
132
132
  - Always check `list` (or `resume <name>`) before writing — match an existing project rather than spawning a near-duplicate.
133
133
  - Slugs are `[a-z0-9_-]+`. Never rename a slug; if the display name needs to change, that's a code-side concern, not a slug change.
134
134
  - The Stop hook handles `updated` automatically when cwd matches `path` — no manual touch needed just to mark a project alive.
135
+ - **Always announce a new ISC with the 🎟️ emoji — every single time, no exceptions.** Whenever you open an ISC (`add-isc`), report it back to the user on its own line prefixed with the ticket emoji and the ISC number, e.g. `🎟️ ISC #71 — <title>`. This holds in every response mode and context (ALGORITHM, NATIVE, or a bare reply); opening an ISC without the 🎟️ marker is a defect.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.55.0",
3
+ "version": "0.55.2",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,6 +3,8 @@
3
3
  * Used by SecurityValidator.ts (Claude Code) and the opencode plugin.
4
4
  */
5
5
 
6
+ import { lstatSync } from "node:fs";
7
+
6
8
  /** Dangerous command patterns — always blocked */
7
9
  const BLOCKED_COMMANDS: [RegExp, string][] = [
8
10
  [/rm\s+-rf\s+[/~]/, "Recursive delete of root or home"],
@@ -67,7 +69,8 @@ const PROTECTED_PATHS: RegExp[] = [
67
69
  /\.gnupg\//,
68
70
  // Claude Code auto-memory — PAL owns memory; writes here indicate wrong system is being used
69
71
  /\.claude\/projects\/[^/]+\/memory\//,
70
- PAL_INSTALLED_DIRS_RE,
72
+ // PAL_INSTALLED_DIRS_RE is enforced by the dedicated branch in checkFilePath
73
+ // (which exempts personal skill dirs); keeping it here would re-block them.
71
74
  // Derived from HOOK_MANAGED_FILES — scoped to managed roots only
72
75
  ...HOOK_MANAGED_FILES.map(
73
76
  (name) =>
@@ -120,6 +123,22 @@ export function checkBashCommand(cmd: string): string | null {
120
123
  return null;
121
124
  }
122
125
 
126
+ /**
127
+ * Shipped skills are symlinks into the PAL repo (engine-managed); personal
128
+ * skills are real dirs authored in place. A not-yet-created skill dir is also
129
+ * personal (scaffolding). So: symlink → shipped/protected, otherwise → personal.
130
+ */
131
+ function isShippedSkillPath(normalized: string): boolean {
132
+ const m = /\.pal\/skills\/([^/]+)/.exec(normalized);
133
+ if (!m) return false;
134
+ const skillRoot = normalized.slice(0, m.index + m[0].length);
135
+ try {
136
+ return lstatSync(skillRoot).isSymbolicLink();
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
123
142
  /** Check a file path against protected patterns. Returns a reason string or null. */
124
143
  export function checkFilePath(filePath: string): string | null {
125
144
  const normalized = filePath.replaceAll("\\", "/");
@@ -141,7 +160,10 @@ export function checkFilePath(filePath: string): string | null {
141
160
  if (PAL_INSTALLED_DIRS_RE.test(normalized)) {
142
161
  const match = new RegExp(/\.pal[/\\](docs|skills|tools)/).exec(normalized);
143
162
  const dir = match ? match[1] : "docs/skills/tools";
144
- return `~/.pal/${dir}/ is managed by 'pal install' — edit the source in the PAL repo instead`;
163
+ const isPersonalSkill = dir === "skills" && !isShippedSkillPath(normalized);
164
+ if (!isPersonalSkill) {
165
+ return `~/.pal/${dir}/ is managed by 'pal install' — edit the source in the PAL repo instead`;
166
+ }
145
167
  }
146
168
  // Check remaining system-protected paths
147
169
  if (PROTECTED_PATHS.some((pattern) => pattern.test(filePath))) {
@@ -375,7 +375,14 @@ function cmdAddIsc(args: string[]): void {
375
375
  p.criteria = current ? `${current.trimEnd()}\n${newLine}` : newLine;
376
376
  p.updated = now();
377
377
  writeProject(p);
378
- ok({ added: true, id, title });
378
+ ok({
379
+ added: true,
380
+ id,
381
+ title,
382
+ announce: `🎟️ ISC #${id} — ${title}`,
383
+ reminder:
384
+ "Surface the `announce` line to the user verbatim, on its own line. Every ISC you open MUST be announced with the 🎟️ ticket marker, in any response mode — omitting it is a defect.",
385
+ });
379
386
  }
380
387
 
381
388
  function cmdCompleteIsc(args: string[]): void {