@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 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, you may read\n its tail for context (last ~200 lines); never quote secrets from it.\n2. Verify the checkpoint thread file named in the payload exists and is valid\n JSON; repair or enrich it if needed (keep its shape).\n3. For each entry in \"learnings\": if it is a reusable rule, distill it into a\n policy file under personal/policies/ (or companies/<company>/policies/ when\n the payload names a company and the lesson is company-specific), following\n core/knowledge/public/hq-core/policies-spec.md. Skip duplicates \u2014 search\n existing policies first.\n4. For durable facts (not rules), update knowledge under personal/knowledge/\n or companies/<company>/knowledge/.\n5. Hook or automation improvements go ONLY under personal/hooks/ as proposals.\n You must never write into .claude/, core/, .agents/, .codex/, or repos/.\n6. Write <runDir>/report.md \u2014 full prose: what you read, what you changed\n (paths), what you skipped and why.\n7. If workspace/checkpoints/sibling/pending.jsonl is non-empty when you\n finish, process those payloads the same way, then truncate the file.\n";
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, you may read
40
- its tail for context (last ~200 lines); never quote secrets from it.
41
- 2. Verify the checkpoint thread file named in the payload exists and is valid
42
- JSON; repair or enrich it if needed (keep its shape).
43
- 3. For each entry in "learnings": if it is a reusable rule, distill it into a
44
- policy file under personal/policies/ (or companies/<company>/policies/ when
45
- the payload names a company and the lesson is company-specific), following
46
- core/knowledge/public/hq-core/policies-spec.md. Skip duplicates — search
47
- existing policies first.
48
- 4. For durable facts (not rules), update knowledge under personal/knowledge/
49
- or companies/<company>/knowledge/.
50
- 5. Hook or automation improvements go ONLY under personal/hooks/ as proposals.
51
- You must never write into .claude/, core/, .agents/, .codex/, or repos/.
52
- 6. Write <runDir>/report.md full prose: what you read, what you changed
53
- (paths), what you skipped and why.
54
- 7. If workspace/checkpoints/sibling/pending.jsonl is non-empty when you
55
- finish, process those payloads the same way, then truncate the file.
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 gitStateFor(cwd, liveRoot) {
200
- return (readGitState(cwd) ??
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: gitStateFor(process.cwd(), liveRoot),
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)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.81.0",
3
+ "version": "5.82.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {