@imdeadpool/guardex 7.0.23 → 7.0.24

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/README.md CHANGED
@@ -672,6 +672,11 @@ npm pack --dry-run
672
672
  <details>
673
673
  <summary><strong>v7.x</strong></summary>
674
674
 
675
+ ### v7.0.24
676
+ - Bumped `@imdeadpool/guardex` from `7.0.23` to `7.0.24` so GitHub Releases and the npm publish retry can advance together after `v7.0.23` landed on GitHub but not on npm.
677
+ - Release verification no longer loses its base ref on tag-triggered runs, so the publish workflow keeps the history it needs before packing and publish checks.
678
+ - Keep the release scoped to version and release automation metadata only; the packaged Guardex CLI payload stays aligned with the already-verified `main` branch contents.
679
+
675
680
  ### v7.0.23
676
681
  - Bumped `@imdeadpool/guardex` from `7.0.22` to `7.0.23` so GitHub release and npm can advance together after `7.0.22` reached npm without a matching published GitHub release.
677
682
  - Active Agents stays easier to scan and more truthful: the package repo remains the canonical source, inspect/install paths stay loadable across VS Code churn, and session rows group under worktrees with clearer merged-cleanup truth.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.23",
3
+ "version": "7.0.24",
4
4
  "description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
package/src/cli/main.js CHANGED
@@ -2431,15 +2431,20 @@ function report(rawArgs) {
2431
2431
  const options = parseReportArgs(rawArgs);
2432
2432
  const subcommand = options.subcommand || 'help';
2433
2433
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
2434
+ const sessionSeverityHelpDetails = sessionSeverityReport.renderSessionSeverityHelpDetails()
2435
+ .split('\n')
2436
+ .map((line) => ` ${line}`)
2437
+ .join('\n');
2434
2438
  console.log(
2435
2439
  `${TOOL_NAME} report commands:\n` +
2436
2440
  ` ${TOOL_NAME} report scorecard [--target <path>] [--repo github.com/<owner>/<repo>] [--scorecard-json <file>] [--output-dir <path>] [--date YYYY-MM-DD] [--dry-run] [--json]\n` +
2437
- ` ${TOOL_NAME} report session-severity --task-size <narrow-patch|medium-change|large-change> --tokens <count> --exec-count <count> --write-stdin-count <count> --completion-before-tail <yes|no> [--expected-bound <count>] [--fragmentation <preset|0-25>] [--finish-path <preset|0-15>] [--post-proof <preset|0-15>] [--json]\n` +
2441
+ ` ${sessionSeverityReport.renderSessionSeverityCommand(TOOL_NAME)}\n` +
2442
+ `${sessionSeverityHelpDetails}\n` +
2438
2443
  `\n` +
2439
2444
  `Examples:\n` +
2440
2445
  ` ${TOOL_NAME} report scorecard --repo github.com/recodeecom/multiagent-safety\n` +
2441
2446
  ` ${TOOL_NAME} report scorecard --scorecard-json ./scorecard.json --date 2026-04-10\n` +
2442
- ` ${TOOL_NAME} report session-severity --task-size narrow-patch --tokens 3850000 --exec-count 18 --write-stdin-count 6 --completion-before-tail yes --fragmentation 14 --finish-path 6 --post-proof 4`,
2447
+ ` ${sessionSeverityReport.renderSessionSeverityExample(TOOL_NAME)}`,
2443
2448
  );
2444
2449
  process.exitCode = 0;
2445
2450
  return;
@@ -4,7 +4,8 @@ const TASK_SIZE_UPPER_BOUNDS = {
4
4
  'large-change': 8_000_000,
5
5
  };
6
6
 
7
- const TASK_SIZE_VALUES = new Set(Object.keys(TASK_SIZE_UPPER_BOUNDS));
7
+ const TASK_SIZE_VALUES = Object.keys(TASK_SIZE_UPPER_BOUNDS);
8
+ const TASK_SIZE_SET = new Set(TASK_SIZE_VALUES);
8
9
  const FRAGMENTATION_PRESET_SCORES = {
9
10
  clean: 0,
10
11
  'few-extra-checks': 5,
@@ -32,6 +33,50 @@ const DRIVER_LABELS = {
32
33
  finishPath: 'finish-path discipline',
33
34
  postProof: 'post-proof drift',
34
35
  };
36
+ const LABEL_BANDS = [
37
+ { max: 15, label: 'Healthy' },
38
+ { max: 30, label: 'Mildly fragmented' },
39
+ { max: 50, label: 'Inefficient' },
40
+ { max: 75, label: 'Runaway' },
41
+ { max: 100, label: 'Catastrophic' },
42
+ ];
43
+ const SESSION_SEVERITY_SUBCOMMAND = 'session-severity';
44
+ const SESSION_SEVERITY_USAGE_ARGS = [
45
+ `--task-size <${TASK_SIZE_VALUES.join('|')}>`,
46
+ '--tokens <count>',
47
+ '--exec-count <count>',
48
+ '--write-stdin-count <count>',
49
+ '--completion-before-tail <yes|no>',
50
+ '[--expected-bound <count>]',
51
+ `[--fragmentation <${Object.keys(FRAGMENTATION_PRESET_SCORES).join('|')}|0-25>]`,
52
+ `[--finish-path <${Object.keys(FINISH_PATH_PRESET_SCORES).join('|')}|0-15>]`,
53
+ `[--post-proof <${Object.keys(POST_PROOF_PRESET_SCORES).join('|')}|0-15>]`,
54
+ '[--json]',
55
+ ].join(' ');
56
+ const SESSION_SEVERITY_COMMAND_TAIL = `${SESSION_SEVERITY_SUBCOMMAND} ${SESSION_SEVERITY_USAGE_ARGS}`;
57
+ const SESSION_SEVERITY_EXAMPLE_ARGS = [
58
+ '--task-size',
59
+ 'narrow-patch',
60
+ '--tokens',
61
+ '3850000',
62
+ '--exec-count',
63
+ '18',
64
+ '--write-stdin-count',
65
+ '6',
66
+ '--completion-before-tail',
67
+ 'yes',
68
+ '--fragmentation',
69
+ '14',
70
+ '--finish-path',
71
+ '6',
72
+ '--post-proof',
73
+ '4',
74
+ ];
75
+ const SESSION_SEVERITY_EXAMPLE_TAIL = `${SESSION_SEVERITY_SUBCOMMAND} ${SESSION_SEVERITY_EXAMPLE_ARGS.join(' ')}`;
76
+
77
+ function formatInteger(value) {
78
+ return Number(value).toLocaleString('en-US');
79
+ }
35
80
 
36
81
  function parseRequiredPositiveInteger(name, rawValue, { allowZero = true } = {}) {
37
82
  const parsed = Number.parseInt(String(rawValue || ''), 10);
@@ -58,8 +103,8 @@ function clampScore(value, min, max) {
58
103
 
59
104
  function parseTaskSize(rawTaskSize) {
60
105
  const normalized = String(rawTaskSize || '').trim();
61
- if (!TASK_SIZE_VALUES.has(normalized)) {
62
- throw new Error(`--task-size must be one of: ${Array.from(TASK_SIZE_VALUES).join(', ')}`);
106
+ if (!TASK_SIZE_SET.has(normalized)) {
107
+ throw new Error(`--task-size must be one of: ${TASK_SIZE_VALUES.join(', ')}`);
63
108
  }
64
109
  return normalized;
65
110
  }
@@ -123,11 +168,7 @@ function scorePostProof(completionBeforeTail, override) {
123
168
  }
124
169
 
125
170
  function labelForTotal(total) {
126
- if (total <= 15) return 'Healthy';
127
- if (total <= 30) return 'Mildly fragmented';
128
- if (total <= 50) return 'Inefficient';
129
- if (total <= 75) return 'Runaway';
130
- return 'Catastrophic';
171
+ return LABEL_BANDS.find((band) => total <= band.max)?.label || LABEL_BANDS[LABEL_BANDS.length - 1].label;
131
172
  }
132
173
 
133
174
  function buildSessionSeverityReport(options) {
@@ -205,9 +246,37 @@ function renderSessionSeverityReport(report) {
205
246
  ].join('\n');
206
247
  }
207
248
 
249
+ function renderSessionSeverityHelpDetails() {
250
+ const taskSizeDefaults = TASK_SIZE_VALUES
251
+ .map((taskSize) => `${taskSize}=${formatInteger(TASK_SIZE_UPPER_BOUNDS[taskSize])}`)
252
+ .join(', ');
253
+ const labelBands = LABEL_BANDS
254
+ .map((band, index) => {
255
+ const min = index === 0 ? 0 : LABEL_BANDS[index - 1].max + 1;
256
+ return `${band.label}=${min}-${band.max}`;
257
+ })
258
+ .join(', ');
259
+ return [`Task-size defaults: ${taskSizeDefaults}`, `Label bands: ${labelBands}`].join('\n');
260
+ }
261
+
262
+ function renderSessionSeverityCommand(toolName) {
263
+ return `${toolName} report ${SESSION_SEVERITY_COMMAND_TAIL}`;
264
+ }
265
+
266
+ function renderSessionSeverityExample(toolName) {
267
+ return `${toolName} report ${SESSION_SEVERITY_EXAMPLE_TAIL}`;
268
+ }
269
+
208
270
  module.exports = {
271
+ LABEL_BANDS,
272
+ SESSION_SEVERITY_COMMAND_TAIL,
273
+ SESSION_SEVERITY_EXAMPLE_ARGS,
274
+ SESSION_SEVERITY_EXAMPLE_TAIL,
209
275
  TASK_SIZE_UPPER_BOUNDS,
210
276
  buildSessionSeverityReport,
211
277
  renderSessionSeverityReport,
278
+ renderSessionSeverityHelpDetails,
279
+ renderSessionSeverityCommand,
280
+ renderSessionSeverityExample,
212
281
  labelForTotal,
213
282
  };
@@ -455,6 +455,44 @@ read_pr_state() {
455
455
  return 0
456
456
  }
457
457
 
458
+ read_merged_pr_for_head() {
459
+ local head_sha="${1:-}"
460
+ local state_line=""
461
+ local parsed_state=""
462
+ local parsed_merged_at=""
463
+ local parsed_url=""
464
+
465
+ if [[ -z "$head_sha" ]]; then
466
+ return 1
467
+ fi
468
+
469
+ state_line="$("$GH_BIN" pr list \
470
+ --state merged \
471
+ --head "$SOURCE_BRANCH" \
472
+ --base "$BASE_BRANCH" \
473
+ --json state,mergedAt,url,headRefOid \
474
+ --jq "map(select(.headRefOid == \"$head_sha\")) | sort_by(.mergedAt // \"\") | reverse | (.[0] // {}) | [(.state // \"\"), (.mergedAt // \"\"), (.url // \"\")] | join(\"\u001f\")" \
475
+ 2>/dev/null || true)"
476
+ if [[ -z "$state_line" ]]; then
477
+ return 1
478
+ fi
479
+
480
+ IFS=$'\x1f' read -r parsed_state parsed_merged_at parsed_url <<< "$state_line"
481
+ if [[ -z "$parsed_state" && -z "$parsed_merged_at" && -z "$parsed_url" ]]; then
482
+ return 1
483
+ fi
484
+ if [[ "$parsed_state" != "MERGED" && -z "$parsed_merged_at" ]]; then
485
+ return 1
486
+ fi
487
+
488
+ PR_STATE="$parsed_state"
489
+ PR_MERGED_AT="$parsed_merged_at"
490
+ if [[ -n "$parsed_url" ]]; then
491
+ pr_url="$parsed_url"
492
+ fi
493
+ return 0
494
+ }
495
+
458
496
  wait_for_pr_merge() {
459
497
  local deadline
460
498
  deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
@@ -509,11 +547,22 @@ wait_for_pr_merge() {
509
547
  }
510
548
 
511
549
  run_pr_flow() {
550
+ local source_head_sha=""
551
+
512
552
  if ! command -v "$GH_BIN" >/dev/null 2>&1; then
513
553
  echo "[agent-branch-finish] PR fallback requested but GitHub CLI not found: ${GH_BIN}" >&2
514
554
  return 1
515
555
  fi
516
556
 
557
+ source_head_sha="$(git -C "$repo_root" rev-parse "$SOURCE_BRANCH" 2>/dev/null || true)"
558
+ if read_merged_pr_for_head "$source_head_sha"; then
559
+ echo "[agent-branch-finish] Source branch head already landed in a merged PR; skipping new PR creation and continuing cleanup." >&2
560
+ if [[ -n "$pr_url" ]]; then
561
+ echo "[agent-branch-finish] Merged PR: ${pr_url}" >&2
562
+ fi
563
+ return 0
564
+ fi
565
+
517
566
  git -C "$source_worktree" push -u origin "$SOURCE_BRANCH"
518
567
 
519
568
  pr_title="$(git -C "$repo_root" log -1 --pretty=%s "$SOURCE_BRANCH" 2>/dev/null || true)"
@@ -155,6 +155,15 @@ env_flag_truthy() {
155
155
  esac
156
156
  }
157
157
 
158
+ maybe_fail_after_auto_transfer_stash() {
159
+ if env_flag_truthy "${GUARDEX_TEST_FAIL_AFTER_AUTO_TRANSFER_STASH:-}"; then
160
+ echo "[agent-branch-start] Simulated failure after capturing auto-transfer stash." >&2
161
+ return 1
162
+ fi
163
+
164
+ return 0
165
+ }
166
+
158
167
  default_worktree_root_rel() {
159
168
  local raw_agent="$1"
160
169
  local override="${GUARDEX_AGENT_TYPE:-}"
@@ -580,6 +589,27 @@ fi
580
589
  auto_transfer_stash_ref=""
581
590
  auto_transfer_message=""
582
591
  auto_transfer_source_branch=""
592
+ auto_transfer_completed=0
593
+
594
+ restore_auto_transfer_stash_on_failure() {
595
+ local exit_code="${1:-0}"
596
+ if [[ "$exit_code" -eq 0 ]] || [[ -z "$auto_transfer_stash_ref" ]] || [[ "$auto_transfer_completed" -eq 1 ]]; then
597
+ return 0
598
+ fi
599
+
600
+ local transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
601
+ if git -C "$repo_root" stash apply "$auto_transfer_stash_ref" >/dev/null 2>&1; then
602
+ git -C "$repo_root" stash drop "$auto_transfer_stash_ref" >/dev/null 2>&1 || true
603
+ auto_transfer_stash_ref=""
604
+ echo "[agent-branch-start] Restored moved changes back to '${transfer_label}' after startup failure." >&2
605
+ else
606
+ echo "[agent-branch-start] Startup failed and auto-restore also failed." >&2
607
+ echo "[agent-branch-start] Changes are preserved in ${auto_transfer_stash_ref} on ${transfer_label}." >&2
608
+ fi
609
+ }
610
+
611
+ trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
612
+
583
613
  current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
584
614
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
585
615
  if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
@@ -593,6 +623,9 @@ if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_bra
593
623
  if [[ -n "$auto_transfer_stash_ref" ]]; then
594
624
  auto_transfer_source_branch="$current_branch"
595
625
  echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
626
+ if ! maybe_fail_after_auto_transfer_stash; then
627
+ exit 1
628
+ fi
596
629
  fi
597
630
  fi
598
631
  fi
@@ -610,7 +643,9 @@ git -C "$worktree_path" branch --unset-upstream "$branch_name" >/dev/null 2>&1 |
610
643
 
611
644
  if [[ -n "$auto_transfer_stash_ref" ]]; then
612
645
  if git -C "$worktree_path" stash apply "$auto_transfer_stash_ref" >/dev/null 2>&1; then
646
+ auto_transfer_completed=1
613
647
  git -C "$repo_root" stash drop "$auto_transfer_stash_ref" >/dev/null 2>&1 || true
648
+ auto_transfer_stash_ref=""
614
649
  transfer_label="${auto_transfer_source_branch:-$BASE_BRANCH}"
615
650
  echo "[agent-branch-start] Moved local changes from '${transfer_label}' into '${branch_name}'."
616
651
  else