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.
- package/README.md +2 -1
- package/assets/skills/analyze-pdf/tools/pdf-download.ts +1 -1
- package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
- package/assets/skills/consulting-report/tools/dev.ts +2 -2
- package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
- package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
- package/assets/skills/opinion/tools/opinion.ts +3 -2
- package/assets/skills/presentation/SKILL.md +1 -1
- package/assets/skills/presentation/tools/doctor.ts +2 -5
- package/assets/skills/presentation/tools/lib/inline.ts +6 -11
- package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
- package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
- package/assets/skills/presentation/tools/setup-template.ts +10 -7
- package/assets/skills/projects/SKILL.md +44 -21
- package/assets/skills/research/tools/gemini-search.ts +2 -2
- package/assets/skills/research/tools/grok-search.ts +2 -2
- package/assets/skills/research/tools/perplexity-search.ts +2 -2
- package/assets/skills/telos/SKILL.md +7 -52
- package/assets/skills/telos/tools/update-telos.ts +0 -1
- package/assets/templates/PAL/ALGORITHM.md +54 -5
- 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/hooks.codex.json +44 -0
- package/assets/templates/hooks.cursor.json +11 -5
- package/assets/templates/pal-settings.json +1 -3
- package/assets/templates/settings.claude.json +2 -1
- package/package.json +2 -1
- package/src/cli/index.ts +112 -14
- package/src/cli/migrate.ts +299 -0
- package/src/cli/setup-identity.ts +3 -3
- package/src/cli/setup-telos.ts +12 -80
- package/src/hooks/CompactRecover.ts +11 -5
- package/src/hooks/LoadContext.ts +35 -11
- package/src/hooks/PreCompactPersist.ts +26 -34
- package/src/hooks/SecurityValidator.ts +43 -21
- package/src/hooks/StopOrchestrator.ts +4 -1
- package/src/hooks/UserPromptOrchestrator.ts +4 -2
- package/src/hooks/handlers/auto-graduate.ts +2 -2
- package/src/hooks/handlers/backup.ts +3 -3
- package/src/hooks/handlers/context-digests.ts +74 -0
- package/src/hooks/handlers/failure.ts +5 -3
- package/src/hooks/handlers/inject-retrieval.ts +29 -6
- package/src/hooks/handlers/persist-last-exchange.ts +76 -0
- package/src/hooks/handlers/rating.ts +2 -1
- package/src/hooks/handlers/readme-sync.ts +3 -2
- package/src/hooks/handlers/session-intelligence.ts +17 -93
- package/src/hooks/handlers/session-name.ts +2 -2
- package/src/hooks/handlers/synthesis.ts +5 -2
- package/src/hooks/handlers/update-counts.ts +3 -2
- package/src/hooks/lib/agent.ts +20 -18
- package/src/hooks/lib/claude-md.ts +69 -14
- package/src/hooks/lib/context.ts +92 -246
- package/src/hooks/lib/entities.ts +7 -7
- package/src/hooks/lib/frontmatter.ts +4 -4
- package/src/hooks/lib/graduation.ts +7 -6
- package/src/hooks/lib/inference.ts +6 -2
- package/src/hooks/lib/learning-category.ts +1 -1
- package/src/hooks/lib/learning-store.ts +6 -1
- package/src/hooks/lib/notify.ts +2 -2
- package/src/hooks/lib/opinions.ts +3 -3
- package/src/hooks/lib/paths.ts +2 -0
- package/src/hooks/lib/projects.ts +142 -74
- package/src/hooks/lib/readme-sync.ts +1 -1
- package/src/hooks/lib/relationship.ts +4 -16
- package/src/hooks/lib/retrieval-index.ts +5 -3
- package/src/hooks/lib/retrieval.ts +11 -12
- package/src/hooks/lib/security.ts +24 -18
- package/src/hooks/lib/semi-static.ts +188 -0
- package/src/hooks/lib/session-names.ts +1 -1
- package/src/hooks/lib/settings.ts +1 -1
- package/src/hooks/lib/setup.ts +2 -65
- package/src/hooks/lib/signals.ts +2 -2
- package/src/hooks/lib/stdin.ts +1 -1
- package/src/hooks/lib/stop.ts +16 -6
- package/src/hooks/lib/token-usage.ts +1 -2
- package/src/hooks/lib/transcript.ts +1 -1
- package/src/hooks/lib/wisdom.ts +5 -5
- package/src/hooks/lib/work-tracking.ts +8 -14
- package/src/targets/claude/uninstall.ts +1 -1
- package/src/targets/codex/install.ts +95 -0
- package/src/targets/codex/uninstall.ts +70 -0
- 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 +166 -14
- package/src/targets/opencode/install.ts +29 -1
- package/src/targets/opencode/plugin.ts +23 -12
- package/src/targets/opencode/uninstall.ts +30 -3
- package/src/tools/agent/algorithm-reflect.ts +1 -1
- package/src/tools/agent/analyze.ts +18 -18
- package/src/tools/agent/handoff-note.ts +116 -0
- package/src/tools/agent/project.ts +375 -75
- package/src/tools/agent/relationship-note.ts +51 -0
- package/src/tools/agent/synthesize.ts +6 -42
- package/src/tools/agent/thread.ts +15 -14
- package/src/tools/agent/wisdom-frame.ts +9 -3
- package/src/tools/import.ts +1 -1
- package/src/tools/relationship-reflect.ts +15 -13
- package/src/tools/self-model.ts +23 -19
- package/src/tools/session-summary.ts +3 -3
- package/src/tools/token-cost.ts +15 -16
- package/assets/skills/telos/tools/update-projects.ts +0 -106
- 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
|
|
3
|
+
* Project — register and manage user projects via ISA.md files.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
141
|
+
// ── append/remove for array fields ───────────────────────────────
|
|
141
142
|
|
|
142
|
-
function appendItem(
|
|
143
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
p.decisions =
|
|
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
|
|
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
|
|
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
|
|
213
|
-
resume <name> print full project
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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();
|