@inkobytes/nexus 1.0.2 → 1.0.3

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
@@ -1,9 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.2 - 2026-06-03
3
+ ## 1.0.3 - 2026-06-03
4
4
 
5
5
  - Collapsed large `nexus doctor` Git Privacy floods into grouped per-root summaries with sample paths.
6
6
  - Prioritized `CONTINUITY.md` and `memories/` samples first so agent-local issues stay visible.
7
+ - Collapsed shared agent roots like `.claude/`, `.codex/`, and `.gemini/` into one concise Git Privacy note for repos that intentionally track them.
8
+ - Colorized `nexus doctor` output and grouped findings under clearer action buckets like `Fix the following` and `Review / informational`.
9
+ - Added `.nexus/config.json` support for `doctor.allowTrackedAgentTrees` so intentionally tracked shared agent trees can be treated as informational.
7
10
 
8
11
  ## 1.0.1 - 2026-06-02
9
12
 
package/README.md CHANGED
@@ -166,7 +166,8 @@ Doctor reports grouped issues:
166
166
  - missing Nexus files
167
167
  - package script exfiltration and install-hook risks
168
168
  - package privacy risks for local/private files
169
- - grouped Git Privacy summaries for tracked private/local trees so large agent folders stay readable
169
+ - grouped Git Privacy summaries for tracked private/local trees, with shared agent dirs collapsed into one concise note
170
+ - colorized action buckets so fixes and informational lock notes are easier to scan
170
171
  - stale nexus locks
171
172
  - missing agent instructions specifically for nexus
172
173
  - missing continuity and memory scaffolds
@@ -176,6 +177,18 @@ With `--fix`, Nexus creates safe missing scaffolds and updates managed protocol
176
177
 
177
178
  With `--json`, Nexus prints the same health sections as structured JSON for tools such as Inkobytes reports.
178
179
 
180
+ If a private repo intentionally tracks shared agent trees like `.claude/`, `.codex/`, or `.gemini/`, you can mark that as allowed in `.nexus/config.json`:
181
+
182
+ ```json
183
+ {
184
+ "doctor": {
185
+ "allowTrackedAgentTrees": true
186
+ }
187
+ }
188
+ ```
189
+
190
+ With that setting, `nexus doctor` keeps the shared-agent-tree note as informational instead of repeating an untrack fix.
191
+
179
192
  Use `doctor` for audit or repair. Do not make it the normal first command for every agent session.
180
193
 
181
194
  ### `nexus soul [--file <path>] [--status | --remove]`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkobytes/nexus",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Multi-agent coordination CLI for coding agents sharing a local repository",
5
5
  "type": "module",
6
6
  "bin": {
@@ -347,6 +347,7 @@ export default function doctor(args) {
347
347
  const fix = args.includes('--fix');
348
348
  const json = args.includes('--json');
349
349
  const root = cwd();
350
+ const colors = createColors();
350
351
  const sections = {
351
352
  'Nexus Files': [],
352
353
  'Agent Instructions': [],
@@ -365,8 +366,8 @@ export default function doctor(args) {
365
366
  const config = getConfig(root);
366
367
 
367
368
  if (!json) {
368
- console.log(`Nexus doctor${fix ? ' --fix' : ''}`);
369
- console.log(`Repo: ${root}\n`);
369
+ console.log(colors.bold(colors.cyan(`Nexus doctor${fix ? ' --fix' : ''}`)));
370
+ console.log(`${colors.dim('Repo:')} ${root}\n`);
370
371
  }
371
372
 
372
373
  const nexusProtocolFiles = ['_NEXUS_CONSTITUTION.md', '_NEXUS_QUEUE.md', '_NEXUS_STANDUP.md'];
@@ -412,7 +413,7 @@ export default function doctor(args) {
412
413
  sections['Package Privacy'].push(issue);
413
414
  }
414
415
 
415
- for (const issue of scanGitPrivacy(root)) {
416
+ for (const issue of scanGitPrivacy(root, config)) {
416
417
  sections['Git Privacy'].push(issue);
417
418
  }
418
419
 
@@ -658,40 +659,95 @@ export default function doctor(args) {
658
659
  }
659
660
 
660
661
  if (changes.length) {
661
- console.log('Applied fixes:');
662
- for (const change of changes) console.log(` - ${change}`);
662
+ console.log(colors.bold(colors.green('Applied fixes')));
663
+ for (const change of changes) console.log(` ${colors.green('-')} ${change}`);
663
664
  console.log('');
664
665
  }
665
666
 
666
667
  let problemCount = 0;
667
668
  for (const [title, entries] of Object.entries(sections)) {
668
- console.log(`[${title}]`);
669
+ console.log(colors.bold(colors.cyan(`[${title}]`)));
669
670
  if (!entries.length) {
670
- console.log(' OK');
671
+ console.log(` ${colors.green('OK')}`);
671
672
  console.log('');
672
673
  continue;
673
674
  }
674
675
 
675
- for (const entry of entries) {
676
+ const actionable = entries.filter((entry) => !entry.ok);
677
+ const informational = entries.filter((entry) => entry.ok);
678
+ problemCount += actionable.length;
679
+
680
+ if (actionable.length) renderEntryBucket('Fix the following', actionable, colors.yellow, colors.red, title, colors);
681
+ if (informational.length) renderEntryBucket('Review / informational', informational, colors.blue, colors.green, title, colors);
682
+ console.log('');
683
+ }
684
+
685
+ if (problemCount) {
686
+ console.log(colors.bold(colors.yellow('Some issues need attention.')));
687
+ console.log(colors.dim('Safe scaffold fixes: `nexus doctor --fix`.'));
688
+ return;
689
+ }
690
+
691
+ console.log(colors.bold(colors.green('All checked Nexus categories are ready.')));
692
+ }
693
+
694
+ function renderEntryBucket(label, entries, headingColor, markerColor, sectionTitle, colors) {
695
+ console.log(` ${headingColor(label)}`);
696
+ const groups = groupEntriesForDisplay(entries, sectionTitle);
697
+ for (const group of groups) {
698
+ if (group.label) {
699
+ console.log(` ${colors.bold(group.label)}`);
700
+ }
701
+ for (const entry of group.entries) {
702
+ const baseIndent = group.label ? ' ' : ' ';
703
+ const detailIndent = group.label ? ' ' : ' ';
676
704
  const prefix = entry.ok ? '-' : '!';
677
- console.log(` ${prefix} ${entry.issue}`);
705
+ console.log(`${baseIndent}${markerColor(prefix)} ${entry.issue}`);
678
706
  if (entry.details) {
679
707
  for (const detail of entry.details) {
680
- console.log(` ${detail}`);
708
+ console.log(`${detailIndent}${colors.dim(detail)}`);
681
709
  }
682
710
  }
683
- if (entry.fix) console.log(` Fix: ${entry.fix}`);
684
- if (!entry.ok) problemCount++;
711
+ if (entry.fix) {
712
+ console.log(`${detailIndent}${colors.bold('Fix:')} ${colors.dim(entry.fix)}`);
713
+ }
685
714
  }
686
- console.log('');
687
715
  }
716
+ }
688
717
 
689
- if (problemCount) {
690
- console.log('Some issues need attention. Safe scaffold fixes: `nexus doctor --fix`.');
691
- return;
718
+ function groupEntriesForDisplay(entries, sectionTitle) {
719
+ const groups = [];
720
+ const grouped = new Map();
721
+
722
+ for (const entry of entries) {
723
+ const label = entry.displayGroup || inferDisplayGroup(entry.issue, sectionTitle);
724
+ if (!label) {
725
+ groups.push({ label: '', entries: [entry] });
726
+ continue;
727
+ }
728
+ if (!grouped.has(label)) {
729
+ const group = { label, entries: [] };
730
+ grouped.set(label, group);
731
+ groups.push(group);
732
+ }
733
+ grouped.get(label).entries.push(entry);
734
+ }
735
+
736
+ return groups;
737
+ }
738
+
739
+ function inferDisplayGroup(issue, sectionTitle) {
740
+ if (sectionTitle === 'Locks') {
741
+ const lockMatch = issue.match(/^(?:Stale lock on|Active lock on|Unverified claim on) ([^ ]+)/);
742
+ if (lockMatch) return lockMatch[1];
692
743
  }
693
744
 
694
- console.log('All checked Nexus categories are ready.');
745
+ if (sectionTitle === 'Queue Authorship') {
746
+ const taskMatch = issue.match(/^Task "([^"]+)"/);
747
+ if (taskMatch) return taskMatch[1];
748
+ }
749
+
750
+ return '';
695
751
  }
696
752
 
697
753
  function extractReadyQueueSection(content) {
@@ -864,6 +920,13 @@ const GIT_PRIVACY_COLLAPSE_ROOTS = [
864
920
  'session-logs',
865
921
  ];
866
922
 
923
+ const GIT_PRIVACY_AGENT_ROOTS = new Set([
924
+ '.agy',
925
+ '.claude',
926
+ '.codex',
927
+ '.gemini',
928
+ ]);
929
+
867
930
  function scanPackagePrivacy(root) {
868
931
  const packagePath = join(root, 'package.json');
869
932
  if (!existsSync(packagePath)) return [];
@@ -895,7 +958,7 @@ function scanPackagePrivacy(root) {
895
958
  return issues;
896
959
  }
897
960
 
898
- function scanGitPrivacy(root) {
961
+ function scanGitPrivacy(root, config) {
899
962
  const gitDir = join(root, '.git');
900
963
  if (!existsSync(gitDir)) return [];
901
964
 
@@ -907,10 +970,10 @@ function scanGitPrivacy(root) {
907
970
  if (result.status !== 0) return [];
908
971
 
909
972
  const tracked = result.stdout.split('\n').filter(Boolean);
910
- return summarizeGitPrivacyIssues(tracked);
973
+ return summarizeGitPrivacyIssues(tracked, config);
911
974
  }
912
975
 
913
- function summarizeGitPrivacyIssues(tracked) {
976
+ function summarizeGitPrivacyIssues(tracked, config) {
914
977
  const grouped = new Map();
915
978
  const singles = [];
916
979
 
@@ -925,6 +988,7 @@ function summarizeGitPrivacyIssues(tracked) {
925
988
  }
926
989
 
927
990
  const issues = [];
991
+ const agentRootSummaries = [];
928
992
 
929
993
  for (const file of singles.sort()) {
930
994
  issues.push({
@@ -935,6 +999,10 @@ function summarizeGitPrivacyIssues(tracked) {
935
999
 
936
1000
  for (const root of Array.from(grouped.keys()).sort()) {
937
1001
  const files = grouped.get(root).slice().sort(compareGitPrivacyFiles);
1002
+ if (GIT_PRIVACY_AGENT_ROOTS.has(root)) {
1003
+ agentRootSummaries.push(`${root}/ (${files.length} files)`);
1004
+ continue;
1005
+ }
938
1006
  const samples = files.slice(0, 5);
939
1007
  const hiddenCount = files.length - samples.length;
940
1008
  const noun = files.length === 1 ? 'path' : 'paths';
@@ -947,6 +1015,21 @@ function summarizeGitPrivacyIssues(tracked) {
947
1015
  issues.push({ issue, fix, details });
948
1016
  }
949
1017
 
1018
+ if (agentRootSummaries.length) {
1019
+ issues.unshift({
1020
+ issue: `Tracked shared agent trees detected: ${agentRootSummaries.join(', ')}`,
1021
+ fix: config.doctor.allowTrackedAgentTrees
1022
+ ? undefined
1023
+ : 'If these agent trees are intentionally versioned in this repo, keep them. Otherwise untrack them without deleting local files: `git rm --cached -r -- <path>`, then add an ignore rule.',
1024
+ details: [
1025
+ config.doctor.allowTrackedAgentTrees
1026
+ ? 'Allowed by `.nexus/config.json` because this repo intentionally versions shared agent trees.'
1027
+ : 'This can be normal in private repos that share agent protocols and memory in Git.',
1028
+ ],
1029
+ ok: config.doctor.allowTrackedAgentTrees,
1030
+ });
1031
+ }
1032
+
950
1033
  return issues;
951
1034
  }
952
1035
 
@@ -969,6 +1052,26 @@ function gitPrivacyPriority(file) {
969
1052
  return 2;
970
1053
  }
971
1054
 
1055
+ function createColors() {
1056
+ const enabled = supportsColor();
1057
+ const wrap = (open, close) => (value) => enabled ? `\u001b[${open}m${value}\u001b[${close}m` : String(value);
1058
+ return {
1059
+ bold: wrap(1, 22),
1060
+ dim: wrap(2, 22),
1061
+ red: wrap(31, 39),
1062
+ green: wrap(32, 39),
1063
+ yellow: wrap(33, 39),
1064
+ blue: wrap(34, 39),
1065
+ cyan: wrap(36, 39),
1066
+ };
1067
+ }
1068
+
1069
+ function supportsColor() {
1070
+ if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') return true;
1071
+ if ('NO_COLOR' in process.env) return false;
1072
+ return Boolean(process.stdout && process.stdout.isTTY);
1073
+ }
1074
+
972
1075
  function scanGeneratedArtifacts(root) {
973
1076
  const gitDir = join(root, '.git');
974
1077
  if (!existsSync(gitDir)) return [];
package/src/lib/config.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Config — resolve project root and Nexus paths
3
3
  */
4
4
 
5
- import { existsSync } from 'fs';
5
+ import { existsSync, readFileSync } from 'fs';
6
6
  import { resolve, join } from 'path';
7
7
  import { cwd } from 'process';
8
8
 
@@ -14,6 +14,7 @@ export function getConfig(fromDir) {
14
14
  const root = fromDir || cwd();
15
15
  const lockDir = join(root, '.nexus', 'locks');
16
16
  const budgetFile = join(root, '.nexus', 'agent-budgets.json');
17
+ const localConfig = readLocalConfig(root);
17
18
 
18
19
  _config = {
19
20
  root,
@@ -28,6 +29,9 @@ export function getConfig(fromDir) {
28
29
  maxDumpFiles: 20,
29
30
  maxClaimAttempts: 10,
30
31
  claimRetryMs: 2000,
32
+ doctor: {
33
+ allowTrackedAgentTrees: Boolean(localConfig.doctor?.allowTrackedAgentTrees),
34
+ },
31
35
  };
32
36
 
33
37
  return _config;
@@ -36,3 +40,15 @@ export function getConfig(fromDir) {
36
40
  export function resetConfig() {
37
41
  _config = null;
38
42
  }
43
+
44
+ function readLocalConfig(root) {
45
+ const path = join(root, '.nexus', 'config.json');
46
+ if (!existsSync(path)) return {};
47
+
48
+ try {
49
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
50
+ return parsed && typeof parsed === 'object' ? parsed : {};
51
+ } catch {
52
+ return {};
53
+ }
54
+ }