@indigoai-us/hq-cli 5.345.88 → 5.345.89

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.
@@ -11,6 +11,7 @@ import { buildCmdExeSpawnPlan, isWindowsBatchFile } from "../utils/windows-spawn
11
11
  import { describeCoreCommand } from "./core-command-catalog.js";
12
12
  import { resolveLiveRoot } from "../utils/hq-roots.js";
13
13
  import { runBundledScript } from "../utils/run-bundled-script.js";
14
+ import { brainstormFrontmatterSet, brainstormSummary, brainstormTitle } from "./core-delegate-brainstorm.js";
14
15
  export const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
15
16
  const SCRIPT_ASSETS = {
16
17
  resolve: "core/scripts/hq-delegate-resolve.sh",
@@ -52,7 +53,7 @@ const USAGE_TEXT = {
52
53
  pickup: "# hq-core: public\n# hq-delegate-pickup.sh — close the delegation loop with a receipt or a FAILED\n# state (2026-09-07).\n#\n# Why: the delegate pipeline ended at \"sent\", which is a send receipt, not a\n# delivery receipt. Seven delegations sat at sent/building with no way to tell\n# whether the recipient could act. This script adds the two missing states:\n#\n# sent --(evidence)--> picked-up recipient demonstrably has the work\n# sent --(silence past window)--> failed nobody picked it up; sender is told why\n#\n# Evidence, checked in order (any one suffices):\n# 1. --ack \"<text>\" an explicit acknowledgement (DM reply, chat) —\n# recorded verbatim with a timestamp\n# 2. repo branch has a commit by the recipient after sentAt\n# (manifest.repo.path + repo.branch, author email == to.principal)\n# 3. --evidence \"<free text>\" any other proof the operator observed\n#\n# Usage:\n# hq-delegate-pickup.sh --manifest <path> --ack \"<text>\" # record receipt\n# hq-delegate-pickup.sh --manifest <path> --evidence \"<text>\" # record receipt\n# hq-delegate-pickup.sh --manifest <path> --check [--window-hours N] # probe repo; fail if silent past window (default 72)\n# hq-delegate-pickup.sh --manifest <path> --status # print state line\n#\n# Exit 0: picked-up (or already). Exit 5: failed (window elapsed, no evidence).\n# Exit 6: still waiting (inside window, no evidence). Exit 2: usage/precondition.\nset -uo pipefail\n",
53
54
  repo: "hq-delegate-repo.sh — code/branch handover for a delegation: make sure the\nproject's branch exists on the remote, record uncommitted work as NOT\ntransferred, and verify the recipient's repository access read-only.\n\nUsage:\n core/scripts/hq-delegate-repo.sh --manifest <path> [--yes] [--github-user <login>]\n\nBehavior (driven by the manifest's repo{} block; no-op when repo is null):\n - records remote URL and the branch's headSha into the manifest\n - a branch that exists only locally requires confirmation: without --yes\n the helper prints the plan and exits 2; with --yes it pushes via an\n explicitly anchored `git -C <abs path> push -u origin <branch>`\n - dirty/untracked files are captured into repo.dirtyFiles[] and appended\n to BRIEF.md as work NOT transferred — never silently dropped\n - recipient access is checked read-only via gh when --github-user is\n given; otherwise (or on a gh failure) repo.accessVerified stays false\n and the brief says so plainly — the helper never claims unverified access\n - never touches vaultPrefixes[] and never pushes repo content to the vault\n\nEnv: HQ_ROOT — HQ root override (default: resolved from script location)\n",
54
55
  secrets: "hq-delegate-secrets.sh — grant a delegation recipient access to the secrets\na project needs, BY NAME ONLY. No command in this helper ever reads,\nprints, logs, or interpolates a secret value.\n\nUsage:\n core/scripts/hq-delegate-secrets.sh --manifest <path> [--yes] [--no-secrets]\n [--env-schema <path>]\n\nBehavior:\n - derives required secret NAMES from the project's / repo's .env.schema\n (--env-schema overrides discovery); no schema -> records secrets: []\n and says so plainly rather than guessing at credentials\n - without --yes: prints a full-prose confirmation listing every name and\n the principal, then exits 2 granting nothing — this step is a privilege\n handover and gets its own confirmation, separate from the file grants\n - with --yes: hq secrets share <NAME> --with <principal> --permission read\n per name, then records the names (and only the names) in the manifest\n - --no-secrets: skips everything, records {secrets: [], secretsSkipped: true}\n\nConsumption on the recipient's side is via `hq run` / `hq secrets exec` —\ndocumented in the brief; values never appear in any delegation artifact\n(policy hq-delegate-never-inlines-secrets-or-share-urls).\n",
55
- transfer: "hq-delegate-transfer.sh — make a delegation real everywhere HQ tracks\nownership: board entry, PRD metadata, work mesh, and the project journal.\n\nUsage:\n core/scripts/hq-delegate-transfer.sh --manifest <path>\n\nBehavior (mode \"transfer\"; mode \"share\" skips every mutation):\n 1. companies/<co>/board.json — the project's entry gets owner=<recipient>\n and a bumped updated_at; created (once) if absent\n 2. prd.json metadata gains owner, delegatedFrom, delegatedAt\n 3. work-mesh: `done` event closes the delegator's in-progress thread and\n broadcasts the reassignment; silently skipped when the helper is\n unavailable (local/offline installs) — the transfer still succeeds\n 4. a dated \"Delegated\" stanza is appended to the project journal naming\n the recipient, delegation id, and transferred scope\n\nIdempotent: re-running updates owner/timestamps in place — never a\nduplicate board entry, never a duplicated journal stanza.\n\nThe delegator's own vault access is untouched (granting the recipient\nwrite does not revoke anything).\n\nEnv: HQ_ROOT — HQ root override (default: resolved from script location)\n",
56
+ transfer: "hq-delegate-transfer.sh — make a delegation real everywhere HQ tracks\nownership: board entry, PRD metadata, work mesh, and the project journal.\n\nUsage:\n core/scripts/hq-delegate-transfer.sh --manifest <path>\n\nBehavior (mode \"transfer\"; mode \"share\" skips every mutation):\n 1. companies/<co>/board.json — the project's entry gets owner=<recipient>\n and a bumped updated_at; created (once) if absent. A brainstorm-stage\n entry (status exploring, brainstorm_path set) is matched by\n brainstorm_path and keeps its status.\n 2. prd.json metadata gains owner, delegatedFrom, delegatedAt; for a\n brainstorm-stage project the same three keys land in the\n brainstorm.md frontmatter (owner, delegated_from, delegated_at)\n 3. work-mesh: `done` event closes the delegator's in-progress thread and\n broadcasts the reassignment; silently skipped when the helper is\n unavailable (local/offline installs) — the transfer still succeeds\n 4. a dated \"Delegated\" stanza is appended to the project journal naming\n the recipient, delegation id, and transferred scope\n\nIdempotent: re-running updates owner/timestamps in place — never a\nduplicate board entry, never a duplicated journal stanza.\n\n",
56
57
  };
57
58
  function usage(name) {
58
59
  return USAGE_TEXT[name];
@@ -189,7 +190,6 @@ export function moveFile(source, target, fileSystem = fs) {
189
190
  }
190
191
  export function writeJsonAtomically(target, value, siblingTemp = false, fileSystem = fs) {
191
192
  const serialized = serializeJqJson(value, "pretty");
192
- // Native delegate JSON writers use LF on every platform.
193
193
  if (siblingTemp) {
194
194
  const temporary = `${target}.tmp.${randomBytes(6).toString("hex")}`;
195
195
  let fd;
@@ -918,14 +918,25 @@ function runTransfer(args, context) {
918
918
  }
919
919
  const now = jqRawPath(document, "ownershipTransferredAt") || new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
920
920
  const today = (process.env.HQ_DELEGATE_NOW || new Date().toISOString()).slice(0, 10);
921
+ const stage = jqRawPath(document, "project.stage", "prd");
921
922
  const prdPath = `companies/${company}/projects/${project}/prd.json`;
922
923
  const prdAbs = path.join(root, prdPath);
924
+ const brainstormPath = `companies/${company}/projects/${project}/brainstorm.md`;
925
+ const brainstormAbs = path.join(root, brainstormPath);
923
926
  const projectDir = path.join(root, "companies", company, "projects", project);
924
927
  const board = path.join(root, "companies", company, "board.json");
925
- if (!fs.existsSync(prdAbs) || !fs.statSync(prdAbs).isFile()) {
928
+ if (stage !== "prd" && stage !== "brainstorm") {
929
+ writeErr(`${prefix} manifest project.stage is '${stage}' — expected prd or brainstorm\n`);
930
+ return 1;
931
+ }
932
+ if (stage === "prd" && (!fs.existsSync(prdAbs) || !fs.statSync(prdAbs).isFile())) {
926
933
  writeErr(`${prefix} prd not found: ${prdPath}\n`);
927
934
  return 1;
928
935
  }
936
+ if (stage === "brainstorm" && (!fs.existsSync(brainstormAbs) || !fs.statSync(brainstormAbs).isFile())) {
937
+ writeErr(`${prefix} brainstorm.md not found: ${brainstormPath}\n`);
938
+ return 1;
939
+ }
929
940
  if (fs.existsSync(board)) {
930
941
  if (!jqValid(board)) {
931
942
  writeErr(`${prefix} board.json is not valid JSON: ${board}\n`);
@@ -936,9 +947,9 @@ function runTransfer(args, context) {
936
947
  fs.mkdirSync(path.dirname(board), { recursive: true });
937
948
  fs.writeFileSync(board, '{"projects": []}\n');
938
949
  }
939
- const prd = readJsonFile(prdAbs);
940
- const title = jqRawPath(prd, "name");
941
- const descriptionValue = jqFallback(jsonAt(prd, "description"), "");
950
+ const prd = stage === "prd" ? readJsonFile(prdAbs) : undefined;
951
+ const title = stage === "prd" ? jqRawPath(prd, "name") : brainstormTitle(brainstormAbs);
952
+ const descriptionValue = stage === "prd" ? jqFallback(jsonAt(prd, "description"), "") : brainstormSummary(brainstormAbs);
942
953
  const description = Buffer.from(serializeJqJson(descriptionValue, "raw"), "utf8")
943
954
  .subarray(0, 300).toString("utf8").replace(/\n+$/, "");
944
955
  const boardDocument = readJsonFile(board);
@@ -948,35 +959,46 @@ function runTransfer(args, context) {
948
959
  const projects = Array.isArray(projectsValue)
949
960
  ? projectsValue
950
961
  : isRecord(projectsValue) ? Object.values(projectsValue) : [];
951
- const hasProject = projects.some((entry) => jsonAt(entry, "prd_path") === prdPath);
962
+ const matchPath = stage === "prd" ? prdPath : brainstormPath;
963
+ const hasProject = projects.some((entry) => jsonAt(entry, stage === "prd" ? "prd_path" : "brainstorm_path") === matchPath);
952
964
  if (hasProject) {
953
- boardDocument.projects = projects.map((entry) => jsonAt(entry, "prd_path") === prdPath && isRecord(entry)
965
+ boardDocument.projects = projects.map((entry) => jsonAt(entry, stage === "prd" ? "prd_path" : "brainstorm_path") === matchPath && isRecord(entry)
954
966
  ? { ...entry, owner: principal, updated_at: now }
955
967
  : entry);
956
968
  }
957
969
  else {
958
- boardDocument.projects = [...projects, {
959
- id: `proj-${project}`,
960
- title,
961
- description,
962
- status: "in_progress",
963
- owner: principal,
964
- prd_path: prdPath,
965
- created_at: now,
966
- updated_at: now,
967
- }];
970
+ const entry = {
971
+ id: `proj-${project}`,
972
+ title,
973
+ description,
974
+ status: stage === "prd" ? "in_progress" : "exploring",
975
+ owner: principal,
976
+ prd_path: stage === "prd" ? prdPath : null,
977
+ created_at: now,
978
+ updated_at: now,
979
+ };
980
+ if (stage === "brainstorm")
981
+ entry.brainstorm_path = brainstormPath;
982
+ boardDocument.projects = [...projects, entry];
968
983
  }
969
984
  boardDocument.updated_at = now;
970
985
  writeJsonAtomically(board, boardDocument);
971
- if (!isRecord(prd))
972
- throw new TypeError("Cannot update non-object prd.json");
973
- if (!isRecord(prd.metadata))
974
- prd.metadata = {};
975
- const metadata = prd.metadata;
976
- metadata.owner = principal;
977
- metadata.delegatedFrom = from;
978
- metadata.delegatedAt = now;
979
- writeJsonAtomically(prdAbs, prd);
986
+ if (stage === "prd") {
987
+ if (!isRecord(prd))
988
+ throw new TypeError("Cannot update non-object prd.json");
989
+ if (!isRecord(prd.metadata))
990
+ prd.metadata = {};
991
+ const metadata = prd.metadata;
992
+ metadata.owner = principal;
993
+ metadata.delegatedFrom = from;
994
+ metadata.delegatedAt = now;
995
+ writeJsonAtomically(prdAbs, prd);
996
+ }
997
+ else {
998
+ brainstormFrontmatterSet(brainstormAbs, "owner", principal);
999
+ brainstormFrontmatterSet(brainstormAbs, "delegated_from", from);
1000
+ brainstormFrontmatterSet(brainstormAbs, "delegated_at", now);
1001
+ }
980
1002
  if (commandExists("hq", context.cwd)) {
981
1003
  const sessionId = process.env.HQ_SESSION_ID || "delegate";
982
1004
  const mesh = subprocess("hq", ["mesh", "session", "note", "--enqueue", "--session-id", sessionId, "--seq", "1", "--harness", "claude-code", "--adapter-version", "1.0.0", "--company-slug", company, "--project", project, "--summary", `Delegated to ${display} (${principal}) — delegation ${delegationId}; ownership transferred`], context.cwd, { stdout: "inherit" });
@@ -993,12 +1015,17 @@ function runTransfer(args, context) {
993
1015
  const prefixesValue = jqFallback(document.vaultPrefixes, []);
994
1016
  const vaultPrefixes = Array.isArray(prefixesValue) ? prefixesValue : [];
995
1017
  const scope = vaultPrefixes.map((entry) => jqRawPath(entry, "prefix")).join(", ");
996
- fs.appendFileSync(journal, `\n## ${today} — Delegated to ${display}\n\n- Delegation: \`${delegationId}\`\n- From: ${from}\n- To: ${display} (${principal})\n- Transferred scope: ${scope}\n- Local board and PRD ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged.\n`);
1018
+ const ownershipLine = stage === "prd"
1019
+ ? "Local board and PRD ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged."
1020
+ : "Brainstorm-stage project (no PRD yet). Local board and brainstorm.md ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged.";
1021
+ fs.appendFileSync(journal, `\n## ${today} — Delegated to ${display}\n\n- Delegation: \`${delegationId}\`\n- From: ${from}\n- To: ${display} (${principal})\n- Transferred scope: ${scope}\n- ${ownershipLine}\n`);
997
1022
  }
998
1023
  document.ownershipTransferredAt = now;
999
1024
  document.workMeshOwnership = "unconfirmed";
1000
1025
  writeJsonAtomically(file, document);
1001
- writeOut(`${prefix} ownership of '${project}' transferred to ${display} (${principal}) — local board, PRD, and journal updated; Work Mesh ownership unconfirmed\n`);
1026
+ const sourceLabel = stage === "prd" ? "PRD" : "brainstorm.md";
1027
+ const stageLabel = stage === "prd" ? "" : " (brainstorm stage)";
1028
+ writeOut(`${prefix} ownership of '${project}'${stageLabel} transferred to ${display} (${principal}) — local board, ${sourceLabel}, and journal updated; Work Mesh ownership unconfirmed\n`);
1002
1029
  return 0;
1003
1030
  }
1004
1031
  function runNative(name, args, context) {
@@ -268,6 +268,7 @@ export const SCAFFOLD_ONLY_ASSETS = [
268
268
  { asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
269
269
  { asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
270
270
  { asset: "core/scripts/lib/hq-cli-floor.sh", root: "live", summary: "Shared CLI version-floor helper sourced by generated forwarders" },
271
+ { asset: "core/scripts/lib/delegate-brainstorm.sh", root: "live", summary: "Shared brainstorm-stage data helper sourced by delegate assets" },
271
272
  { asset: "core/scripts/lib/forwarder.sh", root: "live", summary: "Shared runtime sourced by generated scaffold forwarders" },
272
273
  { asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
273
274
  { asset: "core/scripts/lib/work-mesh-live-hook.sh", root: "live", summary: "Work Mesh Live hook helpers sourced by rebind" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.88",
3
+ "version": "5.345.89",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {