@imdeadpool/guardex 7.0.21 → 7.0.23

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/src/context.js CHANGED
@@ -128,8 +128,19 @@ const TEMPLATE_FILES = [
128
128
  'vscode/guardex-active-agents/extension.js',
129
129
  'vscode/guardex-active-agents/session-schema.js',
130
130
  'vscode/guardex-active-agents/README.md',
131
+ 'vscode/guardex-active-agents/icon.png',
131
132
  ];
132
133
 
134
+ const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set([
135
+ 'scripts/agent-session-state.js',
136
+ 'scripts/install-vscode-active-agents-extension.js',
137
+ 'vscode/guardex-active-agents/package.json',
138
+ 'vscode/guardex-active-agents/extension.js',
139
+ 'vscode/guardex-active-agents/session-schema.js',
140
+ 'vscode/guardex-active-agents/README.md',
141
+ 'vscode/guardex-active-agents/icon.png',
142
+ ]);
143
+
133
144
  const LEGACY_WORKFLOW_SHIM_SPECS = [
134
145
  { relativePath: 'scripts/agent-branch-start.sh', kind: 'shell', command: ['branch', 'start'] },
135
146
  { relativePath: 'scripts/agent-branch-finish.sh', kind: 'shell', command: ['branch', 'finish'] },
@@ -202,6 +213,7 @@ const PACKAGE_SCRIPT_ASSETS = {
202
213
  branchMerge: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-merge.sh'),
203
214
  codexAgent: path.join(TEMPLATE_ROOT, 'scripts', 'codex-agent.sh'),
204
215
  reviewBot: path.join(TEMPLATE_ROOT, 'scripts', 'review-bot-watch.sh'),
216
+ sessionState: path.join(TEMPLATE_ROOT, 'scripts', 'agent-session-state.js'),
205
217
  worktreePrune: path.join(TEMPLATE_ROOT, 'scripts', 'agent-worktree-prune.sh'),
206
218
  lockTool: path.join(TEMPLATE_ROOT, 'scripts', 'agent-file-locks.py'),
207
219
  planInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-plan-workspace.sh'),
@@ -348,8 +360,8 @@ const CLI_COMMAND_DESCRIPTIONS = [
348
360
  ['cleanup', 'Prune merged/stale agent branches and worktrees'],
349
361
  ['release', 'Create or update the current GitHub release with README-generated notes'],
350
362
  ['agents', 'Start/stop repo-scoped review + cleanup bots'],
351
- ['prompt', 'Print AI setup checklist (--exec, --snippet)'],
352
- ['report', 'Security/safety reports (e.g. OpenSSF scorecard)'],
363
+ ['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
364
+ ['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
353
365
  ['help', 'Show this help output'],
354
366
  ['version', 'Print GitGuardex version'],
355
367
  ];
@@ -369,6 +381,12 @@ const AGENT_BOT_DESCRIPTIONS = [
369
381
  const DOCTOR_AUTO_FINISH_DETAIL_LIMIT = 6;
370
382
  const DOCTOR_AUTO_FINISH_BRANCH_LABEL_MAX = 72;
371
383
  const DOCTOR_AUTO_FINISH_MESSAGE_MAX = 160;
384
+ const AI_SETUP_PART_ALIASES = new Map([
385
+ ['task', 'task-loop'],
386
+ ['loop', 'task-loop'],
387
+ ['reviewbot', 'review-bot'],
388
+ ['forksync', 'fork-sync'],
389
+ ]);
372
390
 
373
391
  function envFlagIsTruthy(raw) {
374
392
  const lowered = String(raw || '').trim().toLowerCase();
@@ -383,35 +401,177 @@ function defaultAgentWorktreeRelativeDir(env = process.env) {
383
401
  return isClaudeCodeSession(env) ? CLAUDE_WORKTREE_RELATIVE_DIR : CODEX_WORKTREE_RELATIVE_DIR;
384
402
  }
385
403
 
386
- const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in this repo.
387
-
388
- 1) Install: ${GLOBAL_INSTALL_COMMAND} && gh --version
389
- 2) Bootstrap: gx setup
390
- 3) Repair: gx doctor
391
- 4) Task loop: gx branch start "<task>" "<agent>"
392
- then gx locks claim --branch "<agent-branch>" <file...> -> gx branch finish
393
- 5) Integrate: gx merge --branch <agent-a> --branch <agent-b>
394
- 6) Finish: gx finish --all
395
- 7) Cleanup: gx cleanup
396
- 8) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive
397
- 9) Optional: gx protect add release staging
398
- 10) Optional: gx sync --check && gx sync
399
- 11) Review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY
400
- 12) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml
401
- `;
402
-
403
- const AI_SETUP_COMMANDS = `${GLOBAL_INSTALL_COMMAND}
404
- gh --version
405
- gx setup
406
- gx doctor
407
- gx branch start "<task>" "<agent>"
408
- gx locks claim --branch "<agent-branch>" <file...>
409
- gx merge --branch "<agent-a>" --branch "<agent-b>"
410
- gx finish --all
411
- gx cleanup
412
- gx protect add release staging
413
- gx sync --check && gx sync
414
- `;
404
+ const AI_SETUP_PARTS = [
405
+ {
406
+ name: 'install',
407
+ label: 'Install',
408
+ promptLines: [`${GLOBAL_INSTALL_COMMAND} && gh --version`],
409
+ execLines: [GLOBAL_INSTALL_COMMAND, 'gh --version'],
410
+ },
411
+ {
412
+ name: 'bootstrap',
413
+ label: 'Bootstrap',
414
+ promptLines: ['gx setup'],
415
+ execLines: ['gx setup'],
416
+ },
417
+ {
418
+ name: 'repair',
419
+ label: 'Repair',
420
+ promptLines: ['gx doctor'],
421
+ execLines: ['gx doctor'],
422
+ },
423
+ {
424
+ name: 'task-loop',
425
+ label: 'Task loop',
426
+ promptLines: [
427
+ 'gx branch start "<task>" "<agent>"',
428
+ 'then gx locks claim --branch "<agent-branch>" <file...> -> inspect once -> patch once -> verify once -> gx branch finish',
429
+ 'batch discovery, git/PR, and CI by phase; avoid repeated peeks or stdin loops',
430
+ ],
431
+ execLines: [
432
+ 'gx branch start "<task>" "<agent>"',
433
+ 'gx locks claim --branch "<agent-branch>" <file...>',
434
+ ],
435
+ },
436
+ {
437
+ name: 'integrate',
438
+ label: 'Integrate',
439
+ promptLines: ['gx merge --branch <agent-a> --branch <agent-b>'],
440
+ execLines: ['gx merge --branch <agent-a> --branch <agent-b>'],
441
+ },
442
+ {
443
+ name: 'finish',
444
+ label: 'Finish',
445
+ promptLines: ['gx finish --all'],
446
+ execLines: ['gx finish --all'],
447
+ },
448
+ {
449
+ name: 'cleanup',
450
+ label: 'Cleanup',
451
+ promptLines: ['gx cleanup'],
452
+ execLines: ['gx cleanup'],
453
+ },
454
+ {
455
+ name: 'openspec',
456
+ label: 'OpenSpec',
457
+ promptLines: ['/opsx:propose -> /opsx:apply -> /opsx:archive'],
458
+ },
459
+ {
460
+ name: 'protect',
461
+ label: 'Protect',
462
+ promptLines: ['gx protect add release staging'],
463
+ execLines: ['gx protect add release staging'],
464
+ },
465
+ {
466
+ name: 'sync',
467
+ label: 'Sync',
468
+ promptLines: ['gx sync --check && gx sync'],
469
+ execLines: ['gx sync --check && gx sync'],
470
+ },
471
+ {
472
+ name: 'review-bot',
473
+ label: 'Review bot',
474
+ promptLines: ['install https://github.com/apps/cr-gpt + set OPENAI_API_KEY'],
475
+ },
476
+ {
477
+ name: 'fork-sync',
478
+ label: 'Fork sync',
479
+ promptLines: ['install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml'],
480
+ },
481
+ ];
482
+ const AI_SETUP_PARTS_BY_NAME = new Map(AI_SETUP_PARTS.map((part) => [part.name, part]));
483
+ const AI_SETUP_EXEC_PART_NAMES = AI_SETUP_PARTS
484
+ .filter((part) => Array.isArray(part.execLines))
485
+ .map((part) => part.name);
486
+
487
+ function normalizeAiSetupPartName(rawName) {
488
+ const normalized = String(rawName || '')
489
+ .trim()
490
+ .toLowerCase()
491
+ .replace(/_/g, '-');
492
+ return AI_SETUP_PART_ALIASES.get(normalized) || normalized;
493
+ }
494
+
495
+ function listAiSetupPartNames(options = {}) {
496
+ if (!options.execOnly) return AI_SETUP_PARTS.map((part) => part.name);
497
+ return AI_SETUP_EXEC_PART_NAMES.slice();
498
+ }
499
+
500
+ function parseAiSetupPartNames(rawValue) {
501
+ return String(rawValue || '')
502
+ .split(',')
503
+ .map((entry) => normalizeAiSetupPartName(entry))
504
+ .filter(Boolean);
505
+ }
506
+
507
+ function resolveAiSetupParts(rawPartNames, options = {}) {
508
+ const exec = Boolean(options.exec);
509
+ const requestedPartNames = Array.isArray(rawPartNames) ? rawPartNames : [];
510
+ const availablePartNames = listAiSetupPartNames();
511
+ const execCapablePartNames = listAiSetupPartNames({ execOnly: true });
512
+ const seen = new Set();
513
+ const resolved = [];
514
+
515
+ for (const rawName of requestedPartNames) {
516
+ const name = normalizeAiSetupPartName(rawName);
517
+ const part = AI_SETUP_PARTS_BY_NAME.get(name);
518
+ if (!part) {
519
+ throw new Error(
520
+ `Unknown prompt part: ${rawName}. Available parts: ${availablePartNames.join(', ')}`,
521
+ );
522
+ }
523
+ if (exec && !Array.isArray(part.execLines)) {
524
+ throw new Error(
525
+ `Prompt part '${name}' is not available with --exec. ` +
526
+ `Exec-capable parts: ${execCapablePartNames.join(', ')}`,
527
+ );
528
+ }
529
+ if (seen.has(name)) continue;
530
+ seen.add(name);
531
+ resolved.push(part);
532
+ }
533
+
534
+ return resolved;
535
+ }
536
+
537
+ function renderFullAiSetupPrompt() {
538
+ const lines = ['GitGuardex (gx) setup checklist for Codex/Claude in this repo.', ''];
539
+ const indentWidth = 18;
540
+
541
+ AI_SETUP_PARTS.forEach((part, index) => {
542
+ const [lead, ...tail] = part.promptLines;
543
+ const prefix = `${index + 1}) ${part.label}:`;
544
+ lines.push(`${prefix.padEnd(indentWidth)}${lead}`);
545
+ tail.forEach((line) => lines.push(`${' '.repeat(indentWidth)}${line}`));
546
+ });
547
+
548
+ return `${lines.join('\n')}\n`;
549
+ }
550
+
551
+ function renderPartialAiSetupPrompt(parts) {
552
+ return `${parts
553
+ .map((part) => `${part.label}:\n${part.promptLines.join('\n')}`)
554
+ .join('\n\n')}\n`;
555
+ }
556
+
557
+ function renderAiSetupCommands(parts) {
558
+ return `${parts.flatMap((part) => part.execLines).join('\n')}\n`;
559
+ }
560
+
561
+ function renderAiSetupPrompt(options = {}) {
562
+ const exec = Boolean(options.exec);
563
+ const requestedPartNames = Array.isArray(options.parts) ? options.parts : [];
564
+ if (requestedPartNames.length === 0) {
565
+ return exec
566
+ ? renderAiSetupCommands(resolveAiSetupParts(AI_SETUP_EXEC_PART_NAMES, { exec: true }))
567
+ : renderFullAiSetupPrompt();
568
+ }
569
+ const parts = resolveAiSetupParts(requestedPartNames, { exec });
570
+ return exec ? renderAiSetupCommands(parts) : renderPartialAiSetupPrompt(parts);
571
+ }
572
+
573
+ const AI_SETUP_PROMPT = renderAiSetupPrompt();
574
+ const AI_SETUP_COMMANDS = renderAiSetupPrompt({ exec: true });
415
575
 
416
576
  const SCORECARD_RISK_BY_CHECK = {
417
577
  'Dangerous-Workflow': 'Critical',
@@ -472,6 +632,7 @@ module.exports = {
472
632
  HOOK_NAMES,
473
633
  toDestinationPath,
474
634
  TEMPLATE_FILES,
635
+ PACKAGE_ROOT_SOURCE_OVERRIDES,
475
636
  LEGACY_WORKFLOW_SHIM_SPECS,
476
637
  LEGACY_WORKFLOW_SHIMS,
477
638
  MANAGED_TEMPLATE_DESTINATIONS,
@@ -511,6 +672,9 @@ module.exports = {
511
672
  envFlagIsTruthy,
512
673
  isClaudeCodeSession,
513
674
  defaultAgentWorktreeRelativeDir,
675
+ listAiSetupPartNames,
676
+ parseAiSetupPartNames,
677
+ renderAiSetupPrompt,
514
678
  AI_SETUP_PROMPT,
515
679
  AI_SETUP_COMMANDS,
516
680
  SCORECARD_RISK_BY_CHECK,
@@ -0,0 +1,52 @@
1
+ const fs = require('node:fs');
2
+ const { StringDecoder } = require('node:string_decoder');
3
+
4
+ const stdinWaitArray = new Int32Array(new SharedArrayBuffer(4));
5
+
6
+ function sleepSyncMs(milliseconds) {
7
+ Atomics.wait(stdinWaitArray, 0, 0, milliseconds);
8
+ }
9
+
10
+ function readSingleLineFromStdin(options = {}) {
11
+ const fsModule = options.fsModule || fs;
12
+ const input = options.input || process.stdin;
13
+ const sleepSync = options.sleepSync || sleepSyncMs;
14
+ const retryDelayMs = options.retryDelayMs == null ? 15 : options.retryDelayMs;
15
+ const buffer = Buffer.alloc(1);
16
+ const decoder = new StringDecoder('utf8');
17
+ let text = '';
18
+
19
+ while (true) {
20
+ let bytesRead = 0;
21
+ try {
22
+ bytesRead = fsModule.readSync(input.fd, buffer, 0, 1);
23
+ } catch (error) {
24
+ if (error && ['EAGAIN', 'EWOULDBLOCK', 'EINTR'].includes(error.code)) {
25
+ sleepSync(retryDelayMs);
26
+ continue;
27
+ }
28
+ return text + decoder.end();
29
+ }
30
+
31
+ if (bytesRead === 0) {
32
+ if (input.isTTY) {
33
+ sleepSync(retryDelayMs);
34
+ continue;
35
+ }
36
+ return text + decoder.end();
37
+ }
38
+
39
+ const char = decoder.write(buffer.subarray(0, bytesRead));
40
+ if (!char) {
41
+ continue;
42
+ }
43
+ if (char === '\n' || char === '\r') {
44
+ return text;
45
+ }
46
+ text += char;
47
+ }
48
+ }
49
+
50
+ module.exports = {
51
+ readSingleLineFromStdin,
52
+ };
@@ -0,0 +1,33 @@
1
+ const semver = require('semver');
2
+
3
+ function parseVersionString(version) {
4
+ const trimmed = String(version || '').trim();
5
+ if (!trimmed) {
6
+ return null;
7
+ }
8
+ return semver.valid(trimmed) || null;
9
+ }
10
+
11
+ function compareParsedVersions(left, right) {
12
+ if (!left || !right) {
13
+ return 0;
14
+ }
15
+ return semver.compare(left, right);
16
+ }
17
+
18
+ function isNewerVersion(latest, current) {
19
+ const latestParts = parseVersionString(latest);
20
+ const currentParts = parseVersionString(current);
21
+
22
+ if (!latestParts || !currentParts) {
23
+ return String(latest || '').trim() !== String(current || '').trim();
24
+ }
25
+
26
+ return semver.gt(latestParts, currentParts);
27
+ }
28
+
29
+ module.exports = {
30
+ parseVersionString,
31
+ compareParsedVersions,
32
+ isNewerVersion,
33
+ };