@indigoai-us/hq-cli 5.317.0 → 5.318.1-beta.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,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.318.1-beta.0] — 2026-10-02
6
+ - Let `hq access` use ACL-checked company presigned reads when STS vend rejects a truncated write-policy scope.
7
+ - Let `hq sync now` skip unchanged server-owned cloud placeholders while keeping their warnings visible, so the sync can complete.
8
+
9
+ ## [5.318.0] — 2026-10-02
10
+ - `hq lanes autopilot run` now marks missing lead registry entries as stale and includes a command to remove each stale row.
11
+ - Keep retired policies retired regardless of YAML field order in the session helper.
12
+ - `publish.yml` refuses a manual run with `dry_run=false` and always passes dry-run to the publish job unless the run comes from a tag push.
13
+
5
14
  ## [5.317.0] — 2026-10-02
6
15
  - Add native `hq core session-title`, `session-title-config`, `session-journal` and `session-project` on the fast route behind the default-off `core-native-hook-helpers` flag.
7
16
  - Build required npm self-updates outside the active prefix, then activate only after the staged package is ready, so an interrupted download or unpack leaves the installed CLI runnable.
@@ -368,12 +368,12 @@ emit_company_hard_policies() {
368
368
  lines="$(awk -v co="$co" '
369
369
  function bn(p, n,a,b){ n=split(p,a,"/"); b=a[n]; sub(/\.md$/,"",b); return b }
370
370
  function policy_file(p, n,a){ n=split(p,a,"/"); return a[n] }
371
- function flush(){ if(enf=="hard" && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
372
- FNR==1 { if(seen) flush(); d=0;id="";enf="";rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
371
+ function flush(){ if(enf=="hard" && !retired && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
372
+ FNR==1 { if(seen) flush(); d=0;id="";enf="";retired=0;rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
373
373
  /^---[ \t]*$/ { d++; next }
374
374
  d==1 && /^id:/ { s=$0; sub(/^id:[ \t]*/,"",s); gsub(/^["'"'"']|["'"'"']$/,"",s); id=s; next }
375
375
  d==1 && /^enforcement:/ { s=$0; sub(/^enforcement:[ \t]*/,"",s); gsub(/[ \t]/,"",s); enf=s; next }
376
- d==1 && /^status:[ \t]*retired/ { enf="" ; next }
376
+ d==1 && /^status:[ \t]*retired/ { retired=1; next }
377
377
  d>=2 && /^## Rule[ \t]*$/ { rsec=1; next }
378
378
  d>=2 && rsec && /^## / { rsec=0 }
379
379
  d>=2 && rsec && !rcap && NF { line=$0; gsub(/\*\*/,"",line); if(length(line)>160)line=substr(line,1,157)"..."; rule=line; rcap=1 }
@@ -221,10 +221,20 @@ export async function runAccess(input) {
221
221
  }
222
222
  catch (err) {
223
223
  const status = httpStatusOf(err);
224
+ const vendError = typeof err === "object" && err !== null
225
+ ? err
226
+ : undefined;
224
227
  if (status === 403) {
225
228
  vendDenied = true;
226
229
  steps.push("vend: 403 (no-access)");
227
230
  }
231
+ else if ((status === 422 || vendError?.statusCode === 422) &&
232
+ vendError?.code === "POLICY_WRITE_SCOPE_TRUNCATED") {
233
+ // This failure means the company-wide STS write policy could not fit.
234
+ // Company reads use the separate ACL-filtered presign API, which remains
235
+ // the authority for listing and materializing this particular object.
236
+ steps.push("vend: write policy truncated; using presigned read");
237
+ }
228
238
  else if (status === 404) {
229
239
  steps.push("vend: 404");
230
240
  }
@@ -1697,6 +1697,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
1697
1697
  hqRoot,
1698
1698
  progressRecorder: trackingProgressRecorder(planLimitTracker, pushProgressCompany),
1699
1699
  skipUnchanged: true,
1700
+ serverOwnedPathPolicy: "skip",
1700
1701
  propagateDeletes: true,
1701
1702
  propagateDeletePolicy: resolveDeletePolicy(),
1702
1703
  ...(onConflict ? { onConflict } : {}),
@@ -318,6 +318,7 @@ function hardPolicyLines(root, company, deps) {
318
318
  let delimiters = 0;
319
319
  let id = "";
320
320
  let enforcement = "";
321
+ let retired = false;
321
322
  let ruleSection = false;
322
323
  let rule = "";
323
324
  for (const line of lines) {
@@ -335,7 +336,7 @@ function hardPolicyLines(root, company, deps) {
335
336
  if (enforceMatch)
336
337
  enforcement = (enforceMatch[1] ?? "").replace(/[\t ]/g, "");
337
338
  if (/^status:[\t ]*retired/.test(line))
338
- enforcement = "";
339
+ retired = true;
339
340
  }
340
341
  if (delimiters >= 2 && line === "## Rule") {
341
342
  ruleSection = true;
@@ -351,7 +352,7 @@ function hardPolicyLines(root, company, deps) {
351
352
  rule = `${rule.slice(0, 157)}...`;
352
353
  }
353
354
  }
354
- if (enforcement !== "hard" || !rule)
355
+ if (enforcement !== "hard" || retired || !rule)
355
356
  continue;
356
357
  const policyId = id || basename.replace(/\.md$/, "");
357
358
  result.push(`- [hard] **${policyId}**: ${rule} Full text: \`companies/${company}/policies/${basename}\`.`);
@@ -83,6 +83,13 @@ export interface AutopilotPassResult {
83
83
  skipped: Array<{
84
84
  lead_lane: string;
85
85
  reason: string;
86
+ status?: undefined;
87
+ fix_command?: undefined;
88
+ } | {
89
+ lead_lane: string;
90
+ reason: "lead_lane_missing";
91
+ status: "stale";
92
+ fix_command: string;
86
93
  } | {
87
94
  lane_id: string;
88
95
  error: string;
@@ -35,6 +35,13 @@ function isOpenProjectStory(story) {
35
35
  function isRecord(value) {
36
36
  return value !== null && typeof value === "object" && !Array.isArray(value);
37
37
  }
38
+ function shellQuote(value) {
39
+ return `'${value.replace(/'/g, "'\\''")}'`;
40
+ }
41
+ function staleLeadFixCommand(laneId) {
42
+ const registryTemp = `${LEAD_REGISTRY_REL}.tmp`;
43
+ return `jq --arg lane ${shellQuote(laneId)} '(.juniors) |= map(select(.lane != $lane))' ${shellQuote(LEAD_REGISTRY_REL)} > ${shellQuote(registryTemp)} && mv ${shellQuote(registryTemp)} ${shellQuote(LEAD_REGISTRY_REL)}`;
44
+ }
38
45
  function readJsonFile(filePath, code) {
39
46
  let text;
40
47
  try {
@@ -755,9 +762,23 @@ async function runLeadAutopilotPassUnlocked(input) {
755
762
  const stateFile = path.join(hqRoot, "workspace", "lane-briefs", "autopilot-state.json");
756
763
  const state = readState(stateFile);
757
764
  for (const registered of leads) {
765
+ const registeredLocalHost = registered.host === "local" || registered.host === input.hostname;
758
766
  const lead = byId.get(registered.lane) ?? lanes.find((candidate) => candidate.lane_id.startsWith(`${registered.lane}_`));
759
767
  if (!lead) {
760
- result.skipped.push({ lead_lane: registered.lane, reason: "lead_lane_missing" });
768
+ // A row for another host has no lane record here; that host's pass owns it.
769
+ const registryTargetsThisHost = registeredLocalHost
770
+ ? effectiveOutpostId === null
771
+ : input.outpostId === registered.host;
772
+ if (!registryTargetsThisHost) {
773
+ result.skipped.push({ lead_lane: registered.lane, reason: "lead_host_mismatch" });
774
+ continue;
775
+ }
776
+ result.skipped.push({
777
+ lead_lane: registered.lane,
778
+ reason: "lead_lane_missing",
779
+ status: "stale",
780
+ fix_command: staleLeadFixCommand(registered.lane),
781
+ });
761
782
  continue;
762
783
  }
763
784
  const company = resolveLaneCompany(hqRoot, lead).company;
@@ -765,7 +786,6 @@ async function runLeadAutopilotPassUnlocked(input) {
765
786
  result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_company_unresolved" });
766
787
  continue;
767
788
  }
768
- const registeredLocalHost = registered.host === "local" || registered.host === input.hostname;
769
789
  const hostMatchesRegistry = registeredLocalHost
770
790
  ? lead.host.kind === "local"
771
791
  : lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.317.0",
3
+ "version": "5.318.1-beta.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {