@indigoai-us/hq-cli 5.345.87 → 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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.89] — 2026-10-11
6
+ - Delegation commands now support projects that have a brainstorm and dossier but no PRD yet. The native route and bundled shell fallback build, transfer, publish, and send the brainstorm-stage handoff consistently.
7
+
8
+ ## [5.345.88] — 2026-10-11
9
+ - Work Mesh no longer crash-loops through memory during an internet outage:
10
+ failed presence and membership lookups back off and log once per drop, and a
11
+ downed network stops the held-event flush instead of retrying every session.
12
+ Held events that can be matched to the device's default company are delivered
13
+ instead of being re-queued forever.
14
+
5
15
  ## [5.345.87] — 2026-10-11
6
16
  - `hq bot create setup` no longer fails when HQ's assistant is already on this
7
17
  computer. It answers `ok` with `outcome` `existing`, `adopted` or `created`,
@@ -16,9 +16,16 @@
16
16
  # prompt) — schema documented in
17
17
  # core/knowledge/public/hq-core/delegation-bundle-spec.md.
18
18
  #
19
+ # Source of truth is prd.json. When the project has no prd.json yet but has a
20
+ # brainstorm.md (a brainstorm-stage project, policy
21
+ # hq-brainstorm-leaves-handoffable-project-folder), brainstorm.md is the source
22
+ # of truth: the dossier is brainstorm.md, research/**, and the newest journal
23
+ # note; the brief says so and recommends /plan as the next step.
24
+ #
19
25
  # Fails closed (non-zero, nothing written) when:
20
26
  # - required arguments are missing
21
- # - the project directory or its prd.json does not exist / parses invalid
27
+ # - the project directory does not exist, or it has neither prd.json nor
28
+ # brainstorm.md, or its prd.json parses invalid
22
29
  # - the generated manifest or BRIEF matches a secret-detection pattern
23
30
  #
24
31
  # Env:
@@ -31,6 +38,8 @@ HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
31
38
 
32
39
  # shellcheck source=lib/secret-patterns.sh
33
40
  . "$SCRIPT_DIR/lib/secret-patterns.sh"
41
+ # shellcheck source=lib/delegate-brainstorm.sh
42
+ . "$SCRIPT_DIR/lib/delegate-brainstorm.sh"
34
43
 
35
44
  usage() {
36
45
  sed -n '3,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
@@ -70,10 +79,16 @@ case "$MODE" in transfer|share) ;; *) die "--mode must be transfer or share" ;;
70
79
  case "$TO_KIND" in person|agent) ;; *) die "--to-kind must be person or agent" ;; esac
71
80
 
72
81
  PROJECT_DIR="$HQ_ROOT/companies/$COMPANY/projects/$PROJECT"
82
+ PROJECT_REL="companies/$COMPANY/projects/$PROJECT"
73
83
  PRD="$PROJECT_DIR/prd.json"
74
- [ -d "$PROJECT_DIR" ] || die "project directory not found: companies/$COMPANY/projects/$PROJECT"
75
- [ -f "$PRD" ] || die "project has no prd.json: companies/$COMPANY/projects/$PROJECT/prd.json"
76
- jq -e . "$PRD" >/dev/null 2>&1 || die "prd.json is not valid JSON: $PRD"
84
+ BRAINSTORM="$PROJECT_DIR/brainstorm.md"
85
+ [ -d "$PROJECT_DIR" ] || die "project directory not found: $PROJECT_REL"
86
+ STAGE="$(hq_delegate_project_stage "$PROJECT_DIR")"
87
+ case "$STAGE" in
88
+ prd) jq -e . "$PRD" >/dev/null 2>&1 || die "prd.json is not valid JSON: $PRD" ;;
89
+ brainstorm) ;;
90
+ *) die "project has neither prd.json nor brainstorm.md: $PROJECT_REL (run /brainstorm or /plan first)" ;;
91
+ esac
77
92
 
78
93
  # --- identity ----------------------------------------------------------------
79
94
 
@@ -104,8 +119,14 @@ PROJECT_PREFIX="projects/$PROJECT/"
104
119
 
105
120
  # knowledge entries from prd metadata: split into vault-grantable (under this
106
121
  # company), policies (under this company's policies/), and repo-based docs
107
- # (recorded for the brief, never granted as vault prefixes).
108
- KNOWLEDGE_JSON="$(jq -c '[.metadata.knowledge // [] | .[]]' "$PRD")"
122
+ # (recorded for the brief, never granted as vault prefixes). A brainstorm-stage
123
+ # project keeps its research inside the project folder, so there is nothing
124
+ # outside the dossier prefix to grant.
125
+ if [ "$STAGE" = prd ]; then
126
+ KNOWLEDGE_JSON="$(jq -c '[.metadata.knowledge // [] | .[]]' "$PRD")"
127
+ else
128
+ KNOWLEDGE_JSON="[]"
129
+ fi
109
130
 
110
131
  VAULT_READ_PREFIXES="$(printf '%s' "$KNOWLEDGE_JSON" | jq -r --arg co "$COMPANY" '
111
132
  [ .[]
@@ -142,7 +163,8 @@ printf '%s' "$VAULT_PREFIXES_JSON" | jq -e '
142
163
 
143
164
  # --- repo block (recorded here; verified/pushed by the US-004 step) ----------
144
165
 
145
- REPO_PATH="$(jq -r '.metadata.repoPath // empty' "$PRD")"
166
+ REPO_PATH=""
167
+ [ "$STAGE" = prd ] && REPO_PATH="$(jq -r '.metadata.repoPath // empty' "$PRD")"
146
168
  if [ -n "$REPO_PATH" ]; then
147
169
  REPO_JSON="$(jq -cn \
148
170
  --arg path "$REPO_PATH" \
@@ -156,14 +178,33 @@ fi
156
178
 
157
179
  # --- board id ----------------------------------------------------------------
158
180
 
181
+ # A PRD project is matched by prd_path; a brainstorm-stage entry (status
182
+ # exploring) carries brainstorm_path and a null prd_path.
159
183
  BOARD_ID="null"
160
184
  BOARD_FILE="$HQ_ROOT/companies/$COMPANY/board.json"
161
185
  if [ -f "$BOARD_FILE" ]; then
162
- BOARD_ID="$(jq --arg prd "companies/$COMPANY/projects/$PROJECT/prd.json" \
163
- '[.projects // [] | .[] | select(.prd_path == $prd) | .id] | first // null' \
186
+ BOARD_ID="$(jq --arg prd "$PROJECT_REL/prd.json" --arg bs "$PROJECT_REL/brainstorm.md" \
187
+ '[.projects // [] | .[] | select(.prd_path == $prd or .brainstorm_path == $bs) | .id] | first // null' \
164
188
  "$BOARD_FILE" 2>/dev/null || echo null)"
165
189
  fi
166
190
 
191
+ # --- stage-specific project block ---------------------------------------------
192
+ #
193
+ # prdPath stays the PRD contract for every downstream helper; a brainstorm-stage
194
+ # project sets it to null, names brainstorm.md as sourcePath, and lists the
195
+ # dossier files (brainstorm.md, research/**, newest journal note) that the
196
+ # publish step must ship.
197
+
198
+ if [ "$STAGE" = prd ]; then
199
+ PROJECT_JSON="$(jq -cn --arg name "$PROJECT" --arg prd "$PROJECT_REL/prd.json" --argjson boardId "$BOARD_ID" \
200
+ '{name: $name, stage: "prd", prdPath: $prd, sourcePath: $prd, boardId: $boardId}')"
201
+ else
202
+ DOSSIER_JSON="$(hq_brainstorm_dossier "$HQ_ROOT" "$PROJECT_REL" | jq -R . | jq -cs .)"
203
+ PROJECT_JSON="$(jq -cn --arg name "$PROJECT" --arg bs "$PROJECT_REL/brainstorm.md" \
204
+ --argjson boardId "$BOARD_ID" --argjson dossier "$DOSSIER_JSON" \
205
+ '{name: $name, stage: "brainstorm", prdPath: null, sourcePath: $bs, boardId: $boardId, dossier: $dossier}')"
206
+ fi
207
+
167
208
  # --- checksums of the project dossier ----------------------------------------
168
209
 
169
210
  CHECKSUMS_JSON="$(
@@ -176,8 +217,8 @@ CHECKSUMS_JSON="$(
176
217
 
177
218
  # --- build in a temp dir, scan, then move into place -------------------------
178
219
 
179
- STAGE="$(mktemp -d)"
180
- trap 'rm -rf "$STAGE"' EXIT
220
+ STAGE_DIR="$(mktemp -d)"
221
+ trap 'rm -rf "$STAGE_DIR"' EXIT
181
222
 
182
223
  jq -n \
183
224
  --arg id "$DELEGATION_ID" \
@@ -189,9 +230,7 @@ jq -n \
189
230
  --arg toPrincipal "$TO" \
190
231
  --arg toName "$TO_NAME" \
191
232
  --arg company "$COMPANY" \
192
- --arg project "$PROJECT" \
193
- --arg prdPath "companies/$COMPANY/projects/$PROJECT/prd.json" \
194
- --argjson boardId "$BOARD_ID" \
233
+ --argjson project "$PROJECT_JSON" \
195
234
  --argjson vaultPrefixes "$VAULT_PREFIXES_JSON" \
196
235
  --argjson repo "$REPO_JSON" \
197
236
  --argjson knowledge "$MANIFEST_KNOWLEDGE" \
@@ -207,7 +246,7 @@ jq -n \
207
246
  to: {kind: $toKind, principal: $toPrincipal,
208
247
  displayName: (if $toName == "" then null else $toName end)},
209
248
  company: $company,
210
- project: {name: $project, prdPath: $prdPath, boardId: $boardId},
249
+ project: $project,
211
250
  vaultPrefixes: $vaultPrefixes,
212
251
  repo: $repo,
213
252
  secrets: [],
@@ -215,10 +254,11 @@ jq -n \
215
254
  policies: $policies,
216
255
  checksums: $checksums,
217
256
  status: "building"
218
- }' > "$STAGE/manifest.json"
257
+ }' > "$STAGE_DIR/manifest.json"
219
258
 
220
259
  # --- BRIEF.md — full prose for a reader who has never seen the project -------
221
260
 
261
+ if [ "$STAGE" = prd ]; then
222
262
  DESCRIPTION="$(jq -r '.description // "No description recorded."' "$PRD")"
223
263
  GOAL="$(jq -r '.metadata.goal // empty' "$PRD")"
224
264
  TOTAL_STORIES="$(jq -r '[.userStories // [] | .[]] | length' "$PRD")"
@@ -278,19 +318,65 @@ TRAPS="$(jq -r '[.metadata.securityNotes // empty, (.metadata.executionConventio
278
318
  echo "## Where everything lives"
279
319
  echo
280
320
  echo "The full PRD (every story, acceptance criteria, and decision history) is at \`companies/$COMPANY/projects/$PROJECT/prd.json\`. The pickup prompt that accompanied this brief pulls every file you need on demand — you do not need to run a full sync."
281
- } > "$STAGE/BRIEF.md"
321
+ } > "$STAGE_DIR/BRIEF.md"
322
+ else
323
+ # Brainstorm stage: no stories, no PRD. The brief says so plainly and points
324
+ # the recipient at /plan.
325
+ SUMMARY="$(hq_brainstorm_summary "$BRAINSTORM")"
326
+ [ -n "$SUMMARY" ] || SUMMARY="No summary recorded in brainstorm.md."
327
+ RECOMMENDATION="$(hq_brainstorm_section "$BRAINSTORM" "Recommendation")"
328
+ UNKNOWNS="$(hq_brainstorm_section "$BRAINSTORM" "What We Don't Know")"
329
+ DOSSIER_LIST="$(printf '%s' "$PROJECT_JSON" | jq -r '.dossier[] | "- `" + . + "`"')"
330
+ {
331
+ echo "# Delegation brief — $PROJECT"
332
+ echo
333
+ echo "This brief describes the **$PROJECT** project in the **$COMPANY** company. Completed handoff steps are recorded in the manifest. Pull the files and acknowledge receipt; report any access error to the sender."
334
+ echo
335
+ echo "## What this project is"
336
+ echo
337
+ echo "$SUMMARY"
338
+ echo
339
+ echo "## Where things stand"
340
+ echo
341
+ echo "This project is at the brainstorm stage. There is no PRD yet and no stories have been written; the source of truth is \`$PROJECT_REL/brainstorm.md\`, with research notes and the latest session journal alongside it."
342
+ echo
343
+ if [ -n "$RECOMMENDATION" ]; then
344
+ echo "## Recommendation so far"
345
+ echo
346
+ echo "$RECOMMENDATION"
347
+ echo
348
+ fi
349
+ if [ -n "$UNKNOWNS" ]; then
350
+ echo "## Open questions"
351
+ echo
352
+ echo "$UNKNOWNS"
353
+ echo
354
+ fi
355
+ echo "## Recommended next step"
356
+ echo
357
+ echo "Run \`/plan $PROJECT\` to turn the brainstorm into a PRD with stories. Read brainstorm.md and the research notes first; the recommendation and open questions above come from them."
358
+ echo
359
+ echo "## Where everything lives"
360
+ echo
361
+ echo "The dossier for this handoff:"
362
+ echo
363
+ echo "$DOSSIER_LIST"
364
+ echo
365
+ echo "The pickup prompt that accompanied this brief pulls every file you need on demand — you do not need to run a full sync."
366
+ } > "$STAGE_DIR/BRIEF.md"
367
+ fi
282
368
 
283
369
  # --- fail-closed secret scan -------------------------------------------------
284
370
 
285
- if ! hq_scan_secrets "$STAGE/manifest.json" "$STAGE/BRIEF.md"; then
371
+ if ! hq_scan_secrets "$STAGE_DIR/manifest.json" "$STAGE_DIR/BRIEF.md"; then
286
372
  die "generated bundle matched a secret-detection pattern — nothing written (see stderr above)"
287
373
  fi
288
374
 
289
375
  # --- dry run: print the manifest, write nothing ------------------------------
290
376
 
291
377
  if [ "$DRY_RUN" -eq 1 ]; then
292
- cat "$STAGE/manifest.json"
293
- rm -rf "$STAGE"
378
+ cat "$STAGE_DIR/manifest.json"
379
+ rm -rf "$STAGE_DIR"
294
380
  trap - EXIT
295
381
  echo "hq-delegate-bundle: dry run — nothing written" >&2
296
382
  exit 0
@@ -300,7 +386,7 @@ fi
300
386
 
301
387
  DEST="$HQ_ROOT/workspace/delegations/$DELEGATION_ID"
302
388
  mkdir -p "$(dirname "$DEST")"
303
- mv "$STAGE" "$DEST"
389
+ mv "$STAGE_DIR" "$DEST"
304
390
  trap - EXIT
305
391
 
306
392
  echo "$DELEGATION_ID"
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env bash
2
2
  # hq-core: public
3
3
  # Publish final delegation artifacts and verify their canonical cloud bytes.
4
+ # PRD stage snapshots prd.json, README.md, and the delegation journal.
5
+ # Brainstorm stage (manifest project.stage == "brainstorm") snapshots the
6
+ # manifest's dossier list (brainstorm.md, research/**, newest journal note)
7
+ # plus the delegation journal, and checks ownership in the brainstorm.md
8
+ # frontmatter instead of prd.json.
4
9
  set -euo pipefail
5
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
11
  HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
7
12
  . "$SCRIPT_DIR/lib/secret-patterns.sh"
13
+ . "$SCRIPT_DIR/lib/delegate-brainstorm.sh"
8
14
  die() { echo "hq-delegate-publish: $*" >&2; exit 1; }
9
15
  [ "${1:-}" = --manifest ] && [ -f "${2:-}" ] || die 'usage: --manifest <path>'
10
16
  MANIFEST="$2"
@@ -33,10 +39,19 @@ REL="companies/$COMPANY/projects/$PROJECT"
33
39
  PROJECT_DIR="$HQ_ROOT/$REL"
34
40
  BUNDLE="$(cd "$(dirname "$MANIFEST")" && pwd)"
35
41
  [ -f "$BUNDLE/BRIEF.md" ] || die 'BRIEF.md missing'
36
- [ -f "$PROJECT_DIR/prd.json" ] || die 'PRD missing'
42
+ STAGE_KIND="$(jq -r '.project.stage // "prd"' "$MANIFEST")"
43
+ case "$STAGE_KIND" in
44
+ prd) [ -f "$PROJECT_DIR/prd.json" ] || die 'PRD missing' ;;
45
+ brainstorm) [ -f "$PROJECT_DIR/brainstorm.md" ] || die 'brainstorm.md missing' ;;
46
+ *) die "unknown project stage '$STAGE_KIND'" ;;
47
+ esac
37
48
  if [ "$(jq -r '.mode' "$MANIFEST")" = transfer ]; then
38
49
  EXPECTED_OWNER="$(jq -r '.to.principal' "$MANIFEST")"
39
- jq -e --arg owner "$EXPECTED_OWNER" '.metadata.owner == $owner' "$PROJECT_DIR/prd.json" >/dev/null || die 'local PRD owner does not match recipient'
50
+ if [ "$STAGE_KIND" = prd ]; then
51
+ jq -e --arg owner "$EXPECTED_OWNER" '.metadata.owner == $owner' "$PROJECT_DIR/prd.json" >/dev/null || die 'local PRD owner does not match recipient'
52
+ else
53
+ [ "$(hq_brainstorm_frontmatter_get "$PROJECT_DIR/brainstorm.md" owner)" = "$EXPECTED_OWNER" ] || die 'local brainstorm.md owner does not match recipient'
54
+ fi
40
55
  fi
41
56
  STAGE="$(mktemp -d)"
42
57
  atomic_copy() {
@@ -52,10 +67,26 @@ file_hash() {
52
67
  }
53
68
  # Snapshot final project content, excluding mutable/self-referential receipts.
54
69
  : > "$STAGE/hashes"
55
- for file in "$PROJECT_DIR/prd.json" "$PROJECT_DIR/README.md" "$PROJECT_DIR/journal/delegations.md"; do
56
- [ -f "$file" ] || continue
70
+ if [ "$STAGE_KIND" = prd ]; then
71
+ SNAPSHOT_FILES="$PROJECT_DIR/prd.json
72
+ $PROJECT_DIR/README.md
73
+ $PROJECT_DIR/journal/delegations.md"
74
+ else
75
+ SNAPSHOT_FILES="$(jq -r '.project.dossier[]' "$MANIFEST" | sed "s|^|$HQ_ROOT/|")
76
+ $PROJECT_DIR/journal/delegations.md"
77
+ # Every dossier file the manifest promised must still exist.
78
+ while IFS= read -r file; do
79
+ [ -f "$file" ] || die "dossier file missing: ${file#"$HQ_ROOT/"}"
80
+ done <<EOF
81
+ $(jq -r '.project.dossier[]' "$MANIFEST" | sed "s|^|$HQ_ROOT/|")
82
+ EOF
83
+ fi
84
+ while IFS= read -r file; do
85
+ [ -n "$file" ] && [ -f "$file" ] || continue
57
86
  jq -cn --arg key "${file#"$HQ_ROOT/"}" --arg value "$(file_hash "$file")" '{key:$key,value:$value}' >> "$STAGE/hashes"
58
- done
87
+ done <<EOF
88
+ $SNAPSHOT_FILES
89
+ EOF
59
90
  jq -cn --arg key "$REL/delegation/$ID/BRIEF.md" --arg value "$(file_hash "$STAGE/BRIEF.md")" '{key:$key,value:$value}' >> "$STAGE/hashes"
60
91
  jq -s 'from_entries' "$STAGE/hashes" > "$STAGE/checksums"
61
92
  jq --slurpfile hashes "$STAGE/checksums" '.checksums=$hashes[0] | .status="granted" | .recipientAccess="unconfirmed" | del(.publication,.verifiedAt,.sentAt,.dmEventId)' "$MANIFEST" > "$STAGE/manifest.json"
@@ -34,6 +34,8 @@ HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
34
34
 
35
35
  # shellcheck source=lib/secret-patterns.sh
36
36
  . "$SCRIPT_DIR/lib/secret-patterns.sh"
37
+ # shellcheck source=lib/delegate-brainstorm.sh
38
+ . "$SCRIPT_DIR/lib/delegate-brainstorm.sh"
37
39
 
38
40
  usage() { sed -n '3,27p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
39
41
  die() { echo "hq-delegate-send: $*" >&2; exit 1; }
@@ -100,12 +102,25 @@ MODE="$(jq -r '.mode // "transfer"' "$MANIFEST")"
100
102
  [ -n "$PROJECT" ] || die "manifest has no project.name"
101
103
  [ -n "$PRINCIPAL" ] || die "manifest has no to.principal"
102
104
 
103
- PRD_ABS="$HQ_ROOT/$(jq -r '.project.prdPath' "$MANIFEST")"
105
+ STAGE="$(jq -r '.project.stage // "prd"' "$MANIFEST")"
106
+ PRD_REL="$(jq -r '.project.prdPath // empty' "$MANIFEST")"
107
+ PRD_ABS="${PRD_REL:+$HQ_ROOT/$PRD_REL}"
108
+ BRAINSTORM_ABS="$HQ_ROOT/companies/$COMPANY/projects/$PROJECT/brainstorm.md"
104
109
 
105
110
  # --- assemble prompt sections from the manifest (single source of truth) -----
106
111
 
107
112
  GOAL="" STATE_LINE="" NEXT_STEPS=""
108
- if [ -f "$PRD_ABS" ] && jq -e . "$PRD_ABS" >/dev/null 2>&1; then
113
+ DOSSIER_LINE="Start with the project dossier: \`companies/$COMPANY/projects/$PROJECT/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal."
114
+ if [ "$STAGE" = brainstorm ]; then
115
+ # Brainstorm stage: no PRD, no stories. The next steps are to read the
116
+ # brainstorm and turn it into a PRD with /plan.
117
+ [ -f "$BRAINSTORM_ABS" ] && GOAL="$(hq_brainstorm_summary "$BRAINSTORM_ABS")"
118
+ STATE_LINE="This project is at the brainstorm stage: no PRD yet, no stories. The source of truth is \`companies/$COMPANY/projects/$PROJECT/brainstorm.md\`."
119
+ NEXT_STEPS="1. **Read the brainstorm** — \`brainstorm.md\`, the notes under \`research/\`, and the latest journal entry under \`journal/\`.
120
+ 2. **Turn it into a PRD** — run \`/plan $PROJECT\` to write \`prd.json\` with stories.
121
+ 3. **Start the first story** — once the PRD exists, \`/run-project $PROJECT\` or \`/execute-task\` picks it up."
122
+ DOSSIER_LINE="Start with the project dossier: \`companies/$COMPANY/projects/$PROJECT/\` contains \`brainstorm.md\`, the \`research/\` notes, the latest journal entry, a full brief (the DM's details pane), and the delegation journal. There is no \`prd.json\` yet."
123
+ elif [ -n "$PRD_ABS" ] && [ -f "$PRD_ABS" ] && jq -e . "$PRD_ABS" >/dev/null 2>&1; then
109
124
  GOAL="$(jq -r '.metadata.goal // .description // ""' "$PRD_ABS")"
110
125
  TOTAL="$(jq -r '[.userStories // [] | .[]] | length' "$PRD_ABS")"
111
126
  DONE="$(jq -r '[.userStories // [] | .[] | select(.passes == true)] | length' "$PRD_ABS")"
@@ -195,7 +210,7 @@ trap 'rm -rf "$STAGE"; [ "$SEND" -eq 0 ] || rmdir "$LOCK"' EXIT
195
210
  printf '%s\n' "$GET_LINES"
196
211
  echo '```'
197
212
  echo
198
- echo "Start with the project dossier: \`companies/$COMPANY/projects/$PROJECT/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal."
213
+ echo "$DOSSIER_LINE"
199
214
  echo
200
215
  if [ -n "$REPO_SECTION" ]; then
201
216
  printf '%s\n' "$REPO_SECTION"
@@ -8,8 +8,12 @@
8
8
  #
9
9
  # Behavior (mode "transfer"; mode "share" skips every mutation):
10
10
  # 1. companies/<co>/board.json — the project's entry gets owner=<recipient>
11
- # and a bumped updated_at; created (once) if absent
12
- # 2. prd.json metadata gains owner, delegatedFrom, delegatedAt
11
+ # and a bumped updated_at; created (once) if absent. A brainstorm-stage
12
+ # entry (status exploring, brainstorm_path set) is matched by
13
+ # brainstorm_path and keeps its status.
14
+ # 2. prd.json metadata gains owner, delegatedFrom, delegatedAt; for a
15
+ # brainstorm-stage project the same three keys land in the
16
+ # brainstorm.md frontmatter (owner, delegated_from, delegated_at)
13
17
  # 3. work-mesh: `done` event closes the delegator's in-progress thread and
14
18
  # broadcasts the reassignment; silently skipped when the helper is
15
19
  # unavailable (local/offline installs) — the transfer still succeeds
@@ -32,6 +36,9 @@ HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
32
36
  usage() { sed -n '3,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
33
37
  die() { echo "hq-delegate-transfer: $*" >&2; exit 1; }
34
38
 
39
+ # shellcheck source=lib/delegate-brainstorm.sh
40
+ . "$SCRIPT_DIR/lib/delegate-brainstorm.sh"
41
+
35
42
  command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
36
43
 
37
44
  MANIFEST=""
@@ -68,12 +75,20 @@ jq -e '.status == "granted"' "$MANIFEST" >/dev/null || die "transfer requires co
68
75
  NOW="$(jq -r '.ownershipTransferredAt // empty' "$MANIFEST")"
69
76
  [ -n "$NOW" ] || NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
70
77
  TODAY="$(date -u +%Y-%m-%d)"
71
- PRD_PATH="companies/$COMPANY/projects/$PROJECT/prd.json"
78
+ STAGE="$(jq -r '.project.stage // "prd"' "$MANIFEST")"
79
+ PROJECT_REL="companies/$COMPANY/projects/$PROJECT"
80
+ PRD_PATH="$PROJECT_REL/prd.json"
72
81
  PRD_ABS="$HQ_ROOT/$PRD_PATH"
73
- PROJECT_DIR="$HQ_ROOT/companies/$COMPANY/projects/$PROJECT"
82
+ BRAINSTORM_PATH="$PROJECT_REL/brainstorm.md"
83
+ BRAINSTORM_ABS="$HQ_ROOT/$BRAINSTORM_PATH"
84
+ PROJECT_DIR="$HQ_ROOT/$PROJECT_REL"
74
85
  BOARD="$HQ_ROOT/companies/$COMPANY/board.json"
75
86
 
76
- [ -f "$PRD_ABS" ] || die "prd not found: $PRD_PATH"
87
+ case "$STAGE" in
88
+ prd) [ -f "$PRD_ABS" ] || die "prd not found: $PRD_PATH" ;;
89
+ brainstorm) [ -f "$BRAINSTORM_ABS" ] || die "brainstorm.md not found: $BRAINSTORM_PATH" ;;
90
+ *) die "manifest project.stage is '$STAGE' — expected prd or brainstorm" ;;
91
+ esac
77
92
 
78
93
  # --- 1. board entry: update in place, create once if absent ------------------
79
94
 
@@ -83,47 +98,72 @@ else
83
98
  echo '{"projects": []}' > "$BOARD"
84
99
  fi
85
100
 
86
- TITLE="$(jq -r '.name // empty' "$PRD_ABS")"
87
- DESC="$(jq -r '.description // ""' "$PRD_ABS" | head -c 300)"
101
+ if [ "$STAGE" = prd ]; then
102
+ TITLE="$(jq -r '.name // empty' "$PRD_ABS")"
103
+ DESC="$(jq -r '.description // ""' "$PRD_ABS" | head -c 300)"
104
+ MATCH_PRD="$PRD_PATH"
105
+ MATCH_BS=""
106
+ NEW_STATUS="in_progress"
107
+ else
108
+ TITLE="$(hq_brainstorm_title "$BRAINSTORM_ABS")"
109
+ DESC="$(hq_brainstorm_summary "$BRAINSTORM_ABS" | head -c 300)"
110
+ MATCH_PRD=""
111
+ MATCH_BS="$BRAINSTORM_PATH"
112
+ NEW_STATUS="exploring"
113
+ fi
114
+ [ -n "$TITLE" ] || TITLE="$PROJECT"
88
115
 
116
+ # An existing entry is matched by prd_path (PRD stage) or brainstorm_path
117
+ # (brainstorm stage). The match is by path, so a brainstorm entry whose
118
+ # prd_path is null still updates in place and keeps its status.
89
119
  TMP_BOARD="$(mktemp)"
90
120
  jq \
91
- --arg prd "$PRD_PATH" \
121
+ --arg prd "$MATCH_PRD" \
122
+ --arg bs "$MATCH_BS" \
92
123
  --arg owner "$PRINCIPAL" \
93
124
  --arg now "$NOW" \
94
125
  --arg title "$TITLE" \
95
126
  --arg desc "$DESC" \
96
127
  --arg project "$PROJECT" \
128
+ --arg status "$NEW_STATUS" \
97
129
  '
98
- if ([.projects // [] | .[] | select(.prd_path == $prd)] | length) > 0 then
130
+ def matches: ($prd != "" and .prd_path == $prd) or ($bs != "" and .brainstorm_path == $bs);
131
+ if ([.projects // [] | .[] | select(matches)] | length) > 0 then
99
132
  .projects = [.projects[] |
100
- if .prd_path == $prd then .owner = $owner | .updated_at = $now else . end]
133
+ if matches then .owner = $owner | .updated_at = $now else . end]
101
134
  else
102
135
  .projects = (.projects // []) + [{
103
136
  id: ("proj-" + $project),
104
137
  title: $title,
105
138
  description: $desc,
106
- status: "in_progress",
139
+ status: $status,
107
140
  owner: $owner,
108
- prd_path: $prd,
141
+ prd_path: (if $prd == "" then null else $prd end),
109
142
  created_at: $now,
110
143
  updated_at: $now
111
- }]
144
+ } + (if $bs == "" then {} else {brainstorm_path: $bs} end)]
112
145
  end
113
146
  | .updated_at = $now
114
147
  ' "$BOARD" > "$TMP_BOARD" && mv "$TMP_BOARD" "$BOARD"
115
148
 
116
- # --- 2. prd metadata ----------------------------------------------------------
117
-
118
- TMP_PRD="$(mktemp)"
119
- jq \
120
- --arg owner "$PRINCIPAL" \
121
- --arg from "$FROM" \
122
- --arg now "$NOW" \
123
- '.metadata.owner = $owner
124
- | .metadata.delegatedFrom = $from
125
- | .metadata.delegatedAt = $now' \
126
- "$PRD_ABS" > "$TMP_PRD" && mv "$TMP_PRD" "$PRD_ABS"
149
+ # --- 2. ownership on the source of truth -------------------------------------
150
+ # PRD stage: prd.json metadata. Brainstorm stage: brainstorm.md frontmatter.
151
+
152
+ if [ "$STAGE" = prd ]; then
153
+ TMP_PRD="$(mktemp)"
154
+ jq \
155
+ --arg owner "$PRINCIPAL" \
156
+ --arg from "$FROM" \
157
+ --arg now "$NOW" \
158
+ '.metadata.owner = $owner
159
+ | .metadata.delegatedFrom = $from
160
+ | .metadata.delegatedAt = $now' \
161
+ "$PRD_ABS" > "$TMP_PRD" && mv "$TMP_PRD" "$PRD_ABS"
162
+ else
163
+ hq_brainstorm_frontmatter_set "$BRAINSTORM_ABS" owner "$PRINCIPAL"
164
+ hq_brainstorm_frontmatter_set "$BRAINSTORM_ABS" delegated_from "$FROM"
165
+ hq_brainstorm_frontmatter_set "$BRAINSTORM_ABS" delegated_at "$NOW"
166
+ fi
127
167
 
128
168
  # --- 3. work mesh: close the delegator's thread, broadcast reassignment ------
129
169
  # Silently tolerated when unavailable (local/offline installs no-op).
@@ -162,7 +202,11 @@ if ! grep -qF "$DELEGATION_ID" "$JOURNAL_FILE"; then
162
202
  echo "- From: $FROM"
163
203
  echo "- To: $DISPLAY ($PRINCIPAL)"
164
204
  echo "- Transferred scope: $SCOPE"
165
- echo "- Local board and PRD ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged."
205
+ if [ "$STAGE" = prd ]; then
206
+ echo "- Local board and PRD ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged."
207
+ else
208
+ echo "- Brainstorm-stage project (no PRD yet). Local board and brainstorm.md ownership updated; Work Mesh ownership is unconfirmed. Existing delegator access is unchanged."
209
+ fi
166
210
  } >> "$JOURNAL_FILE"
167
211
  fi
168
212
 
@@ -172,4 +216,8 @@ TMP_MANIFEST="$(mktemp)"
172
216
  jq --arg now "$NOW" '.ownershipTransferredAt = $now | .workMeshOwnership = "unconfirmed"' "$MANIFEST" > "$TMP_MANIFEST" \
173
217
  && mv "$TMP_MANIFEST" "$MANIFEST"
174
218
 
175
- echo "hq-delegate-transfer: ownership of '$PROJECT' transferred to $DISPLAY ($PRINCIPAL) — local board, PRD, and journal updated; Work Mesh ownership unconfirmed"
219
+ if [ "$STAGE" = prd ]; then
220
+ echo "hq-delegate-transfer: ownership of '$PROJECT' transferred to $DISPLAY ($PRINCIPAL) — local board, PRD, and journal updated; Work Mesh ownership unconfirmed"
221
+ else
222
+ echo "hq-delegate-transfer: ownership of '$PROJECT' (brainstorm stage) transferred to $DISPLAY ($PRINCIPAL) — local board, brainstorm.md, and journal updated; Work Mesh ownership unconfirmed"
223
+ fi
@@ -17,7 +17,8 @@
17
17
  # core/scripts/hq-delegate-verify.sh --dry-run \
18
18
  # --company <slug> --project <name> --to <principal> [--mode transfer|share]
19
19
  #
20
- # Prints recipient, mode, every prefix with its permission, the secret
20
+ # Prints recipient, mode, project stage (prd, or brainstorm with the dossier
21
+ # files and the /plan next step), every prefix with its permission, the secret
21
22
  # names an .env.schema would grant, repo + branch, and the DM headline that
22
23
  # would be sent. Exits 0. Creates nothing under workspace/delegations/ and
23
24
  # invokes no mutating command.
@@ -83,6 +84,14 @@ if [ "$DRY_RUN" -eq 1 ]; then
83
84
  echo " Mode: $MODE"
84
85
  echo " Company: $COMPANY"
85
86
  echo " Project: $PROJECT"
87
+ if [ "$(printf '%s' "$PLAN_JSON" | jq -r '.project.stage // "prd"')" = brainstorm ]; then
88
+ echo " Stage: brainstorm — no PRD yet; source of truth is brainstorm.md; recommended next step is /plan $PROJECT"
89
+ echo
90
+ echo " Dossier files that would be published:"
91
+ printf '%s' "$PLAN_JSON" | jq -r '.project.dossier[] | " \(.)"'
92
+ else
93
+ echo " Stage: prd"
94
+ fi
86
95
  echo
87
96
  echo " Vault grants that would be written:"
88
97
  printf '%s' "$PLAN_JSON" | jq -r '.vaultPrefixes[] | " \(.permission)\ton \(.prefix)\t(\(.reason // ""))"'