portable-agent-layer 0.35.0 → 0.37.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 (108) hide show
  1. package/README.md +2 -1
  2. package/assets/skills/analyze-pdf/tools/pdf-download.ts +1 -1
  3. package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
  4. package/assets/skills/consulting-report/tools/dev.ts +2 -2
  5. package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
  6. package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
  7. package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
  8. package/assets/skills/opinion/tools/opinion.ts +3 -2
  9. package/assets/skills/presentation/SKILL.md +1 -1
  10. package/assets/skills/presentation/tools/doctor.ts +2 -5
  11. package/assets/skills/presentation/tools/lib/inline.ts +6 -11
  12. package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
  13. package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
  14. package/assets/skills/presentation/tools/setup-template.ts +10 -7
  15. package/assets/skills/projects/SKILL.md +44 -21
  16. package/assets/skills/research/tools/gemini-search.ts +2 -2
  17. package/assets/skills/research/tools/grok-search.ts +2 -2
  18. package/assets/skills/research/tools/perplexity-search.ts +2 -2
  19. package/assets/skills/telos/SKILL.md +7 -52
  20. package/assets/skills/telos/tools/update-telos.ts +0 -1
  21. package/assets/templates/PAL/ALGORITHM.md +54 -5
  22. package/assets/templates/PAL/PROJECT_LIFECYCLE.md +48 -0
  23. package/assets/templates/PAL/README.md +1 -1
  24. package/assets/templates/PAL/STEERING_RULES.md +4 -0
  25. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +32 -17
  26. package/assets/templates/PAL/WORK_TRACKING.md +1 -1
  27. package/assets/templates/hooks.codex.json +44 -0
  28. package/assets/templates/hooks.cursor.json +11 -5
  29. package/assets/templates/pal-settings.json +1 -3
  30. package/assets/templates/settings.claude.json +2 -1
  31. package/package.json +2 -1
  32. package/src/cli/index.ts +112 -14
  33. package/src/cli/migrate.ts +299 -0
  34. package/src/cli/setup-identity.ts +3 -3
  35. package/src/cli/setup-telos.ts +12 -80
  36. package/src/hooks/CompactRecover.ts +11 -5
  37. package/src/hooks/LoadContext.ts +35 -11
  38. package/src/hooks/PreCompactPersist.ts +26 -34
  39. package/src/hooks/SecurityValidator.ts +43 -21
  40. package/src/hooks/StopOrchestrator.ts +4 -1
  41. package/src/hooks/UserPromptOrchestrator.ts +4 -2
  42. package/src/hooks/handlers/auto-graduate.ts +2 -2
  43. package/src/hooks/handlers/backup.ts +3 -3
  44. package/src/hooks/handlers/context-digests.ts +74 -0
  45. package/src/hooks/handlers/failure.ts +5 -3
  46. package/src/hooks/handlers/inject-retrieval.ts +29 -6
  47. package/src/hooks/handlers/persist-last-exchange.ts +76 -0
  48. package/src/hooks/handlers/rating.ts +2 -1
  49. package/src/hooks/handlers/readme-sync.ts +3 -2
  50. package/src/hooks/handlers/session-intelligence.ts +17 -93
  51. package/src/hooks/handlers/session-name.ts +2 -2
  52. package/src/hooks/handlers/synthesis.ts +5 -2
  53. package/src/hooks/handlers/update-counts.ts +3 -2
  54. package/src/hooks/lib/agent.ts +20 -18
  55. package/src/hooks/lib/claude-md.ts +69 -14
  56. package/src/hooks/lib/context.ts +92 -246
  57. package/src/hooks/lib/entities.ts +7 -7
  58. package/src/hooks/lib/frontmatter.ts +4 -4
  59. package/src/hooks/lib/graduation.ts +7 -6
  60. package/src/hooks/lib/inference.ts +6 -2
  61. package/src/hooks/lib/learning-category.ts +1 -1
  62. package/src/hooks/lib/learning-store.ts +6 -1
  63. package/src/hooks/lib/notify.ts +2 -2
  64. package/src/hooks/lib/opinions.ts +3 -3
  65. package/src/hooks/lib/paths.ts +2 -0
  66. package/src/hooks/lib/projects.ts +142 -74
  67. package/src/hooks/lib/readme-sync.ts +1 -1
  68. package/src/hooks/lib/relationship.ts +4 -16
  69. package/src/hooks/lib/retrieval-index.ts +5 -3
  70. package/src/hooks/lib/retrieval.ts +11 -12
  71. package/src/hooks/lib/security.ts +24 -18
  72. package/src/hooks/lib/semi-static.ts +188 -0
  73. package/src/hooks/lib/session-names.ts +1 -1
  74. package/src/hooks/lib/settings.ts +1 -1
  75. package/src/hooks/lib/setup.ts +2 -65
  76. package/src/hooks/lib/signals.ts +2 -2
  77. package/src/hooks/lib/stdin.ts +1 -1
  78. package/src/hooks/lib/stop.ts +16 -6
  79. package/src/hooks/lib/token-usage.ts +1 -2
  80. package/src/hooks/lib/transcript.ts +1 -1
  81. package/src/hooks/lib/wisdom.ts +5 -5
  82. package/src/hooks/lib/work-tracking.ts +8 -14
  83. package/src/targets/claude/uninstall.ts +1 -1
  84. package/src/targets/codex/install.ts +95 -0
  85. package/src/targets/codex/uninstall.ts +70 -0
  86. package/src/targets/copilot/install.ts +39 -8
  87. package/src/targets/copilot/uninstall.ts +58 -17
  88. package/src/targets/cursor/install.ts +8 -0
  89. package/src/targets/cursor/uninstall.ts +18 -1
  90. package/src/targets/lib.ts +166 -14
  91. package/src/targets/opencode/install.ts +29 -1
  92. package/src/targets/opencode/plugin.ts +23 -12
  93. package/src/targets/opencode/uninstall.ts +30 -3
  94. package/src/tools/agent/algorithm-reflect.ts +1 -1
  95. package/src/tools/agent/analyze.ts +18 -18
  96. package/src/tools/agent/handoff-note.ts +116 -0
  97. package/src/tools/agent/project.ts +375 -75
  98. package/src/tools/agent/relationship-note.ts +51 -0
  99. package/src/tools/agent/synthesize.ts +6 -42
  100. package/src/tools/agent/thread.ts +15 -14
  101. package/src/tools/agent/wisdom-frame.ts +9 -3
  102. package/src/tools/import.ts +1 -1
  103. package/src/tools/relationship-reflect.ts +15 -13
  104. package/src/tools/self-model.ts +23 -19
  105. package/src/tools/session-summary.ts +3 -3
  106. package/src/tools/token-cost.ts +15 -16
  107. package/assets/skills/telos/tools/update-projects.ts +0 -106
  108. package/assets/templates/telos/PROJECTS.md +0 -7
@@ -1,30 +1,30 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Project — register and manage user projects via per-project progress JSONs.
3
+ * Project — register and manage user projects via ISA.md files.
4
4
  *
5
- * Replaces the hand-edited `~/.pal/telos/PROJECTS.md`. The AI is the primary
6
- * caller (proactive registration on unregistered cwds, append-as-you-go updates);
7
- * the user is the escape hatch for fine-grained control.
8
- *
9
- * State: `~/.pal/memory/state/progress/{slug}.json`. Auto-touched on Stop hook
10
- * when cwd resolves into a registered project.
5
+ * Each project is stored at `~/.pal/memory/projects/{slug}/ISA.md`.
6
+ * Frontmatter holds operational state; body holds ISA spec sections.
11
7
  *
12
8
  * Usage:
13
9
  * bun ~/.pal/tools/project.ts list
14
10
  * bun ~/.pal/tools/project.ts create [name] [--path PATH] [--objectives "..."]
15
11
  * bun ~/.pal/tools/project.ts resume <name>
16
12
  * bun ~/.pal/tools/project.ts complete | archive | pause | unpause <name>
17
- * bun ~/.pal/tools/project.ts add-fact <name> "text"
18
- * bun ~/.pal/tools/project.ts add-objective <name> "text"
19
13
  * bun ~/.pal/tools/project.ts add-next <name> "text"
20
14
  * bun ~/.pal/tools/project.ts add-blocker <name> "text"
21
15
  * bun ~/.pal/tools/project.ts add-decision <name> "decision" "rationale"
22
16
  * bun ~/.pal/tools/project.ts add-handoff <name> "text"
23
- * bun ~/.pal/tools/project.ts rm-fact | rm-objective | rm-next | rm-blocker <name> <index>
17
+ * bun ~/.pal/tools/project.ts rm-next | rm-blocker <name> <index>
18
+ * bun ~/.pal/tools/project.ts update-section <name> <section> "content"
19
+ * bun ~/.pal/tools/project.ts criteria <name>
20
+ * bun ~/.pal/tools/project.ts isa-init <name>
21
+ * bun ~/.pal/tools/project.ts migrate
24
22
  */
25
23
 
24
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
26
25
  import { resolve } from "node:path";
27
26
  import { parseArgs } from "node:util";
27
+ import { paths } from "../../hooks/lib/paths";
28
28
  import {
29
29
  defaultSlug,
30
30
  deleteProject,
@@ -65,8 +65,7 @@ function cmdList(): void {
65
65
  path: p.path,
66
66
  updated: p.updated,
67
67
  stale: isStale(p),
68
- objectives: p.objectives?.length ?? 0,
69
- next_steps: p.next_steps?.length ?? 0,
68
+ next: p.next?.length ?? 0,
70
69
  blockers: p.blockers?.length ?? 0,
71
70
  }));
72
71
  ok({ count: all.length, projects: rows });
@@ -100,11 +99,13 @@ function cmdCreate(args: string[]): void {
100
99
  );
101
100
  }
102
101
 
103
- const objectives = values.objectives
102
+ const goalLines = values.objectives
104
103
  ? values.objectives
105
104
  .split(/[\n;|]/)
106
105
  .map((s) => s.trim())
107
106
  .filter(Boolean)
107
+ .map((s) => `- ${s}`)
108
+ .join("\n")
108
109
  : undefined;
109
110
 
110
111
  const project: ProjectProgress = {
@@ -113,7 +114,7 @@ function cmdCreate(args: string[]): void {
113
114
  status: "active",
114
115
  created: now(),
115
116
  updated: now(),
116
- objectives,
117
+ ...(goalLines ? { goal: goalLines } : {}),
117
118
  };
118
119
  writeProject(project);
119
120
  ok({ created: true, project });
@@ -137,14 +138,10 @@ function setStatus(name: string, status: ProjectStatus): void {
137
138
  ok({ updated: true, name, status });
138
139
  }
139
140
 
140
- // ── append helpers ────────────────────────────────────────────────
141
+ // ── append/remove for array fields ───────────────────────────────
141
142
 
142
- function appendItem(
143
- name: string,
144
- field: "facts" | "objectives" | "next_steps" | "blockers",
145
- text: string
146
- ): void {
147
- if (!text?.trim()) fail(`Empty ${field.replace("_", " ")} text.`);
143
+ function appendItem(name: string, field: "next" | "blockers", text: string): void {
144
+ if (!text?.trim()) fail(`Empty ${field} text.`);
148
145
  const p = requireProject(name);
149
146
  const list = p[field] ?? [];
150
147
  list.push(text.trim());
@@ -154,11 +151,7 @@ function appendItem(
154
151
  ok({ updated: true, name, field, count: list.length });
155
152
  }
156
153
 
157
- function removeItem(
158
- name: string,
159
- field: "facts" | "objectives" | "next_steps" | "blockers",
160
- indexArg: string
161
- ): void {
154
+ function removeItem(name: string, field: "next" | "blockers", indexArg: string): void {
162
155
  const idx = parseInt(indexArg, 10);
163
156
  if (!Number.isInteger(idx) || idx < 0) fail(`Invalid index "${indexArg}".`);
164
157
  const p = requireProject(name);
@@ -171,18 +164,22 @@ function removeItem(
171
164
  ok({ updated: true, name, field, removed, count: list.length });
172
165
  }
173
166
 
167
+ // ── decisions (body section append) ──────────────────────────────
168
+
174
169
  function addDecision(name: string, decision: string, rationale: string): void {
175
170
  if (!decision?.trim() || !rationale?.trim())
176
171
  fail("Usage: add-decision <name> <decision> <rationale>");
177
172
  const p = requireProject(name);
178
- const list = p.decisions ?? [];
179
- list.push({ ts: now(), decision: decision.trim(), rationale: rationale.trim() });
180
- p.decisions = list;
173
+ const date = new Date().toISOString().slice(0, 10);
174
+ const line = `- ${date}: ${decision.trim()} (${rationale.trim()})`;
175
+ p.decisions = p.decisions ? `${p.decisions}\n${line}` : line;
181
176
  p.updated = now();
182
177
  writeProject(p);
183
- ok({ updated: true, name, count: list.length });
178
+ ok({ updated: true, name });
184
179
  }
185
180
 
181
+ // ── handoff ───────────────────────────────────────────────────────
182
+
186
183
  function addHandoff(name: string, text: string): void {
187
184
  if (!text?.trim()) fail("Empty handoff text.");
188
185
  const p = requireProject(name);
@@ -192,43 +189,348 @@ function addHandoff(name: string, text: string): void {
192
189
  ok({ updated: true, name });
193
190
  }
194
191
 
192
+ // ── set-path ──────────────────────────────────────────────────────
193
+
194
+ function cmdSetPath(args: string[]): void {
195
+ const [name, ...rest] = args;
196
+ if (!name || rest.length === 0) fail("Usage: set-path <name> <new-path>");
197
+ const newPath = resolve(rest.join(" ").trim());
198
+ const p = requireProject(name);
199
+ p.path = newPath;
200
+ p.updated = now();
201
+ writeProject(p);
202
+ ok({ updated: true, name, path: newPath });
203
+ }
204
+
205
+ // ── update-section ────────────────────────────────────────────────
206
+
207
+ const VALID_SECTIONS = [
208
+ "problem",
209
+ "goal",
210
+ "criteria",
211
+ "vision",
212
+ "constraints",
213
+ "out_of_scope",
214
+ "context",
215
+ "decisions",
216
+ "changelog",
217
+ ] as const;
218
+ type Section = (typeof VALID_SECTIONS)[number];
219
+
220
+ function cmdUpdateSection(args: string[]): void {
221
+ const [name, section, ...rest] = args;
222
+ if (!name || !section) fail("Usage: update-section <name> <section> <content>");
223
+ const key = section.toLowerCase().replace(/\s+/g, "_") as Section;
224
+ if (!(VALID_SECTIONS as readonly string[]).includes(key)) {
225
+ fail(`Unknown section "${section}". Valid: ${VALID_SECTIONS.join(", ")}`);
226
+ }
227
+ const content = rest.join(" ").trim();
228
+ if (!content) fail("Empty content.");
229
+ const p = requireProject(name);
230
+ (p as unknown as Record<string, unknown>)[key] = content;
231
+ p.updated = now();
232
+ writeProject(p);
233
+ ok({ updated: true, name, section: key });
234
+ }
235
+
236
+ // ── criteria ──────────────────────────────────────────────────────
237
+
238
+ function cmdCriteria(args: string[]): void {
239
+ const name = args[0];
240
+ if (!name) fail("Usage: criteria <name>");
241
+ const p = requireProject(name);
242
+ ok({ name, criteria: p.criteria ?? "" });
243
+ }
244
+
245
+ // ── isa-init ──────────────────────────────────────────────────────
246
+
247
+ function cmdIsaInit(args: string[]): void {
248
+ const name = args[0];
249
+ if (!name) fail("Usage: isa-init <name>");
250
+ const p = requireProject(name);
251
+ const sections: Array<keyof ProjectProgress> = [
252
+ "problem",
253
+ "goal",
254
+ "criteria",
255
+ "vision",
256
+ "constraints",
257
+ "out_of_scope",
258
+ "context",
259
+ ];
260
+ let scaffolded = 0;
261
+ const pr = p as unknown as Record<string, unknown>;
262
+ for (const s of sections) {
263
+ if (!pr[s as string]) {
264
+ pr[s as string] = "";
265
+ scaffolded++;
266
+ }
267
+ }
268
+ // Remove empty strings so they don't clutter the ISA body
269
+ for (const s of sections) {
270
+ if (pr[s as string] === "") pr[s as string] = undefined;
271
+ }
272
+ p.updated = now();
273
+ writeProject(p);
274
+ ok({ initialized: true, name, scaffolded });
275
+ }
276
+
277
+ // ── migrate (from old JSON format) ───────────────────────────────
278
+
279
+ interface LegacyDecision {
280
+ ts: string;
281
+ decision: string;
282
+ rationale: string;
283
+ }
284
+
285
+ interface LegacyProject {
286
+ name: string;
287
+ path: string;
288
+ status: ProjectStatus;
289
+ created: string;
290
+ updated: string;
291
+ facts?: string[];
292
+ objectives?: string[];
293
+ next_steps?: string[];
294
+ blockers?: string[];
295
+ handoff?: string;
296
+ decisions?: LegacyDecision[];
297
+ }
298
+
299
+ function cmdMigrate(): void {
300
+ const progressDir = paths.progress();
301
+ if (!existsSync(progressDir)) {
302
+ ok({ migrated: 0, skipped: 0, results: [] });
303
+ return;
304
+ }
305
+
306
+ const files = readdirSync(progressDir).filter((f) => f.endsWith(".json"));
307
+ if (files.length === 0) {
308
+ ok({ migrated: 0, skipped: 0, results: [] });
309
+ return;
310
+ }
311
+
312
+ let migrated = 0;
313
+ let skipped = 0;
314
+ const results: string[] = [];
315
+
316
+ for (const file of files) {
317
+ const slug = file.slice(0, -5);
318
+ const filePath = resolve(progressDir, file);
319
+
320
+ if (readProject(slug)) {
321
+ skipped++;
322
+ results.push(`${slug}: skipped (ISA.md already exists)`);
323
+ continue;
324
+ }
325
+
326
+ try {
327
+ const raw = JSON.parse(readFileSync(filePath, "utf-8")) as LegacyProject;
328
+ if (!raw?.name || !raw?.path || !raw?.status) {
329
+ skipped++;
330
+ results.push(`${slug}: skipped (malformed JSON)`);
331
+ continue;
332
+ }
333
+
334
+ const p: ProjectProgress = {
335
+ name: raw.name,
336
+ path: raw.path,
337
+ status: raw.status,
338
+ created: raw.created,
339
+ updated: raw.updated,
340
+ ...(raw.handoff ? { handoff: raw.handoff } : {}),
341
+ ...(raw.next_steps?.length ? { next: raw.next_steps } : {}),
342
+ ...(raw.blockers?.length ? { blockers: raw.blockers } : {}),
343
+ };
344
+
345
+ if (raw.facts?.length) p.context = raw.facts.join("\n");
346
+ if (raw.objectives?.length) p.goal = raw.objectives.map((o) => `- ${o}`).join("\n");
347
+ if (raw.decisions?.length) {
348
+ p.decisions = raw.decisions
349
+ .map((d) => `- ${d.ts.slice(0, 10)}: ${d.decision} (${d.rationale})`)
350
+ .join("\n");
351
+ }
352
+
353
+ writeProject(p);
354
+ migrated++;
355
+ results.push(`${slug}: migrated`);
356
+ } catch {
357
+ skipped++;
358
+ results.push(`${slug}: skipped (read/write error)`);
359
+ }
360
+ }
361
+
362
+ ok({ migrated, skipped, results });
363
+ }
364
+
195
365
  // ── rm (project) ──────────────────────────────────────────────────
196
366
 
197
367
  function cmdRm(args: string[]): void {
198
368
  const name = args[0];
199
- if (!name) fail("Usage: rm <name> (deletes the entire project state file)");
369
+ if (!name) fail("Usage: rm <name> (deletes the entire project directory)");
200
370
  const removed = deleteProject(name);
201
371
  if (!removed) fail(`No project named "${name}".`);
202
372
  ok({ deleted: true, name });
203
373
  }
204
374
 
375
+ // ── ISC helpers ──────────────────────────────────────────────────
376
+
377
+ interface Isc {
378
+ id: number;
379
+ text: string;
380
+ checked: boolean;
381
+ }
382
+
383
+ function parseIscs(criteria: string): Isc[] {
384
+ const out: Isc[] = [];
385
+ for (const line of criteria.split("\n")) {
386
+ const m = new RegExp(/^-\s+\[( |x)\]\s+ISC-(\d+):\s+(.+)$/i).exec(line);
387
+ if (m) out.push({ id: Number(m[2]), text: m[3].trim(), checked: m[1] === "x" });
388
+ }
389
+ return out;
390
+ }
391
+
392
+ function nextIscId(criteria: string): number {
393
+ const ids = parseIscs(criteria).map((i) => i.id);
394
+ return ids.length > 0 ? Math.max(...ids) + 1 : 1;
395
+ }
396
+
397
+ function patchIsc(criteria: string, id: number, checked: boolean): string {
398
+ const marker = checked ? "[x]" : "[ ]";
399
+ return criteria.replace(
400
+ new RegExp(String.raw`^(-\s+)\[[ x]\](\s+ISC-${id}:)`, "m"),
401
+ `$1${marker}$2`
402
+ );
403
+ }
404
+
405
+ function cmdAddIsc(args: string[]): void {
406
+ const name = args[0] ?? fail("Usage: add-isc <name> <title>");
407
+ const title = args.slice(1).join(" ").trim();
408
+ if (!title) fail("Usage: add-isc <name> <title>");
409
+ const p = requireProject(name);
410
+ const current = p.criteria ?? "";
411
+ const id = nextIscId(current);
412
+ const newLine = `- [ ] ISC-${id}: ${title}`;
413
+ p.criteria = current ? `${current.trimEnd()}\n${newLine}` : newLine;
414
+ p.updated = now();
415
+ writeProject(p);
416
+ ok({ added: true, id, title });
417
+ }
418
+
419
+ function cmdCheckIsc(args: string[]): void {
420
+ const name = args[0] ?? fail("Usage: check-isc <name> <id>");
421
+ const id = Number(args[1] ?? fail("Usage: check-isc <name> <id>"));
422
+ if (!Number.isInteger(id) || id < 1) fail("ISC id must be a positive integer");
423
+ const p = requireProject(name);
424
+ const current = p.criteria ?? "";
425
+ const existing = parseIscs(current).find((i) => i.id === id);
426
+ if (!existing) fail(`ISC-${id} not found in project "${name}"`);
427
+ if (existing.checked) {
428
+ ok({ checked: true, id, alreadyDone: true });
429
+ return;
430
+ }
431
+ p.criteria = patchIsc(current, id, true);
432
+ p.updated = now();
433
+ writeProject(p);
434
+ ok({ checked: true, id });
435
+ }
436
+
437
+ function cmdListIsc(args: string[]): void {
438
+ const name = args[0] ?? fail("Usage: list-isc <name>");
439
+ const p = requireProject(name);
440
+ const iscs = parseIscs(p.criteria ?? "");
441
+ const open = iscs.filter((i) => !i.checked);
442
+ const done = iscs.filter((i) => i.checked);
443
+ ok({ name, total: iscs.length, open: open.length, done: done.length, iscs });
444
+ }
445
+
446
+ // ── Task ISA (work/) ──────────────────────────────────────────────
447
+
448
+ function taskSlug(title: string): string {
449
+ const sanitized = title
450
+ .toLowerCase()
451
+ .replace(/[^a-z0-9]+/g, "-")
452
+ .replace(/^-+|-+$/g, "")
453
+ .slice(0, 40);
454
+ return `${sanitized}-${Date.now().toString(36)}`;
455
+ }
456
+
457
+ function taskIsaPath(slug: string): string {
458
+ const dir = resolve(paths.work(), slug);
459
+ mkdirSync(dir, { recursive: true });
460
+ return resolve(dir, "ISA.md");
461
+ }
462
+
463
+ function cmdScaffoldTaskIsa(args: string[]): void {
464
+ const title = args.join(" ").trim();
465
+ if (!title) fail("Usage: scaffold-task-isa <title>");
466
+ const slug = taskSlug(title);
467
+ const ts = new Date().toISOString();
468
+ const content = [
469
+ "---",
470
+ `task: "${title}"`,
471
+ `slug: "${slug}"`,
472
+ "phase: active",
473
+ `started: "${ts}"`,
474
+ `updated: "${ts}"`,
475
+ "---",
476
+ "",
477
+ "## Goal",
478
+ "",
479
+ "",
480
+ "## Criteria",
481
+ "",
482
+ "",
483
+ ].join("\n");
484
+ const filePath = taskIsaPath(slug);
485
+ writeFileSync(filePath, content, "utf-8");
486
+ ok({ created: true, slug, path: filePath });
487
+ }
488
+
489
+ function cmdCompleteTaskIsa(args: string[]): void {
490
+ const slug = args[0] ?? fail("Usage: complete-task-isa <slug>");
491
+ const filePath = resolve(paths.work(), slug, "ISA.md");
492
+ if (!existsSync(filePath)) fail(`Task ISA not found: ${slug}`);
493
+ const content = readFileSync(filePath, "utf-8");
494
+ const updated = content
495
+ .replace(/^phase: .+$/m, "phase: complete")
496
+ .replace(/^updated: .+$/m, `updated: "${new Date().toISOString()}"`);
497
+ writeFileSync(filePath, updated, "utf-8");
498
+ ok({ completed: true, slug });
499
+ }
500
+
205
501
  // ── dispatch ──────────────────────────────────────────────────────
206
502
 
207
503
  function help(): void {
208
- console.log(`Project — manage PAL project state.
504
+ console.log(`Project — manage PAL project state (ISA.md backed).
209
505
 
210
506
  Commands:
211
507
  list show all registered projects
212
- create [name] [--path PATH] [--objectives X] register a project (defaults: name=basename(cwd), path=cwd)
213
- resume <name> print full project JSON
508
+ create [name] [--path PATH] [--objectives X] register a project
509
+ resume <name> print full project ISA
214
510
  complete <name> mark complete
215
511
  archive <name> mark archived
216
512
  pause <name> | unpause <name> toggle paused/active
217
- add-fact <name> "text" append a stable fact / reference
218
- add-objective <name> "text" append objective
513
+ set-path <name> <new-path> update the registered path
219
514
  add-next <name> "text" append next step
220
515
  add-blocker <name> "text" append blocker
221
- add-decision <name> "decision" "rationale" log a decision
516
+ add-decision <name> "decision" "rationale" log a dated decision entry
222
517
  add-handoff <name> "text" overwrite handoff field
223
- rm-fact <name> <index> remove fact by index
224
- rm-objective <name> <index> remove objective by index
225
518
  rm-next <name> <index> remove next step by index
226
519
  rm-blocker <name> <index> remove blocker by index
227
- rm <name> delete the entire project file
520
+ update-section <name> <section> "content" set an ISA body section
521
+ criteria <name> print the Criteria section
522
+ add-isc <name> "title" append a new open ISC to Criteria
523
+ check-isc <name> <id> mark ISC-N as done
524
+ list-isc <name> list all ISCs with open/done status
525
+ isa-init <name> mark project as ISA-initialized
526
+ scaffold-task-isa <title> create a one-shot task ISA in memory/work/
527
+ complete-task-isa <slug> mark a task ISA as complete
528
+ migrate migrate old JSON progress files → ISA.md
529
+ rm <name> delete the entire project
228
530
  `);
229
531
  }
230
532
 
231
- export function run(): void {
533
+ function run(): void {
232
534
  const [cmd, ...rest] = Bun.argv.slice(2);
233
535
  if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
234
536
  help();
@@ -256,24 +558,10 @@ export function run(): void {
256
558
  case "unpause":
257
559
  setStatus(rest[0] ?? fail("Usage: unpause <name>"), "active");
258
560
  return;
259
- case "add-fact":
260
- appendItem(
261
- rest[0] ?? fail("Usage: add-fact <name> <text>"),
262
- "facts",
263
- rest.slice(1).join(" ")
264
- );
265
- return;
266
- case "add-objective":
267
- appendItem(
268
- rest[0] ?? fail("Usage: add-objective <name> <text>"),
269
- "objectives",
270
- rest.slice(1).join(" ")
271
- );
272
- return;
273
561
  case "add-next":
274
562
  appendItem(
275
563
  rest[0] ?? fail("Usage: add-next <name> <text>"),
276
- "next_steps",
564
+ "next",
277
565
  rest.slice(1).join(" ")
278
566
  );
279
567
  return;
@@ -297,26 +585,8 @@ export function run(): void {
297
585
  rest.slice(1).join(" ")
298
586
  );
299
587
  return;
300
- case "rm-fact":
301
- removeItem(
302
- rest[0] ?? fail("Usage: rm-fact <name> <index>"),
303
- "facts",
304
- rest[1] ?? ""
305
- );
306
- return;
307
- case "rm-objective":
308
- removeItem(
309
- rest[0] ?? fail("Usage: rm-objective <name> <index>"),
310
- "objectives",
311
- rest[1] ?? ""
312
- );
313
- return;
314
588
  case "rm-next":
315
- removeItem(
316
- rest[0] ?? fail("Usage: rm-next <name> <index>"),
317
- "next_steps",
318
- rest[1] ?? ""
319
- );
589
+ removeItem(rest[0] ?? fail("Usage: rm-next <name> <index>"), "next", rest[1] ?? "");
320
590
  return;
321
591
  case "rm-blocker":
322
592
  removeItem(
@@ -325,6 +595,36 @@ export function run(): void {
325
595
  rest[1] ?? ""
326
596
  );
327
597
  return;
598
+ case "update-section":
599
+ cmdUpdateSection(rest);
600
+ return;
601
+ case "criteria":
602
+ cmdCriteria(rest);
603
+ return;
604
+ case "add-isc":
605
+ cmdAddIsc(rest);
606
+ return;
607
+ case "check-isc":
608
+ cmdCheckIsc(rest);
609
+ return;
610
+ case "list-isc":
611
+ cmdListIsc(rest);
612
+ return;
613
+ case "isa-init":
614
+ cmdIsaInit(rest);
615
+ return;
616
+ case "scaffold-task-isa":
617
+ cmdScaffoldTaskIsa(rest);
618
+ return;
619
+ case "complete-task-isa":
620
+ cmdCompleteTaskIsa(rest);
621
+ return;
622
+ case "migrate":
623
+ cmdMigrate();
624
+ return;
625
+ case "set-path":
626
+ cmdSetPath(rest);
627
+ return;
328
628
  case "rm":
329
629
  cmdRm(rest);
330
630
  return;
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * RelationshipNote — Write a B entry to today's relationship log.
4
+ *
5
+ * Called in the ALGORITHM LEARN phase. Claude writes the B entry directly
6
+ * from full session context — no inference call needed.
7
+ *
8
+ * Usage:
9
+ * bun ~/.pal/tools/relationship-note.ts --b "what I did this session"
10
+ */
11
+
12
+ import { parseArgs } from "node:util";
13
+ import { appendNotes } from "../../hooks/lib/relationship";
14
+
15
+ function run() {
16
+ const { values } = parseArgs({
17
+ args: Bun.argv.slice(2),
18
+ options: {
19
+ b: { type: "string" },
20
+ help: { type: "boolean", short: "h" },
21
+ },
22
+ });
23
+
24
+ if (values.help) {
25
+ console.log(`
26
+ RelationshipNote — Append a B entry to today's relationship log
27
+
28
+ Usage:
29
+ bun ~/.pal/tools/relationship-note.ts --b "description"
30
+
31
+ Arguments:
32
+ --b What happened this session (1-2 sentences, first-person, specific)
33
+
34
+ Output: appends to memory/relationship/YYYY-MM/YYYY-MM-DD.md
35
+ `);
36
+ process.exit(0);
37
+ }
38
+
39
+ if (!values.b) {
40
+ console.error("Required: --b");
41
+ process.exit(1);
42
+ }
43
+
44
+ appendNotes([{ type: "Session", text: values.b }]);
45
+
46
+ console.log(
47
+ JSON.stringify({ success: true, message: "Relationship note written" }, null, 2)
48
+ );
49
+ }
50
+
51
+ if (import.meta.main) run();