portable-agent-layer 0.88.1 → 0.89.1
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 +8 -5
- package/assets/agents/gemini-researcher.md +13 -0
- package/assets/agents/grok-researcher.md +13 -0
- package/assets/agents/multi-perspective-researcher.md +12 -0
- package/assets/agents/perplexity-researcher.md +13 -0
- package/assets/agents/skill-author.md +17 -0
- package/assets/agents/subagent-author.md +18 -1
- package/assets/skills/create-subagent/SKILL.md +3 -2
- package/assets/skills/create-subagent/authoring-guide.md +16 -0
- package/assets/templates/PAL/MEMORY_SYSTEM.md +1 -1
- package/assets/templates/PAL/README.md +6 -5
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -1
- package/assets/templates/hooks.antigravity.json +42 -0
- package/assets/templates/settings.antigravity.json +44 -0
- package/package.json +1 -1
- package/src/cli/builtin-tools.ts +28 -23
- package/src/cli/doctor/agents.ts +64 -64
- package/src/cli/identity.ts +26 -20
- package/src/cli/index.ts +109 -312
- package/src/cli/knowledge.ts +160 -201
- package/src/cli/ledger-command.ts +69 -0
- package/src/cli/ledger.ts +20 -101
- package/src/cli/personal-context.ts +15 -12
- package/src/cli/rule.ts +29 -29
- package/src/cli/server.ts +33 -52
- package/src/cli/session-agent.ts +7 -9
- package/src/cli/skill.ts +73 -64
- package/src/cli/subagent.ts +51 -58
- package/src/cli/tree.ts +177 -0
- package/src/hooks/InvocationContext.ts +26 -0
- package/src/hooks/LedgerCommit.ts +4 -2
- package/src/hooks/LedgerSnapshot.ts +2 -0
- package/src/hooks/SecurityValidator.ts +2 -0
- package/src/hooks/StopOrchestrator.ts +12 -2
- package/src/hooks/UserPromptOrchestrator.ts +3 -15
- package/src/hooks/handlers/context-digests.ts +21 -21
- package/src/hooks/handlers/readme-sync.ts +6 -2
- package/src/hooks/lib/agent-definition.ts +6 -16
- package/src/hooks/lib/agent-registry.ts +88 -0
- package/src/hooks/lib/agent.ts +47 -23
- package/src/hooks/lib/antigravity-inference.ts +84 -0
- package/src/hooks/lib/antigravity-transcript.ts +129 -0
- package/src/hooks/lib/claim-check.ts +13 -2
- package/src/hooks/lib/claim-log.ts +5 -1
- package/src/hooks/lib/claude-md.ts +21 -1
- package/src/hooks/lib/context.ts +5 -1
- package/src/hooks/lib/export-manifest.ts +23 -0
- package/src/hooks/lib/export.ts +1 -24
- package/src/hooks/lib/hook-turn.ts +25 -2
- package/src/hooks/lib/import-merge.ts +1 -1
- package/src/hooks/lib/inference.ts +77 -1
- package/src/hooks/lib/invocation-context.ts +99 -0
- package/src/hooks/lib/ledger-hook.ts +14 -6
- package/src/hooks/lib/models.ts +34 -17
- package/src/hooks/lib/paths.ts +39 -6
- package/src/hooks/lib/prompt-capture.ts +28 -0
- package/src/hooks/lib/readme-sync.ts +3 -31
- package/src/hooks/lib/security-gate.ts +10 -3
- package/src/hooks/lib/semi-static.ts +13 -3
- package/src/hooks/lib/session-context.ts +3 -1
- package/src/hooks/lib/stop.ts +1 -0
- package/src/hooks/lib/transcript.ts +6 -3
- package/src/targets/agent-render.ts +3 -3
- package/src/targets/antigravity/install.ts +68 -0
- package/src/targets/antigravity/uninstall.ts +66 -0
- package/src/targets/lib.ts +81 -64
- package/src/targets/opencode/install.ts +2 -0
- package/src/targets/opencode/permission.ts +39 -0
- package/src/targets/opencode/uninstall.ts +9 -8
- package/src/tools/agent/algorithm-reflect.ts +70 -62
- package/src/tools/agent/algorithm-synthesize.ts +39 -14
- package/src/tools/agent/analyze.ts +31 -36
- package/src/tools/agent/handoff-note.ts +51 -54
- package/src/tools/agent/interaction.ts +32 -28
- package/src/tools/agent/project.ts +300 -287
- package/src/tools/agent/relationship-note.ts +40 -36
- package/src/tools/agent/relationship-reflect.ts +23 -32
- package/src/tools/agent/synthesize.ts +26 -27
- package/src/tools/agent/thread.ts +42 -51
- package/src/tools/agent/wisdom-frame.ts +45 -38
- package/src/tools/control-room/server.ts +0 -2
- package/src/tools/control-room/ui/dist/assets/{index-BZMDzWFa.js → index-mFpXpiBj.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/screens/log.tsx +10 -1
- package/src/tools/lib/command.ts +275 -0
- package/src/tools/subagent-doctor.ts +74 -9
- package/src/tools/token-cost.ts +18 -17
|
@@ -5,26 +5,11 @@
|
|
|
5
5
|
* Each project is stored at `~/.pal/memory/projects/{slug}/ISA.md`.
|
|
6
6
|
* Frontmatter holds operational state; body holds ISA spec sections.
|
|
7
7
|
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* pal cli project list
|
|
10
|
-
* pal cli project create [name] [--path PATH] [--objectives "..."] [--serves goal|revenue|fun]
|
|
11
|
-
* pal cli project serves <name> <goal|revenue|fun> [note]
|
|
12
|
-
* pal cli project resume <name>
|
|
13
|
-
* pal cli project complete | archive | pause | unpause <name>
|
|
14
|
-
* pal cli project add-next <name> "text"
|
|
15
|
-
* pal cli project add-blocker <name> "text"
|
|
16
|
-
* pal cli project add-decision <name> "decision" "rationale"
|
|
17
|
-
* pal cli project add-handoff <name> "text"
|
|
18
|
-
* pal cli project rm-next | rm-blocker <name> <index>
|
|
19
|
-
* pal cli project update-section <name> <section> "content"
|
|
20
|
-
* pal cli project criteria <name>
|
|
21
|
-
* pal cli project isa-init <name>
|
|
22
|
-
* pal cli project migrate
|
|
8
|
+
* Usage: pal cli project <command> --help
|
|
23
9
|
*/
|
|
24
10
|
|
|
25
11
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
26
12
|
import { resolve } from "node:path";
|
|
27
|
-
import { parseArgs } from "node:util";
|
|
28
13
|
import { writeBinding } from "../../hooks/lib/bindings";
|
|
29
14
|
import { paths, toPath } from "../../hooks/lib/paths";
|
|
30
15
|
import {
|
|
@@ -37,9 +22,11 @@ import {
|
|
|
37
22
|
proposeBinding,
|
|
38
23
|
readAllProjects,
|
|
39
24
|
readProject,
|
|
25
|
+
type ServesKind,
|
|
40
26
|
writeProject,
|
|
41
27
|
} from "../../hooks/lib/projects";
|
|
42
28
|
import { isServesKind, SERVES_KINDS, setServes } from "../../hooks/lib/serves";
|
|
29
|
+
import { group, type Leaf, leaf, runCommand, UsageError } from "../lib/command";
|
|
43
30
|
import {
|
|
44
31
|
archiveLine,
|
|
45
32
|
completeIsc,
|
|
@@ -76,9 +63,40 @@ function requireProject(name: string): ProjectProgress {
|
|
|
76
63
|
return p as ProjectProgress;
|
|
77
64
|
}
|
|
78
65
|
|
|
66
|
+
// ── argument values ───────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function positiveId(value: string, label: string): number {
|
|
69
|
+
const id = Number(value);
|
|
70
|
+
if (!Number.isInteger(id) || id < 1) {
|
|
71
|
+
throw new UsageError(`${label} must be a positive integer, got "${value}"`);
|
|
72
|
+
}
|
|
73
|
+
return id;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function listIndex(value: string): number {
|
|
77
|
+
const index = Number(value);
|
|
78
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
79
|
+
throw new UsageError(`<index> must be a non-negative integer, got "${value}"`);
|
|
80
|
+
}
|
|
81
|
+
return index;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function nonBlank(text: string, slot: string): string {
|
|
85
|
+
const trimmed = text.trim();
|
|
86
|
+
if (!trimmed) throw new UsageError(`${slot} is empty`);
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function servesKind(value: string, label: string): ServesKind {
|
|
91
|
+
if (!isServesKind(value)) {
|
|
92
|
+
throw new UsageError(`${label} must be one of: ${SERVES_KINDS.join(", ")}`);
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
// ── list ──────────────────────────────────────────────────────────
|
|
80
98
|
|
|
81
|
-
function cmdList():
|
|
99
|
+
function cmdList(): undefined {
|
|
82
100
|
const all = readAllProjects().sort((a, b) => b.updated.localeCompare(a.updated));
|
|
83
101
|
const rows = all.map((p) => ({
|
|
84
102
|
name: p.name,
|
|
@@ -94,27 +112,35 @@ function cmdList(): void {
|
|
|
94
112
|
|
|
95
113
|
// ── create ────────────────────────────────────────────────────────
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
serves: { type: "string" },
|
|
105
|
-
"serves-note": { type: "string" },
|
|
106
|
-
},
|
|
107
|
-
allowPositionals: true,
|
|
108
|
-
});
|
|
115
|
+
interface CreateValues {
|
|
116
|
+
path?: string;
|
|
117
|
+
name?: string;
|
|
118
|
+
objectives?: string;
|
|
119
|
+
serves?: string;
|
|
120
|
+
"serves-note"?: string;
|
|
121
|
+
}
|
|
109
122
|
|
|
123
|
+
function projectName(
|
|
124
|
+
positional: string | undefined,
|
|
125
|
+
flag: string | undefined
|
|
126
|
+
): string | undefined {
|
|
127
|
+
if (positional !== undefined && flag !== undefined) {
|
|
128
|
+
throw new UsageError("give the name once: as [name] or as --name");
|
|
129
|
+
}
|
|
130
|
+
return positional ?? flag;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function cmdCreate(given: string | undefined, values: CreateValues): undefined {
|
|
110
134
|
const path = toPath(values.path ?? process.cwd());
|
|
111
|
-
const name = (
|
|
135
|
+
const name = (given ?? defaultSlug(path)).trim();
|
|
112
136
|
|
|
113
137
|
if (!/^[a-z0-9_-]+$/.test(name)) {
|
|
114
|
-
|
|
138
|
+
throw new UsageError(
|
|
115
139
|
`Invalid project name "${name}". Use lowercase letters, digits, hyphens, underscores.`
|
|
116
140
|
);
|
|
117
141
|
}
|
|
142
|
+
const serves =
|
|
143
|
+
values.serves === undefined ? undefined : servesKind(values.serves, "--serves");
|
|
118
144
|
|
|
119
145
|
if (readProject(name)) {
|
|
120
146
|
fail(
|
|
@@ -131,10 +157,6 @@ function cmdCreate(args: string[]): void {
|
|
|
131
157
|
.join("\n")
|
|
132
158
|
: undefined;
|
|
133
159
|
|
|
134
|
-
if (values.serves !== undefined && !isServesKind(values.serves)) {
|
|
135
|
-
fail(`--serves must be one of: ${SERVES_KINDS.join(", ")}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
160
|
const project: ProjectProgress = {
|
|
139
161
|
name,
|
|
140
162
|
path,
|
|
@@ -142,9 +164,7 @@ function cmdCreate(args: string[]): void {
|
|
|
142
164
|
created: now(),
|
|
143
165
|
updated: now(),
|
|
144
166
|
...(goalLines ? { goal: goalLines } : {}),
|
|
145
|
-
...(
|
|
146
|
-
? { serves: values.serves, serves_by: "user" as const }
|
|
147
|
-
: {}),
|
|
167
|
+
...(serves ? { serves, serves_by: "user" as const } : {}),
|
|
148
168
|
...(values["serves-note"] ? { serves_note: values["serves-note"] } : {}),
|
|
149
169
|
};
|
|
150
170
|
writeProject(project);
|
|
@@ -152,15 +172,11 @@ function cmdCreate(args: string[]): void {
|
|
|
152
172
|
}
|
|
153
173
|
|
|
154
174
|
/** The answer the scaffolder asks for, and the one place importance can be corrected. */
|
|
155
|
-
function cmdServes(
|
|
156
|
-
const [name, kind, ...note] = args;
|
|
157
|
-
if (!name || !kind) fail("Usage: serves <name> <goal|revenue|fun> [note]");
|
|
158
|
-
if (!isServesKind(kind)) fail(`serves must be one of: ${SERVES_KINDS.join(", ")}`);
|
|
159
|
-
|
|
175
|
+
function cmdServes(name: string, kind: ServesKind, note: string): undefined {
|
|
160
176
|
const outcome = setServes({
|
|
161
177
|
name,
|
|
162
178
|
kind,
|
|
163
|
-
note: note.
|
|
179
|
+
note: note.trim() || undefined,
|
|
164
180
|
by: "user",
|
|
165
181
|
});
|
|
166
182
|
if (outcome === "missing") fail(`No project named "${name}".`);
|
|
@@ -173,9 +189,7 @@ function cmdServes(args: string[]): void {
|
|
|
173
189
|
// Criteria/Changelog blobs collapse to open-ISC titles + counts. Full ISC text
|
|
174
190
|
// is fetched on demand via show-isc / list-isc, so resume stays cheap on
|
|
175
191
|
// projects carrying a large backlog.
|
|
176
|
-
function cmdResume(
|
|
177
|
-
const name = args[0];
|
|
178
|
-
if (!name) fail("Usage: resume <name>");
|
|
192
|
+
function cmdResume(name: string): undefined {
|
|
179
193
|
const { criteria, changelog, ...project } = requireProject(name);
|
|
180
194
|
const iscs = parseIscs(criteria ?? "");
|
|
181
195
|
const archived = parseIscs(changelog ?? "");
|
|
@@ -205,7 +219,7 @@ function cmdResume(args: string[]): void {
|
|
|
205
219
|
|
|
206
220
|
// ── status transitions ────────────────────────────────────────────
|
|
207
221
|
|
|
208
|
-
function setStatus(name: string, status: ProjectStatus):
|
|
222
|
+
function setStatus(name: string, status: ProjectStatus): undefined {
|
|
209
223
|
const p = requireProject(name);
|
|
210
224
|
p.status = status;
|
|
211
225
|
p.updated = now();
|
|
@@ -215,20 +229,18 @@ function setStatus(name: string, status: ProjectStatus): void {
|
|
|
215
229
|
|
|
216
230
|
// ── append/remove for array fields ───────────────────────────────
|
|
217
231
|
|
|
218
|
-
function appendItem(name: string, field: "next" | "blockers", text: string):
|
|
219
|
-
|
|
232
|
+
function appendItem(name: string, field: "next" | "blockers", text: string): undefined {
|
|
233
|
+
const item = nonBlank(text, `${field} text`);
|
|
220
234
|
const p = requireProject(name);
|
|
221
235
|
const list = p[field] ?? [];
|
|
222
|
-
list.push(
|
|
236
|
+
list.push(item);
|
|
223
237
|
p[field] = list;
|
|
224
238
|
p.updated = now();
|
|
225
239
|
writeProject(p);
|
|
226
240
|
ok({ updated: true, name, field, count: list.length });
|
|
227
241
|
}
|
|
228
242
|
|
|
229
|
-
function removeItem(name: string, field: "next" | "blockers",
|
|
230
|
-
const idx = parseInt(indexArg, 10);
|
|
231
|
-
if (!Number.isInteger(idx) || idx < 0) fail(`Invalid index "${indexArg}".`);
|
|
243
|
+
function removeItem(name: string, field: "next" | "blockers", idx: number): undefined {
|
|
232
244
|
const p = requireProject(name);
|
|
233
245
|
const list = p[field] ?? [];
|
|
234
246
|
if (idx >= list.length) fail(`Index ${idx} out of range (length ${list.length}).`);
|
|
@@ -241,12 +253,12 @@ function removeItem(name: string, field: "next" | "blockers", indexArg: string):
|
|
|
241
253
|
|
|
242
254
|
// ── decisions (body section append) ──────────────────────────────
|
|
243
255
|
|
|
244
|
-
function addDecision(name: string, decision: string, rationale: string):
|
|
245
|
-
|
|
246
|
-
|
|
256
|
+
function addDecision(name: string, decision: string, rationale: string): undefined {
|
|
257
|
+
const what = nonBlank(decision, "<decision>");
|
|
258
|
+
const why = nonBlank(rationale, "<rationale...>");
|
|
247
259
|
const p = requireProject(name);
|
|
248
260
|
const date = new Date().toISOString().slice(0, 10);
|
|
249
|
-
const line = `- ${date}: ${
|
|
261
|
+
const line = `- ${date}: ${what} (${why})`;
|
|
250
262
|
p.decisions = p.decisions ? `${p.decisions}\n${line}` : line;
|
|
251
263
|
p.updated = now();
|
|
252
264
|
writeProject(p);
|
|
@@ -255,10 +267,10 @@ function addDecision(name: string, decision: string, rationale: string): void {
|
|
|
255
267
|
|
|
256
268
|
// ── handoff ───────────────────────────────────────────────────────
|
|
257
269
|
|
|
258
|
-
function addHandoff(name: string, text: string):
|
|
259
|
-
|
|
270
|
+
function addHandoff(name: string, text: string): undefined {
|
|
271
|
+
const handoff = nonBlank(text, "handoff text");
|
|
260
272
|
const p = requireProject(name);
|
|
261
|
-
p.handoff =
|
|
273
|
+
p.handoff = handoff;
|
|
262
274
|
p.updated = now();
|
|
263
275
|
writeProject(p);
|
|
264
276
|
ok({ updated: true, name });
|
|
@@ -269,10 +281,8 @@ function addHandoff(name: string, text: string): void {
|
|
|
269
281
|
// Where a project lives is machine-local, so this writes a binding rather than a
|
|
270
282
|
// field on the record. Unlike the save path it does not require the directory to
|
|
271
283
|
// exist yet: naming where a repo is about to be cloned is a legitimate use.
|
|
272
|
-
function cmdSetPath(
|
|
273
|
-
const
|
|
274
|
-
if (!name || rest.length === 0) fail("Usage: set-path <name> <new-path>");
|
|
275
|
-
const newPath = toPath(rest.join(" ").trim());
|
|
284
|
+
function cmdSetPath(name: string, rawPath: string): undefined {
|
|
285
|
+
const newPath = toPath(nonBlank(rawPath, "<path...>"));
|
|
276
286
|
const p = requireProject(name);
|
|
277
287
|
writeBinding(p.name, newPath);
|
|
278
288
|
p.updated = now();
|
|
@@ -295,15 +305,19 @@ const VALID_SECTIONS = [
|
|
|
295
305
|
] as const;
|
|
296
306
|
type Section = (typeof VALID_SECTIONS)[number];
|
|
297
307
|
|
|
298
|
-
function
|
|
299
|
-
const
|
|
300
|
-
if (!name || !section) fail("Usage: update-section <name> <section> <content>");
|
|
301
|
-
const key = section.toLowerCase().replace(/\s+/g, "_") as Section;
|
|
308
|
+
function sectionKey(section: string): Section {
|
|
309
|
+
const key = section.toLowerCase().replace(/\s+/g, "_");
|
|
302
310
|
if (!(VALID_SECTIONS as readonly string[]).includes(key)) {
|
|
303
|
-
|
|
311
|
+
throw new UsageError(
|
|
312
|
+
`Unknown section "${section}". Valid: ${VALID_SECTIONS.join(", ")}`
|
|
313
|
+
);
|
|
304
314
|
}
|
|
305
|
-
|
|
306
|
-
|
|
315
|
+
return key as Section;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cmdUpdateSection(name: string, section: string, text: string): undefined {
|
|
319
|
+
const key = sectionKey(section);
|
|
320
|
+
const content = nonBlank(text, "<content...>");
|
|
307
321
|
const p = requireProject(name);
|
|
308
322
|
(p as unknown as Record<string, unknown>)[key] = content;
|
|
309
323
|
p.updated = now();
|
|
@@ -313,18 +327,14 @@ function cmdUpdateSection(args: string[]): void {
|
|
|
313
327
|
|
|
314
328
|
// ── criteria ──────────────────────────────────────────────────────
|
|
315
329
|
|
|
316
|
-
function cmdCriteria(
|
|
317
|
-
const name = args[0];
|
|
318
|
-
if (!name) fail("Usage: criteria <name>");
|
|
330
|
+
function cmdCriteria(name: string): undefined {
|
|
319
331
|
const p = requireProject(name);
|
|
320
332
|
ok({ name, criteria: p.criteria ?? "" });
|
|
321
333
|
}
|
|
322
334
|
|
|
323
335
|
// ── isa-init ──────────────────────────────────────────────────────
|
|
324
336
|
|
|
325
|
-
function cmdIsaInit(
|
|
326
|
-
const name = args[0];
|
|
327
|
-
if (!name) fail("Usage: isa-init <name>");
|
|
337
|
+
function cmdIsaInit(name: string): undefined {
|
|
328
338
|
const p = requireProject(name);
|
|
329
339
|
const sections: Array<keyof ProjectProgress> = [
|
|
330
340
|
"problem",
|
|
@@ -354,7 +364,7 @@ function cmdIsaInit(args: string[]): void {
|
|
|
354
364
|
|
|
355
365
|
// ── migrate (from old JSON format) ───────────────────────────────
|
|
356
366
|
|
|
357
|
-
function cmdMigrate():
|
|
367
|
+
function cmdMigrate(): undefined {
|
|
358
368
|
const progressDir = paths.progress();
|
|
359
369
|
if (!existsSync(progressDir)) {
|
|
360
370
|
ok({ migrated: 0, skipped: 0, results: [] });
|
|
@@ -403,9 +413,7 @@ function cmdMigrate(): void {
|
|
|
403
413
|
|
|
404
414
|
// ── rm (project) ──────────────────────────────────────────────────
|
|
405
415
|
|
|
406
|
-
function cmdRm(
|
|
407
|
-
const name = args[0];
|
|
408
|
-
if (!name) fail("Usage: rm <name> (deletes the entire project directory)");
|
|
416
|
+
function cmdRm(name: string): undefined {
|
|
409
417
|
const removed = deleteProject(name);
|
|
410
418
|
if (!removed) fail(`No project named "${name}".`);
|
|
411
419
|
ok({ deleted: true, name });
|
|
@@ -416,10 +424,8 @@ function cmdRm(args: string[]): void {
|
|
|
416
424
|
// Three states, not two: a retired ISC is one that stopped being valid, which the
|
|
417
425
|
// record must not report as completed work. The box character is the storage form
|
|
418
426
|
// and the id stays in it, so a retired line keeps reserving its id in nextIscId.
|
|
419
|
-
function cmdAddIsc(
|
|
420
|
-
const
|
|
421
|
-
const title = args.slice(1).join(" ").trim();
|
|
422
|
-
if (!title) fail("Usage: add-isc <name> <title>");
|
|
427
|
+
function cmdAddIsc(name: string, text: string): undefined {
|
|
428
|
+
const title = nonBlank(text, "<title...>");
|
|
423
429
|
const p = requireProject(name);
|
|
424
430
|
const current = p.criteria ?? "";
|
|
425
431
|
const id = nextIscId(current, p.changelog ?? "");
|
|
@@ -439,10 +445,7 @@ function cmdAddIsc(args: string[]): void {
|
|
|
439
445
|
|
|
440
446
|
// Completing an ISC moves its line out of Criteria and into the dated Changelog
|
|
441
447
|
// archive, so Criteria stays exactly the open set and never re-bloats context.
|
|
442
|
-
function cmdCompleteIsc(
|
|
443
|
-
const name = args[0] ?? fail("Usage: complete-isc <name> <id>");
|
|
444
|
-
const id = Number(args[1] ?? fail("Usage: complete-isc <name> <id>"));
|
|
445
|
-
if (!Number.isInteger(id) || id < 1) fail("ISC id must be a positive integer");
|
|
448
|
+
function cmdCompleteIsc(name: string, id: number): undefined {
|
|
446
449
|
const p = requireProject(name);
|
|
447
450
|
const move = completeIsc(sectionsOf(p), id);
|
|
448
451
|
if (!move.ok) fail(`${move.reason} in project "${name}"`);
|
|
@@ -467,10 +470,7 @@ function applyIscMove(p: ProjectProgress, move: IscMove & { ok: true }): void {
|
|
|
467
470
|
|
|
468
471
|
// Reopening pulls the line back out of the Changelog (or legacy Criteria) into
|
|
469
472
|
// the open set.
|
|
470
|
-
function cmdReopenIsc(
|
|
471
|
-
const name = args[0] ?? fail("Usage: reopen-isc <name> <id>");
|
|
472
|
-
const id = Number(args[1] ?? fail("Usage: reopen-isc <name> <id>"));
|
|
473
|
-
if (!Number.isInteger(id) || id < 1) fail("ISC id must be a positive integer");
|
|
473
|
+
function cmdReopenIsc(name: string, id: number): undefined {
|
|
474
474
|
const p = requireProject(name);
|
|
475
475
|
const move = reopenIsc(sectionsOf(p), id);
|
|
476
476
|
if (!move.ok) fail(`${move.reason} in project "${name}"`);
|
|
@@ -482,11 +482,23 @@ function cmdReopenIsc(args: string[]): void {
|
|
|
482
482
|
ok({ checked: false, id });
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
485
|
+
interface IscListFlags {
|
|
486
|
+
all?: boolean;
|
|
487
|
+
closed?: boolean;
|
|
488
|
+
retired?: boolean;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function iscListFlag(flags: IscListFlags): Set<string> {
|
|
492
|
+
const chosen = Object.entries(flags)
|
|
493
|
+
.filter(([, on]) => on)
|
|
494
|
+
.map(([flag]) => `--${flag}`);
|
|
495
|
+
if (chosen.length > 1) {
|
|
496
|
+
throw new UsageError(`${chosen.join(" and ")} cannot be combined; pick one`);
|
|
497
|
+
}
|
|
498
|
+
return new Set(chosen);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function cmdListIsc(name: string, flags: Set<string>): undefined {
|
|
490
502
|
const p = requireProject(name);
|
|
491
503
|
const criteria = parseIscs(p.criteria ?? "");
|
|
492
504
|
const all = [...criteria, ...parseIscs(p.changelog ?? "")];
|
|
@@ -505,10 +517,7 @@ function cmdListIsc(args: string[]): void {
|
|
|
505
517
|
|
|
506
518
|
// show-isc prints one ISC's full text on demand — the "detail" counterpart to
|
|
507
519
|
// resume's titles. Scans Criteria (open + not-yet-archived) and Changelog.
|
|
508
|
-
function cmdShowIsc(
|
|
509
|
-
const name = args[0];
|
|
510
|
-
const id = Number(args[1]);
|
|
511
|
-
if (!name || !Number.isInteger(id) || id < 1) fail("Usage: show-isc <name> <id>");
|
|
520
|
+
function cmdShowIsc(name: string, id: number): undefined {
|
|
512
521
|
const p = requireProject(name);
|
|
513
522
|
const isc = [...parseIscs(p.criteria ?? ""), ...parseIscs(p.changelog ?? "")].find(
|
|
514
523
|
(i) => i.id === id
|
|
@@ -520,18 +529,7 @@ function cmdShowIsc(args: string[]): void {
|
|
|
520
529
|
// retire-isc closes an ISC that stopped being valid, which complete-isc cannot say:
|
|
521
530
|
// completing files it as done work. The line moves to the Changelog under its own
|
|
522
531
|
// heading as [~], so it still reserves its id and never reads as finished.
|
|
523
|
-
function cmdRetireIsc(
|
|
524
|
-
const positional = args.filter((a) => !a.startsWith("--"));
|
|
525
|
-
const name = positional[0];
|
|
526
|
-
const id = Number(positional[1]);
|
|
527
|
-
if (!name || !Number.isInteger(id) || id < 1) {
|
|
528
|
-
fail("Usage: retire-isc <name> <id> [--by <supersedingId>]");
|
|
529
|
-
}
|
|
530
|
-
const byIndex = args.indexOf("--by");
|
|
531
|
-
const by = byIndex === -1 ? null : Number(args[byIndex + 1]);
|
|
532
|
-
if (byIndex !== -1 && (!Number.isInteger(by) || (by ?? 0) < 1)) {
|
|
533
|
-
fail("--by expects a positive ISC id");
|
|
534
|
-
}
|
|
532
|
+
function cmdRetireIsc(name: string, id: number, by: number | null): undefined {
|
|
535
533
|
const p = requireProject(name);
|
|
536
534
|
if (parseIscs(p.changelog ?? "").some((i) => i.id === id && i.status === "retired")) {
|
|
537
535
|
ok({ retired: true, id, alreadyRetired: true });
|
|
@@ -554,13 +552,8 @@ function cmdRetireIsc(args: string[]): void {
|
|
|
554
552
|
// edit-isc rewrites one ISC's text in place, keeping its id and open/done state.
|
|
555
553
|
// The id never leaves the record, so nextIscId still reserves it. Returns the
|
|
556
554
|
// previous text because the ISA files carry no version history of their own.
|
|
557
|
-
function cmdEditIsc(
|
|
558
|
-
const
|
|
559
|
-
const id = Number(args[1]);
|
|
560
|
-
const text = args.slice(2).join(" ").trim();
|
|
561
|
-
if (!name || !Number.isInteger(id) || id < 1 || !text) {
|
|
562
|
-
fail('Usage: edit-isc <name> <id> "new text"');
|
|
563
|
-
}
|
|
555
|
+
function cmdEditIsc(name: string, id: number, newText: string): undefined {
|
|
556
|
+
const text = nonBlank(newText, "<text...>");
|
|
564
557
|
const p = requireProject(name);
|
|
565
558
|
const inCriteria = parseIscs(p.criteria ?? "").find((i) => i.id === id);
|
|
566
559
|
const isc = inCriteria ?? parseIscs(p.changelog ?? "").find((i) => i.id === id);
|
|
@@ -592,8 +585,7 @@ function cmdEditIsc(args: string[]): void {
|
|
|
592
585
|
|
|
593
586
|
// Backfill: sweep any done ISCs still sitting in Criteria (legacy projects, or
|
|
594
587
|
// completions from before archive-on-complete) into the Changelog in one pass.
|
|
595
|
-
function cmdPruneIsc(
|
|
596
|
-
const name = args[0] ?? fail("Usage: prune-isc <name>");
|
|
588
|
+
function cmdPruneIsc(name: string): undefined {
|
|
597
589
|
const p = requireProject(name);
|
|
598
590
|
const done = parseIscs(p.criteria ?? "").filter((i) => i.status !== "open");
|
|
599
591
|
for (const isc of done) {
|
|
@@ -618,9 +610,8 @@ function taskIsaPath(slug: string): string {
|
|
|
618
610
|
return resolve(dir, "ISA.md");
|
|
619
611
|
}
|
|
620
612
|
|
|
621
|
-
function cmdScaffoldTaskIsa(
|
|
622
|
-
const title =
|
|
623
|
-
if (!title) fail("Usage: scaffold-task-isa <title>");
|
|
613
|
+
function cmdScaffoldTaskIsa(text: string): undefined {
|
|
614
|
+
const title = nonBlank(text, "<title...>");
|
|
624
615
|
const slug = taskSlug(title);
|
|
625
616
|
const ts = new Date().toISOString();
|
|
626
617
|
const content = [
|
|
@@ -644,8 +635,7 @@ function cmdScaffoldTaskIsa(args: string[]): void {
|
|
|
644
635
|
ok({ created: true, slug, path: filePath });
|
|
645
636
|
}
|
|
646
637
|
|
|
647
|
-
function cmdCompleteTaskIsa(
|
|
648
|
-
const slug = args[0] ?? fail("Usage: complete-task-isa <slug>");
|
|
638
|
+
function cmdCompleteTaskIsa(slug: string): undefined {
|
|
649
639
|
const filePath = resolve(paths.work(), slug, "ISA.md");
|
|
650
640
|
if (!existsSync(filePath)) fail(`Task ISA not found: ${slug}`);
|
|
651
641
|
const content = readFileSync(filePath, "utf-8");
|
|
@@ -656,163 +646,186 @@ function cmdCompleteTaskIsa(args: string[]): void {
|
|
|
656
646
|
ok({ completed: true, slug });
|
|
657
647
|
}
|
|
658
648
|
|
|
659
|
-
// ──
|
|
660
|
-
|
|
661
|
-
function
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
help();
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
switch (cmd) {
|
|
704
|
-
case "list":
|
|
705
|
-
cmdList();
|
|
706
|
-
return;
|
|
707
|
-
case "create":
|
|
708
|
-
cmdCreate(rest);
|
|
709
|
-
return;
|
|
710
|
-
case "serves":
|
|
711
|
-
cmdServes(rest);
|
|
712
|
-
return;
|
|
713
|
-
case "resume":
|
|
714
|
-
cmdResume(rest);
|
|
715
|
-
return;
|
|
716
|
-
case "complete":
|
|
717
|
-
setStatus(rest[0] ?? fail("Usage: complete <name>"), "complete");
|
|
718
|
-
return;
|
|
719
|
-
case "archive":
|
|
720
|
-
setStatus(rest[0] ?? fail("Usage: archive <name>"), "archived");
|
|
721
|
-
return;
|
|
722
|
-
case "pause":
|
|
723
|
-
setStatus(rest[0] ?? fail("Usage: pause <name>"), "paused");
|
|
724
|
-
return;
|
|
725
|
-
case "unpause":
|
|
726
|
-
setStatus(rest[0] ?? fail("Usage: unpause <name>"), "active");
|
|
727
|
-
return;
|
|
728
|
-
case "add-next":
|
|
729
|
-
appendItem(
|
|
730
|
-
rest[0] ?? fail("Usage: add-next <name> <text>"),
|
|
731
|
-
"next",
|
|
732
|
-
rest.slice(1).join(" ")
|
|
733
|
-
);
|
|
734
|
-
return;
|
|
735
|
-
case "add-blocker":
|
|
736
|
-
appendItem(
|
|
737
|
-
rest[0] ?? fail("Usage: add-blocker <name> <text>"),
|
|
738
|
-
"blockers",
|
|
739
|
-
rest.slice(1).join(" ")
|
|
740
|
-
);
|
|
741
|
-
return;
|
|
742
|
-
case "add-decision":
|
|
743
|
-
addDecision(
|
|
744
|
-
rest[0] ?? fail("Usage: add-decision <name> <decision> <rationale>"),
|
|
745
|
-
rest[1] ?? "",
|
|
746
|
-
rest.slice(2).join(" ")
|
|
747
|
-
);
|
|
748
|
-
return;
|
|
749
|
-
case "add-handoff":
|
|
750
|
-
addHandoff(
|
|
751
|
-
rest[0] ?? fail("Usage: add-handoff <name> <text>"),
|
|
752
|
-
rest.slice(1).join(" ")
|
|
753
|
-
);
|
|
754
|
-
return;
|
|
755
|
-
case "rm-next":
|
|
756
|
-
removeItem(rest[0] ?? fail("Usage: rm-next <name> <index>"), "next", rest[1] ?? "");
|
|
757
|
-
return;
|
|
758
|
-
case "rm-blocker":
|
|
759
|
-
removeItem(
|
|
760
|
-
rest[0] ?? fail("Usage: rm-blocker <name> <index>"),
|
|
761
|
-
"blockers",
|
|
762
|
-
rest[1] ?? ""
|
|
763
|
-
);
|
|
764
|
-
return;
|
|
765
|
-
case "update-section":
|
|
766
|
-
cmdUpdateSection(rest);
|
|
767
|
-
return;
|
|
768
|
-
case "criteria":
|
|
769
|
-
cmdCriteria(rest);
|
|
770
|
-
return;
|
|
771
|
-
case "add-isc":
|
|
772
|
-
cmdAddIsc(rest);
|
|
773
|
-
return;
|
|
774
|
-
case "complete-isc":
|
|
775
|
-
cmdCompleteIsc(rest);
|
|
776
|
-
return;
|
|
777
|
-
case "reopen-isc":
|
|
778
|
-
cmdReopenIsc(rest);
|
|
779
|
-
return;
|
|
780
|
-
case "list-isc":
|
|
781
|
-
cmdListIsc(rest);
|
|
782
|
-
return;
|
|
783
|
-
case "show-isc":
|
|
784
|
-
cmdShowIsc(rest);
|
|
785
|
-
return;
|
|
786
|
-
case "retire-isc":
|
|
787
|
-
cmdRetireIsc(rest);
|
|
788
|
-
return;
|
|
789
|
-
case "edit-isc":
|
|
790
|
-
cmdEditIsc(rest);
|
|
791
|
-
return;
|
|
792
|
-
case "prune-isc":
|
|
793
|
-
cmdPruneIsc(rest);
|
|
794
|
-
return;
|
|
795
|
-
case "isa-init":
|
|
796
|
-
cmdIsaInit(rest);
|
|
797
|
-
return;
|
|
798
|
-
case "scaffold-task-isa":
|
|
799
|
-
cmdScaffoldTaskIsa(rest);
|
|
800
|
-
return;
|
|
801
|
-
case "complete-task-isa":
|
|
802
|
-
cmdCompleteTaskIsa(rest);
|
|
803
|
-
return;
|
|
804
|
-
case "migrate":
|
|
805
|
-
cmdMigrate();
|
|
806
|
-
return;
|
|
807
|
-
case "set-path":
|
|
808
|
-
cmdSetPath(rest);
|
|
809
|
-
return;
|
|
810
|
-
case "rm":
|
|
811
|
-
cmdRm(rest);
|
|
812
|
-
return;
|
|
813
|
-
default:
|
|
814
|
-
fail(`Unknown command "${cmd}". Run 'pal cli project help' for usage.`);
|
|
815
|
-
}
|
|
649
|
+
// ── command tree ──────────────────────────────────────────────────
|
|
650
|
+
|
|
651
|
+
function joined(words: string[]): string {
|
|
652
|
+
return words.join(" ");
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function nameLeaf(summary: string, act: (name: string) => undefined): Leaf {
|
|
656
|
+
return leaf({ summary, args: "<name>", run: ({ positionals }) => act(positionals[0]) });
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function statusLeaf(summary: string, status: ProjectStatus): Leaf {
|
|
660
|
+
return nameLeaf(summary, (name) => setStatus(name, status));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function iscLeaf(summary: string, act: (name: string, id: number) => undefined): Leaf {
|
|
664
|
+
return leaf({
|
|
665
|
+
summary,
|
|
666
|
+
args: "<name> <id>",
|
|
667
|
+
run: ({ positionals }) => act(positionals[0], positiveId(positionals[1], "<id>")),
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function textLeaf(
|
|
672
|
+
summary: string,
|
|
673
|
+
slot: string,
|
|
674
|
+
act: (name: string, text: string) => undefined
|
|
675
|
+
): Leaf {
|
|
676
|
+
return leaf({
|
|
677
|
+
summary,
|
|
678
|
+
args: `<name> ${slot}`,
|
|
679
|
+
run: ({ positionals }) => act(positionals[0], joined(positionals.slice(1))),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function removeLeaf(summary: string, field: "next" | "blockers"): Leaf {
|
|
684
|
+
return leaf({
|
|
685
|
+
summary,
|
|
686
|
+
args: "<name> <index>",
|
|
687
|
+
run: ({ positionals }) =>
|
|
688
|
+
removeItem(positionals[0], field, listIndex(positionals[1])),
|
|
689
|
+
});
|
|
816
690
|
}
|
|
817
691
|
|
|
818
|
-
|
|
692
|
+
export const command = group({
|
|
693
|
+
summary: "Manage PAL project state (ISA.md backed)",
|
|
694
|
+
commands: {
|
|
695
|
+
list: leaf({ summary: "Show all registered projects", run: cmdList }),
|
|
696
|
+
create: leaf({
|
|
697
|
+
summary: "Register a project",
|
|
698
|
+
args: "[name]",
|
|
699
|
+
options: {
|
|
700
|
+
path: {
|
|
701
|
+
type: "string",
|
|
702
|
+
value: "<path>",
|
|
703
|
+
description: "Where the project lives (default: cwd)",
|
|
704
|
+
},
|
|
705
|
+
name: {
|
|
706
|
+
type: "string",
|
|
707
|
+
value: "<name>",
|
|
708
|
+
description: "Slug, instead of [name] (default: the path's basename)",
|
|
709
|
+
},
|
|
710
|
+
objectives: {
|
|
711
|
+
type: "string",
|
|
712
|
+
value: "<a;b>",
|
|
713
|
+
description: "Goal lines, split on ';', '|' or newlines",
|
|
714
|
+
},
|
|
715
|
+
serves: {
|
|
716
|
+
type: "string",
|
|
717
|
+
value: `<${SERVES_KINDS.join("|")}>`,
|
|
718
|
+
description: "What the project is for",
|
|
719
|
+
},
|
|
720
|
+
"serves-note": {
|
|
721
|
+
type: "string",
|
|
722
|
+
value: "<text>",
|
|
723
|
+
description: "Why, in a few words",
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
run: ({ positionals, values }) =>
|
|
727
|
+
cmdCreate(projectName(positionals[0], values.name), values),
|
|
728
|
+
}),
|
|
729
|
+
serves: leaf({
|
|
730
|
+
summary: "Say what it is for — outranks PAL's guess",
|
|
731
|
+
args: `<name> <${SERVES_KINDS.join("|")}> [note...]`,
|
|
732
|
+
run: ({ positionals: [name, kind, ...note] }) =>
|
|
733
|
+
cmdServes(name, servesKind(kind, "serves"), joined(note)),
|
|
734
|
+
}),
|
|
735
|
+
resume: nameLeaf(
|
|
736
|
+
"Print lean project view (open-ISC titles; full text via show-isc)",
|
|
737
|
+
cmdResume
|
|
738
|
+
),
|
|
739
|
+
complete: statusLeaf("Mark complete", "complete"),
|
|
740
|
+
archive: statusLeaf("Mark archived", "archived"),
|
|
741
|
+
pause: statusLeaf("Mark paused", "paused"),
|
|
742
|
+
unpause: statusLeaf("Mark active again", "active"),
|
|
743
|
+
"set-path": leaf({
|
|
744
|
+
summary: "Update the registered path",
|
|
745
|
+
args: "<name> <path...>",
|
|
746
|
+
run: ({ positionals: [name, ...path] }) => cmdSetPath(name, joined(path)),
|
|
747
|
+
}),
|
|
748
|
+
"add-next": textLeaf("Append next step", "<text...>", (name, text) =>
|
|
749
|
+
appendItem(name, "next", text)
|
|
750
|
+
),
|
|
751
|
+
"add-blocker": textLeaf("Append blocker", "<text...>", (name, text) =>
|
|
752
|
+
appendItem(name, "blockers", text)
|
|
753
|
+
),
|
|
754
|
+
"add-decision": leaf({
|
|
755
|
+
summary: "Log a dated decision entry",
|
|
756
|
+
args: "<name> <decision> <rationale...>",
|
|
757
|
+
run: ({ positionals: [name, decision, ...rationale] }) =>
|
|
758
|
+
addDecision(name, decision, joined(rationale)),
|
|
759
|
+
}),
|
|
760
|
+
"add-handoff": textLeaf("Overwrite handoff field", "<text...>", addHandoff),
|
|
761
|
+
"rm-next": removeLeaf("Remove next step by index", "next"),
|
|
762
|
+
"rm-blocker": removeLeaf("Remove blocker by index", "blockers"),
|
|
763
|
+
"update-section": leaf({
|
|
764
|
+
summary: "Set an ISA body section",
|
|
765
|
+
args: "<name> <section> <content...>",
|
|
766
|
+
details: `Sections: ${VALID_SECTIONS.join(", ")}`,
|
|
767
|
+
run: ({ positionals: [name, section, ...content] }) =>
|
|
768
|
+
cmdUpdateSection(name, section, joined(content)),
|
|
769
|
+
}),
|
|
770
|
+
criteria: nameLeaf("Print the Criteria section", cmdCriteria),
|
|
771
|
+
"add-isc": textLeaf("Append a new open ISC to Criteria", "<title...>", cmdAddIsc),
|
|
772
|
+
"complete-isc": iscLeaf("Mark ISC-N as done", cmdCompleteIsc),
|
|
773
|
+
"reopen-isc": iscLeaf("Reopen ISC-N (mark not done)", cmdReopenIsc),
|
|
774
|
+
"list-isc": leaf({
|
|
775
|
+
summary: "List open ISCs (default), or the closed or retired ones",
|
|
776
|
+
args: "<name>",
|
|
777
|
+
options: {
|
|
778
|
+
all: { type: "boolean", description: "Open, done and retired" },
|
|
779
|
+
closed: { type: "boolean", description: "Done only" },
|
|
780
|
+
retired: { type: "boolean", description: "Retired only" },
|
|
781
|
+
},
|
|
782
|
+
run: ({ positionals, values }) => cmdListIsc(positionals[0], iscListFlag(values)),
|
|
783
|
+
}),
|
|
784
|
+
"show-isc": iscLeaf("Print one ISC's full text", cmdShowIsc),
|
|
785
|
+
"edit-isc": leaf({
|
|
786
|
+
summary: "Rewrite ISC-N's text, keeping its id and state",
|
|
787
|
+
args: "<name> <id> <text...>",
|
|
788
|
+
run: ({ positionals: [name, id, ...text] }) =>
|
|
789
|
+
cmdEditIsc(name, positiveId(id, "<id>"), joined(text)),
|
|
790
|
+
}),
|
|
791
|
+
"retire-isc": leaf({
|
|
792
|
+
summary: "Close ISC-N as no longer valid, not as done",
|
|
793
|
+
args: "<name> <id>",
|
|
794
|
+
options: {
|
|
795
|
+
by: { type: "string", value: "<id>", description: "The ISC that supersedes it" },
|
|
796
|
+
},
|
|
797
|
+
run: ({ positionals: [name, id], values }) =>
|
|
798
|
+
cmdRetireIsc(
|
|
799
|
+
name,
|
|
800
|
+
positiveId(id, "<id>"),
|
|
801
|
+
values.by === undefined ? null : positiveId(values.by, "--by")
|
|
802
|
+
),
|
|
803
|
+
}),
|
|
804
|
+
"prune-isc": nameLeaf(
|
|
805
|
+
"Archive done ISCs from Criteria into the Changelog",
|
|
806
|
+
cmdPruneIsc
|
|
807
|
+
),
|
|
808
|
+
"isa-init": nameLeaf("Mark project as ISA-initialized", cmdIsaInit),
|
|
809
|
+
"scaffold-task-isa": leaf({
|
|
810
|
+
summary: "Create a one-shot task ISA in memory/work/",
|
|
811
|
+
args: "<title...>",
|
|
812
|
+
run: ({ positionals }) => cmdScaffoldTaskIsa(joined(positionals)),
|
|
813
|
+
}),
|
|
814
|
+
"complete-task-isa": leaf({
|
|
815
|
+
summary: "Mark a task ISA as complete",
|
|
816
|
+
args: "<slug>",
|
|
817
|
+
run: ({ positionals }) => cmdCompleteTaskIsa(positionals[0]),
|
|
818
|
+
}),
|
|
819
|
+
migrate: leaf({
|
|
820
|
+
summary: "Migrate old JSON progress files → ISA.md",
|
|
821
|
+
run: cmdMigrate,
|
|
822
|
+
}),
|
|
823
|
+
rm: nameLeaf("Delete the entire project", cmdRm),
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
export function run(argv: string[] = scriptArgs()): Promise<number> {
|
|
828
|
+
return runCommand(command, argv, ["pal", "cli", "project"]);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (import.meta.main) process.exit(await run());
|