@indigoai-us/hq-cli 5.81.0 → 5.82.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/CHANGELOG.md +14 -0
- package/dist/commands/core-checkpoint.d.ts +1 -1
- package/dist/commands/core-checkpoint.js +131 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.82.0]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq core checkpoint` reaches full `/checkpoint`-skill parity: new
|
|
10
|
+
`--insight`, `--commit`, `--initial-commit`, and `--worker*` options
|
|
11
|
+
(payload equivalents included), automatic caller-repo `remote_url`
|
|
12
|
+
capture, `commits_made` derivation from `--initial-commit`, and a
|
|
13
|
+
sibling prompt that executes the complete checkpoint flow — in-place
|
|
14
|
+
thread upgrade/rename, knowledge-repo scan, journal close, legacy
|
|
15
|
+
checkpoint, thread index refresh, document-release best-effort,
|
|
16
|
+
transcript-derived learnings — under explicit allow-list write
|
|
17
|
+
bounds. (#289)
|
|
18
|
+
|
|
5
19
|
## [5.81.0]
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -11,7 +11,7 @@ import { Command } from "commander";
|
|
|
11
11
|
* Kept in TypeScript rather than in a bundled asset: it is an instruction to
|
|
12
12
|
* a locally-installed agent, not a scaffold script that should be packaged.
|
|
13
13
|
*/
|
|
14
|
-
export declare const SIBLING_PROMPT_TEMPLATE = "You are the HQ checkpoint sibling \u2014 a background maintenance agent for this\nHQ install. Your parent session's state is in <payloadPath>. Work\nquietly and do not ask questions; if something is ambiguous, record it in the\nreport instead of guessing.\n\n1. Read the payload. If it lists a transcript path that exists,
|
|
14
|
+
export declare const SIBLING_PROMPT_TEMPLATE = "You are the HQ checkpoint sibling \u2014 a background maintenance agent for this\nHQ install. Your parent session's state is in <payloadPath>. Work\nquietly and do not ask questions; if something is ambiguous, record it in the\nreport instead of guessing.\n\n1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)\n both for session context and to extract additional reusable learnings/insights\n the parent did not pass explicitly. Never quote secrets\n or tokens from the transcript. If .claude/skills/checkpoint/SKILL.md exists\n under this HQ root, read it and follow it wherever it goes beyond these instructions;\n the write bounds below always win over the skill text.\n2. Upgrade the thread file named in the payload IN PLACE: verify/repair its\n JSON; fill git.remote_url, git.initial_commit, git.commits_made, and\n git.knowledge_repos by scanning core/knowledge/public/*,\n core/knowledge/private/*, personal/knowledge/*, and companies/*/knowledge\n for symlinks or directories containing .git, recording dirty repositories\n as {\"<name>\": {\"commit\": \"<short>\", \"dirty\": true}}. Fill worker,\n next_steps, and insights; set type to \"checkpoint\"; then rename the file to\n drop -auto- from its filename. Use the renamed path in every reference you\n write afterwards.\n3. For every explicit or transcript-derived learning that is a reusable rule,\n distill a non-duplicate policy file under personal/policies/ or, only when\n the payload names a company and the rule is company-specific,\n companies/<company>/policies/, following\n core/knowledge/public/hq-core/policies-spec.md. Store up to two explicit or\n transcript-derived insights per core/knowledge/public/hq-core/insights-spec.md\n when present, otherwise workspace/insights/. Durable facts (not rules) may\n go under personal/knowledge/ or companies/<company>/knowledge/ only.\n4. Close an active session journal fail-soft with\n bash .claude/skills/_shared/journal.sh close \"<project_dir>\" \"<one-line synthesis>\".\n Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with\n id, created_at, summary, files, and next_steps for backward compatibility.\n5. Update workspace/threads/recent.md and regenerate\n workspace/threads/INDEX.md. For each company whose knowledge path appears\n in files_touched, regenerate companies/<company>/knowledge/INDEX.md under\n core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation\n is allowed for those companies, but knowledge/policy content writes remain\n restricted to the payload's named company.\n6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;\n skip silently on any failure. Hook or automation improvements go ONLY under\n personal/hooks/ as proposals.\n7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.\n8. Write <runDir>/report.md \u2014 full prose: what you read, what you changed\n (paths), and what you skipped and why. If\n workspace/checkpoints/sibling/pending.jsonl is non-empty when you finish,\n process those payloads with this same flow, then truncate the file.\n";
|
|
15
15
|
export declare function renderSiblingPrompt(runDir: string, payloadPath: string): string;
|
|
16
16
|
/** Attach the native checkpoint command to the hidden `hq core` group. */
|
|
17
17
|
export declare function registerCoreCheckpointCommand(core: Command): void;
|
|
@@ -36,23 +36,47 @@ HQ install. Your parent session's state is in <payloadPath>. Work
|
|
|
36
36
|
quietly and do not ask questions; if something is ambiguous, record it in the
|
|
37
37
|
report instead of guessing.
|
|
38
38
|
|
|
39
|
-
1. Read the payload. If it lists a transcript path that exists,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
or
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)
|
|
40
|
+
both for session context and to extract additional reusable learnings/insights
|
|
41
|
+
the parent did not pass explicitly. Never quote secrets
|
|
42
|
+
or tokens from the transcript. If .claude/skills/checkpoint/SKILL.md exists
|
|
43
|
+
under this HQ root, read it and follow it wherever it goes beyond these instructions;
|
|
44
|
+
the write bounds below always win over the skill text.
|
|
45
|
+
2. Upgrade the thread file named in the payload IN PLACE: verify/repair its
|
|
46
|
+
JSON; fill git.remote_url, git.initial_commit, git.commits_made, and
|
|
47
|
+
git.knowledge_repos by scanning core/knowledge/public/*,
|
|
48
|
+
core/knowledge/private/*, personal/knowledge/*, and companies/*/knowledge
|
|
49
|
+
for symlinks or directories containing .git, recording dirty repositories
|
|
50
|
+
as {"<name>": {"commit": "<short>", "dirty": true}}. Fill worker,
|
|
51
|
+
next_steps, and insights; set type to "checkpoint"; then rename the file to
|
|
52
|
+
drop -auto- from its filename. Use the renamed path in every reference you
|
|
53
|
+
write afterwards.
|
|
54
|
+
3. For every explicit or transcript-derived learning that is a reusable rule,
|
|
55
|
+
distill a non-duplicate policy file under personal/policies/ or, only when
|
|
56
|
+
the payload names a company and the rule is company-specific,
|
|
57
|
+
companies/<company>/policies/, following
|
|
58
|
+
core/knowledge/public/hq-core/policies-spec.md. Store up to two explicit or
|
|
59
|
+
transcript-derived insights per core/knowledge/public/hq-core/insights-spec.md
|
|
60
|
+
when present, otherwise workspace/insights/. Durable facts (not rules) may
|
|
61
|
+
go under personal/knowledge/ or companies/<company>/knowledge/ only.
|
|
62
|
+
4. Close an active session journal fail-soft with
|
|
63
|
+
bash .claude/skills/_shared/journal.sh close "<project_dir>" "<one-line synthesis>".
|
|
64
|
+
Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with
|
|
65
|
+
id, created_at, summary, files, and next_steps for backward compatibility.
|
|
66
|
+
5. Update workspace/threads/recent.md and regenerate
|
|
67
|
+
workspace/threads/INDEX.md. For each company whose knowledge path appears
|
|
68
|
+
in files_touched, regenerate companies/<company>/knowledge/INDEX.md under
|
|
69
|
+
core/knowledge/public/hq-core/index-md-spec.md. Mechanical index generation
|
|
70
|
+
is allowed for those companies, but knowledge/policy content writes remain
|
|
71
|
+
restricted to the payload's named company.
|
|
72
|
+
6. Run .claude/skills/document-release/SKILL.md best-effort when it exists;
|
|
73
|
+
skip silently on any failure. Hook or automation improvements go ONLY under
|
|
74
|
+
personal/hooks/ as proposals.
|
|
75
|
+
7. WRITE BOUNDS: you may write only under personal/, workspace/, and companies/<company>/ as constrained above. You must NEVER write into .claude/, core/, .agents/, .codex/, repos/, or anywhere outside the HQ root.
|
|
76
|
+
8. Write <runDir>/report.md — full prose: what you read, what you changed
|
|
77
|
+
(paths), and what you skipped and why. If
|
|
78
|
+
workspace/checkpoints/sibling/pending.jsonl is non-empty when you finish,
|
|
79
|
+
process those payloads with this same flow, then truncate the file.
|
|
56
80
|
`;
|
|
57
81
|
export function renderSiblingPrompt(runDir, payloadPath) {
|
|
58
82
|
return SIBLING_PROMPT_TEMPLATE
|
|
@@ -105,6 +129,27 @@ function asStringList(value, field) {
|
|
|
105
129
|
}
|
|
106
130
|
return value;
|
|
107
131
|
}
|
|
132
|
+
function asWorker(value) {
|
|
133
|
+
if (value === undefined || value === null)
|
|
134
|
+
return {};
|
|
135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
136
|
+
usage("checkpoint: payload worker must be an object");
|
|
137
|
+
}
|
|
138
|
+
const worker = value;
|
|
139
|
+
return {
|
|
140
|
+
id: asOptionalString(worker.id, "worker.id"),
|
|
141
|
+
skill: asOptionalString(worker.skill, "worker.skill"),
|
|
142
|
+
state: asOptionalString(worker.state, "worker.state"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function resolveWorker(options, command, payload) {
|
|
146
|
+
const payloadWorker = asWorker(payload.worker);
|
|
147
|
+
const id = wasPassed(command, "worker") ? options.worker : payloadWorker.id;
|
|
148
|
+
const skill = wasPassed(command, "workerSkill") ? options.workerSkill : payloadWorker.skill;
|
|
149
|
+
const suppliedState = wasPassed(command, "workerState") ? options.workerState : payloadWorker.state;
|
|
150
|
+
const state = suppliedState ?? (id ? "completed" : null);
|
|
151
|
+
return { id: id ?? null, skill: skill ?? null, state };
|
|
152
|
+
}
|
|
108
153
|
function wasPassed(command, option) {
|
|
109
154
|
return command.getOptionValueSource(option) === "cli";
|
|
110
155
|
}
|
|
@@ -129,6 +174,9 @@ function mergeInput(options, command, payload) {
|
|
|
129
174
|
const learnings = wasPassed(command, "learning")
|
|
130
175
|
? options.learning
|
|
131
176
|
: asStringList(payload.learnings, "learnings");
|
|
177
|
+
const insights = wasPassed(command, "insight")
|
|
178
|
+
? options.insight
|
|
179
|
+
: asStringList(payload.insights, "insights");
|
|
132
180
|
const decisions = wasPassed(command, "decision")
|
|
133
181
|
? options.decision
|
|
134
182
|
: asStringList(payload.decisions, "decisions");
|
|
@@ -138,6 +186,12 @@ function mergeInput(options, command, payload) {
|
|
|
138
186
|
const tags = wasPassed(command, "tag")
|
|
139
187
|
? options.tag
|
|
140
188
|
: asStringList(payload.tags, "tags");
|
|
189
|
+
const commits = wasPassed(command, "commit")
|
|
190
|
+
? options.commit
|
|
191
|
+
: asStringList(payload.commits, "commits");
|
|
192
|
+
const initialCommit = wasPassed(command, "initialCommit")
|
|
193
|
+
? options.initialCommit
|
|
194
|
+
: asOptionalString(payload.initial_commit, "initial_commit");
|
|
141
195
|
const trigger = (wasPassed(command, "trigger")
|
|
142
196
|
? options.trigger
|
|
143
197
|
: asOptionalString(payload.trigger, "trigger")) ?? DEFAULT_TRIGGER;
|
|
@@ -152,9 +206,13 @@ function mergeInput(options, command, payload) {
|
|
|
152
206
|
summary,
|
|
153
207
|
files,
|
|
154
208
|
learnings,
|
|
209
|
+
insights,
|
|
155
210
|
decisions,
|
|
156
211
|
nextSteps,
|
|
157
212
|
tags,
|
|
213
|
+
commits,
|
|
214
|
+
initialCommit,
|
|
215
|
+
worker: resolveWorker(options, command, payload),
|
|
158
216
|
trigger,
|
|
159
217
|
company,
|
|
160
218
|
sessionId,
|
|
@@ -190,19 +248,49 @@ function readGitState(repoDir) {
|
|
|
190
248
|
branch: read(["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
191
249
|
current_commit: read(["rev-parse", "--short", "HEAD"]),
|
|
192
250
|
dirty: read(["status", "--porcelain"]) !== "",
|
|
251
|
+
remote_url: (() => {
|
|
252
|
+
try {
|
|
253
|
+
return read(["remote", "get-url", "origin"]);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
})(),
|
|
193
259
|
};
|
|
194
260
|
}
|
|
195
261
|
catch {
|
|
196
262
|
return null;
|
|
197
263
|
}
|
|
198
264
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
265
|
+
function commitsSince(repoDir, initialCommit) {
|
|
266
|
+
try {
|
|
267
|
+
const output = execFileSync("git", ["-C", repoDir, "log", "--no-decorate", "--pretty=format:%h: %s", `${initialCommit}..HEAD`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
268
|
+
return output ? output.split("\n") : [];
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function gitStateFor(cwd, liveRoot, initialCommit, explicitCommits) {
|
|
275
|
+
const callerState = readGitState(cwd);
|
|
276
|
+
const state = callerState ??
|
|
201
277
|
readGitState(liveRoot) ?? {
|
|
202
278
|
branch: "unknown",
|
|
203
279
|
current_commit: "unknown",
|
|
204
280
|
dirty: false,
|
|
205
|
-
|
|
281
|
+
remote_url: null,
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
...state,
|
|
285
|
+
// Remote URLs and derived history describe the caller's repository only.
|
|
286
|
+
remote_url: callerState?.remote_url ?? null,
|
|
287
|
+
initial_commit: initialCommit ?? null,
|
|
288
|
+
commits_made: explicitCommits.length > 0
|
|
289
|
+
? explicitCommits
|
|
290
|
+
: initialCommit && callerState
|
|
291
|
+
? commitsSince(cwd, initialCommit)
|
|
292
|
+
: [],
|
|
293
|
+
};
|
|
206
294
|
}
|
|
207
295
|
function writeStamps(liveRoot, sessionId) {
|
|
208
296
|
const stateDir = path.join(liveRoot, "workspace", "orchestrator", "hook-state");
|
|
@@ -287,6 +375,7 @@ function siblingPayload(input, threadPath) {
|
|
|
287
375
|
summary: input.summary ?? "",
|
|
288
376
|
files: input.files,
|
|
289
377
|
learnings: input.learnings,
|
|
378
|
+
insights: input.insights,
|
|
290
379
|
decisions: input.decisions,
|
|
291
380
|
next_steps: input.nextSteps,
|
|
292
381
|
tags: input.tags,
|
|
@@ -294,6 +383,7 @@ function siblingPayload(input, threadPath) {
|
|
|
294
383
|
company: input.company ?? null,
|
|
295
384
|
session_id: input.sessionId ?? null,
|
|
296
385
|
transcript: input.transcript ?? null,
|
|
386
|
+
worker: input.worker,
|
|
297
387
|
thread_path: threadPath,
|
|
298
388
|
pending_payloads: [],
|
|
299
389
|
};
|
|
@@ -410,9 +500,15 @@ function hasGateProbeConflict(options, command) {
|
|
|
410
500
|
"summary",
|
|
411
501
|
"file",
|
|
412
502
|
"learning",
|
|
503
|
+
"insight",
|
|
413
504
|
"decision",
|
|
414
505
|
"next",
|
|
415
506
|
"tag",
|
|
507
|
+
"commit",
|
|
508
|
+
"initialCommit",
|
|
509
|
+
"worker",
|
|
510
|
+
"workerSkill",
|
|
511
|
+
"workerState",
|
|
416
512
|
"trigger",
|
|
417
513
|
"company",
|
|
418
514
|
"sessionId",
|
|
@@ -456,6 +552,7 @@ function runCheckpoint(options, command, group) {
|
|
|
456
552
|
const threadId = `T-${formatTimestamp(now)}-auto-${summarySlug(input.summary)}`;
|
|
457
553
|
const threadPath = path.join(liveRoot, "workspace", "threads", `${threadId}.json`);
|
|
458
554
|
const relativeThreadPath = path.relative(liveRoot, threadPath);
|
|
555
|
+
const git = gitStateFor(process.cwd(), liveRoot, input.initialCommit, input.commits);
|
|
459
556
|
const stampPaths = [
|
|
460
557
|
path.join(liveRoot, "workspace", "orchestrator", "hook-state", "checkpoint-cli-last"),
|
|
461
558
|
path.join(liveRoot, "workspace", "orchestrator", "hook-state", `checkpoint-cli-last-${input.sessionId ? input.sessionId.replace(/[^A-Za-z0-9._-]/g, "_") || "unknown" : "unknown"}`),
|
|
@@ -465,6 +562,11 @@ function runCheckpoint(options, command, group) {
|
|
|
465
562
|
live_root: liveRoot,
|
|
466
563
|
idle: false,
|
|
467
564
|
thread: relativeThreadPath,
|
|
565
|
+
checkpoint: {
|
|
566
|
+
git,
|
|
567
|
+
insights: input.insights,
|
|
568
|
+
worker: input.worker,
|
|
569
|
+
},
|
|
468
570
|
stamps: stampPaths.map((stampPath) => path.relative(liveRoot, stampPath)),
|
|
469
571
|
sibling: options.agent === false ? null : { backend },
|
|
470
572
|
});
|
|
@@ -478,12 +580,14 @@ function runCheckpoint(options, command, group) {
|
|
|
478
580
|
updated_at: now.toISOString(),
|
|
479
581
|
workspace_root: liveRoot,
|
|
480
582
|
cwd: process.cwd(),
|
|
481
|
-
git
|
|
583
|
+
git,
|
|
482
584
|
conversation_summary: input.summary,
|
|
483
585
|
files_touched: input.files,
|
|
484
586
|
next_steps: input.nextSteps,
|
|
485
587
|
learnings: input.learnings,
|
|
588
|
+
insights: input.insights,
|
|
486
589
|
decisions: input.decisions,
|
|
590
|
+
worker: input.worker,
|
|
487
591
|
session_id: input.sessionId ?? null,
|
|
488
592
|
metadata: {
|
|
489
593
|
title: titleFor(input.summary),
|
|
@@ -516,9 +620,15 @@ export function registerCoreCheckpointCommand(core) {
|
|
|
516
620
|
.option("--summary <text>", "1–2 sentence outcome summary")
|
|
517
621
|
.option("--file <path>", "file touched this turn", collect, [])
|
|
518
622
|
.option("--learning <text>", "reusable lesson", collect, [])
|
|
623
|
+
.option("--insight <text>", "session insight", collect, [])
|
|
519
624
|
.option("--decision <text>", "decision made", collect, [])
|
|
520
625
|
.option("--next <text>", "remaining next step", collect, [])
|
|
521
626
|
.option("--tag <tag>", "checkpoint tag", collect, [])
|
|
627
|
+
.option("--commit <entry>", "session commit (<hash>: <message>)", collect, [])
|
|
628
|
+
.option("--initial-commit <sha>", "commit at the start of this session")
|
|
629
|
+
.option("--worker <id>", "active worker identifier")
|
|
630
|
+
.option("--worker-skill <name>", "active worker skill")
|
|
631
|
+
.option("--worker-state <state>", "active worker state")
|
|
522
632
|
.option("--trigger <name>", "checkpoint trigger", DEFAULT_TRIGGER)
|
|
523
633
|
.option("--company <slug>", "active company scope")
|
|
524
634
|
.option("--session-id <id>", "caller session id (default: $CLAUDE_CODE_SESSION_ID when set)")
|