@inkobytes/nexus 1.0.2 → 1.0.4
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 +5 -1
- package/README.md +14 -1
- package/package.json +1 -1
- package/src/commands/doctor.js +257 -22
- package/src/lib/config.js +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.0.
|
|
3
|
+
## 1.0.4 - 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.
|
|
10
|
+
- Reformatted lock and queue findings into compact field-style output like `file`, `by`, `needs`, `impact`, and shorter `fix` lines.
|
|
7
11
|
|
|
8
12
|
## 1.0.1 - 2026-06-02
|
|
9
13
|
|
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
|
|
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
package/src/commands/doctor.js
CHANGED
|
@@ -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(
|
|
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
|
|
|
@@ -520,6 +521,13 @@ export default function doctor(args) {
|
|
|
520
521
|
sections.Locks.push({
|
|
521
522
|
issue: `Stale lock on ${lock.target} (${lock.age}s old)`,
|
|
522
523
|
fix: 'Run `nexus clean --stale`.',
|
|
524
|
+
displayGroup: lock.target,
|
|
525
|
+
lockInfo: {
|
|
526
|
+
target: lock.target,
|
|
527
|
+
agent: lock.agent || '',
|
|
528
|
+
kind: 'stale',
|
|
529
|
+
age: `${lock.age}s old`,
|
|
530
|
+
},
|
|
523
531
|
});
|
|
524
532
|
}
|
|
525
533
|
}
|
|
@@ -531,18 +539,38 @@ export default function doctor(args) {
|
|
|
531
539
|
issue: `Active lock on ${lock.target} (${age})`,
|
|
532
540
|
fix: 'No action if the agent is still working. Use `nexus status` to inspect.',
|
|
533
541
|
ok: true,
|
|
542
|
+
displayGroup: lock.target,
|
|
543
|
+
lockInfo: {
|
|
544
|
+
target: lock.target,
|
|
545
|
+
agent: lock.agent || '',
|
|
546
|
+
kind: 'active',
|
|
547
|
+
age,
|
|
548
|
+
},
|
|
534
549
|
});
|
|
535
550
|
if (!lock.model) {
|
|
536
551
|
sections.Locks.push({
|
|
537
552
|
issue: `Active lock on ${lock.target} has no --model metadata`,
|
|
538
553
|
fix: 'Use `nexus claim ... --model <name>` for future claims; only the human operator can declare the real model.',
|
|
539
554
|
ok: true,
|
|
555
|
+
displayGroup: lock.target,
|
|
556
|
+
lockInfo: {
|
|
557
|
+
target: lock.target,
|
|
558
|
+
agent: lock.agent || '',
|
|
559
|
+
kind: 'missing_model',
|
|
560
|
+
},
|
|
540
561
|
});
|
|
541
562
|
}
|
|
542
563
|
if (!lock.verified) {
|
|
543
564
|
sections.Locks.push({
|
|
544
565
|
issue: `Unverified claim on ${lock.target} by ${lock.agent} (trust: ${lock.trustSource}) — no CLAUDECODE or NEXUS_AGENT env detected at claim time`,
|
|
545
566
|
fix: 'If this is a local/unverified model, set NEXUS_AGENT=@handle before claiming. If unexpected, inspect the lock.',
|
|
567
|
+
displayGroup: lock.target,
|
|
568
|
+
lockInfo: {
|
|
569
|
+
target: lock.target,
|
|
570
|
+
agent: lock.agent || '',
|
|
571
|
+
kind: 'unverified',
|
|
572
|
+
trustSource: lock.trustSource,
|
|
573
|
+
},
|
|
546
574
|
});
|
|
547
575
|
}
|
|
548
576
|
}
|
|
@@ -611,8 +639,15 @@ export default function doctor(args) {
|
|
|
611
639
|
if (unapproved.length) {
|
|
612
640
|
for (const id of unapproved) {
|
|
613
641
|
sections['Queue Authorship'].push({
|
|
614
|
-
issue: `Task "${id}" is
|
|
615
|
-
fix: '
|
|
642
|
+
issue: `Task "${id}" is missing Review: approved`,
|
|
643
|
+
fix: 'add `Review: approved` and `Approved by: human`, or move it to `## Proposed Queue`',
|
|
644
|
+
displayGroup: id,
|
|
645
|
+
queueInfo: {
|
|
646
|
+
taskId: id,
|
|
647
|
+
state: 'auto-flow: yes in Ready Queue',
|
|
648
|
+
needs: 'Review: approved',
|
|
649
|
+
impact: 'nexus next will skip it',
|
|
650
|
+
},
|
|
616
651
|
});
|
|
617
652
|
}
|
|
618
653
|
} else {
|
|
@@ -658,40 +693,193 @@ export default function doctor(args) {
|
|
|
658
693
|
}
|
|
659
694
|
|
|
660
695
|
if (changes.length) {
|
|
661
|
-
console.log('Applied fixes
|
|
662
|
-
for (const change of changes) console.log(` - ${change}`);
|
|
696
|
+
console.log(colors.bold(colors.green('Applied fixes')));
|
|
697
|
+
for (const change of changes) console.log(` ${colors.green('-')} ${change}`);
|
|
663
698
|
console.log('');
|
|
664
699
|
}
|
|
665
700
|
|
|
666
701
|
let problemCount = 0;
|
|
667
702
|
for (const [title, entries] of Object.entries(sections)) {
|
|
668
|
-
console.log(`[${title}]`);
|
|
703
|
+
console.log(colors.bold(colors.cyan(`[${title}]`)));
|
|
669
704
|
if (!entries.length) {
|
|
670
|
-
console.log('
|
|
705
|
+
console.log(` ${colors.green('OK')}`);
|
|
671
706
|
console.log('');
|
|
672
707
|
continue;
|
|
673
708
|
}
|
|
674
709
|
|
|
675
|
-
|
|
710
|
+
const actionable = entries.filter((entry) => !entry.ok);
|
|
711
|
+
const informational = entries.filter((entry) => entry.ok);
|
|
712
|
+
problemCount += actionable.length;
|
|
713
|
+
|
|
714
|
+
if (actionable.length) renderEntryBucket('Fix the following', actionable, colors.yellow, colors.red, title, colors);
|
|
715
|
+
if (informational.length) renderEntryBucket('Review / informational', informational, colors.blue, colors.green, title, colors);
|
|
716
|
+
console.log('');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (problemCount) {
|
|
720
|
+
console.log(colors.bold(colors.yellow('Some issues need attention.')));
|
|
721
|
+
console.log(colors.dim('Safe scaffold fixes: `nexus doctor --fix`.'));
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
console.log(colors.bold(colors.green('All checked Nexus categories are ready.')));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function renderEntryBucket(label, entries, headingColor, markerColor, sectionTitle, colors) {
|
|
729
|
+
console.log(` ${headingColor(label)}`);
|
|
730
|
+
if (sectionTitle === 'Locks') {
|
|
731
|
+
renderLockEntries(entries, markerColor, colors);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (sectionTitle === 'Queue Authorship') {
|
|
735
|
+
renderQueueEntries(entries, markerColor, colors);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const groups = groupEntriesForDisplay(entries, sectionTitle);
|
|
739
|
+
for (const group of groups) {
|
|
740
|
+
if (group.label) {
|
|
741
|
+
console.log(` ${colors.bold(group.label)}`);
|
|
742
|
+
}
|
|
743
|
+
for (const entry of group.entries) {
|
|
744
|
+
const baseIndent = group.label ? ' ' : ' ';
|
|
745
|
+
const detailIndent = group.label ? ' ' : ' ';
|
|
676
746
|
const prefix = entry.ok ? '-' : '!';
|
|
677
|
-
console.log(
|
|
747
|
+
console.log(`${baseIndent}${markerColor(prefix)} ${entry.issue}`);
|
|
678
748
|
if (entry.details) {
|
|
679
749
|
for (const detail of entry.details) {
|
|
680
|
-
console.log(
|
|
750
|
+
console.log(`${detailIndent}${colors.dim(detail)}`);
|
|
681
751
|
}
|
|
682
752
|
}
|
|
683
|
-
if (entry.fix)
|
|
684
|
-
|
|
753
|
+
if (entry.fix) {
|
|
754
|
+
console.log(`${detailIndent}${colors.bold('Fix:')} ${colors.dim(entry.fix)}`);
|
|
755
|
+
}
|
|
685
756
|
}
|
|
686
|
-
console.log('');
|
|
687
757
|
}
|
|
758
|
+
}
|
|
688
759
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
760
|
+
function renderLockEntries(entries, markerColor, colors) {
|
|
761
|
+
const groups = new Map();
|
|
762
|
+
|
|
763
|
+
for (const entry of entries) {
|
|
764
|
+
const target = entry.lockInfo?.target || entry.displayGroup || entry.issue;
|
|
765
|
+
if (!groups.has(target)) groups.set(target, []);
|
|
766
|
+
groups.get(target).push(entry);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
for (const [target, lockEntries] of groups) {
|
|
770
|
+
const agent = lockEntries.find((entry) => entry.lockInfo?.agent)?.lockInfo?.agent || '';
|
|
771
|
+
console.log(` ${colors.bold(`file: ${target}`)}`);
|
|
772
|
+
console.log(` ${colors.dim(`by: ${agent || 'unknown'}`)}`);
|
|
773
|
+
for (const entry of lockEntries) {
|
|
774
|
+
const prefix = entry.ok ? '-' : '!';
|
|
775
|
+
const state = formatLockState(entry);
|
|
776
|
+
console.log(` ${markerColor(prefix)} ${state}`);
|
|
777
|
+
if (entry.fix) {
|
|
778
|
+
console.log(` ${colors.bold('fix:')} ${colors.dim(compactLockFix(entry))}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function renderQueueEntries(entries, markerColor, colors) {
|
|
785
|
+
const groups = new Map();
|
|
786
|
+
|
|
787
|
+
for (const entry of entries) {
|
|
788
|
+
const taskId = entry.queueInfo?.taskId || entry.displayGroup || entry.issue;
|
|
789
|
+
if (!groups.has(taskId)) groups.set(taskId, []);
|
|
790
|
+
groups.get(taskId).push(entry);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
for (const [taskId, taskEntries] of groups) {
|
|
794
|
+
console.log(` ${colors.bold(`task: ${taskId}`)}`);
|
|
795
|
+
for (const entry of taskEntries) {
|
|
796
|
+
const prefix = entry.ok ? '-' : '!';
|
|
797
|
+
const state = entry.queueInfo?.state || entry.issue;
|
|
798
|
+
const needs = entry.queueInfo?.needs;
|
|
799
|
+
const impact = entry.queueInfo?.impact;
|
|
800
|
+
console.log(` ${markerColor(prefix)} ${state}`);
|
|
801
|
+
if (needs) {
|
|
802
|
+
console.log(` ${colors.dim(`needs: ${needs}`)}`);
|
|
803
|
+
}
|
|
804
|
+
if (impact) {
|
|
805
|
+
console.log(` ${colors.dim(`impact: ${impact}`)}`);
|
|
806
|
+
}
|
|
807
|
+
if (entry.fix) {
|
|
808
|
+
console.log(` ${colors.bold('fix:')} ${colors.dim(entry.fix)}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
692
811
|
}
|
|
812
|
+
}
|
|
693
813
|
|
|
694
|
-
|
|
814
|
+
function formatLockState(entry) {
|
|
815
|
+
const info = entry.lockInfo;
|
|
816
|
+
if (!info) return entry.issue;
|
|
817
|
+
|
|
818
|
+
switch (info.kind) {
|
|
819
|
+
case 'stale':
|
|
820
|
+
return `stale lock (${info.age})`;
|
|
821
|
+
case 'active':
|
|
822
|
+
return `active lock (${info.age})`;
|
|
823
|
+
case 'missing_model':
|
|
824
|
+
return 'missing --model metadata';
|
|
825
|
+
case 'unverified':
|
|
826
|
+
return `unverified claim (trust: ${info.trustSource || 'unknown'})`;
|
|
827
|
+
default:
|
|
828
|
+
return entry.issue;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function compactLockFix(entry) {
|
|
833
|
+
const info = entry.lockInfo;
|
|
834
|
+
if (!info) return entry.fix;
|
|
835
|
+
|
|
836
|
+
switch (info.kind) {
|
|
837
|
+
case 'stale':
|
|
838
|
+
return 'run `nexus clean --stale`';
|
|
839
|
+
case 'active':
|
|
840
|
+
return 'leave it if someone is working, or inspect with `nexus status`';
|
|
841
|
+
case 'missing_model':
|
|
842
|
+
return 'use `nexus claim ... --model <name>` on future claims';
|
|
843
|
+
case 'unverified':
|
|
844
|
+
return 'set `NEXUS_AGENT=@handle` for local claims, or inspect the lock';
|
|
845
|
+
default:
|
|
846
|
+
return entry.fix;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function groupEntriesForDisplay(entries, sectionTitle) {
|
|
851
|
+
const groups = [];
|
|
852
|
+
const grouped = new Map();
|
|
853
|
+
|
|
854
|
+
for (const entry of entries) {
|
|
855
|
+
const label = entry.displayGroup || inferDisplayGroup(entry.issue, sectionTitle);
|
|
856
|
+
if (!label) {
|
|
857
|
+
groups.push({ label: '', entries: [entry] });
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (!grouped.has(label)) {
|
|
861
|
+
const group = { label, entries: [] };
|
|
862
|
+
grouped.set(label, group);
|
|
863
|
+
groups.push(group);
|
|
864
|
+
}
|
|
865
|
+
grouped.get(label).entries.push(entry);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return groups;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function inferDisplayGroup(issue, sectionTitle) {
|
|
872
|
+
if (sectionTitle === 'Locks') {
|
|
873
|
+
const lockMatch = issue.match(/^(?:Stale lock on|Active lock on|Unverified claim on) ([^ ]+)/);
|
|
874
|
+
if (lockMatch) return lockMatch[1];
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (sectionTitle === 'Queue Authorship') {
|
|
878
|
+
const taskMatch = issue.match(/^Task "([^"]+)"/);
|
|
879
|
+
if (taskMatch) return taskMatch[1];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return '';
|
|
695
883
|
}
|
|
696
884
|
|
|
697
885
|
function extractReadyQueueSection(content) {
|
|
@@ -864,6 +1052,13 @@ const GIT_PRIVACY_COLLAPSE_ROOTS = [
|
|
|
864
1052
|
'session-logs',
|
|
865
1053
|
];
|
|
866
1054
|
|
|
1055
|
+
const GIT_PRIVACY_AGENT_ROOTS = new Set([
|
|
1056
|
+
'.agy',
|
|
1057
|
+
'.claude',
|
|
1058
|
+
'.codex',
|
|
1059
|
+
'.gemini',
|
|
1060
|
+
]);
|
|
1061
|
+
|
|
867
1062
|
function scanPackagePrivacy(root) {
|
|
868
1063
|
const packagePath = join(root, 'package.json');
|
|
869
1064
|
if (!existsSync(packagePath)) return [];
|
|
@@ -895,7 +1090,7 @@ function scanPackagePrivacy(root) {
|
|
|
895
1090
|
return issues;
|
|
896
1091
|
}
|
|
897
1092
|
|
|
898
|
-
function scanGitPrivacy(root) {
|
|
1093
|
+
function scanGitPrivacy(root, config) {
|
|
899
1094
|
const gitDir = join(root, '.git');
|
|
900
1095
|
if (!existsSync(gitDir)) return [];
|
|
901
1096
|
|
|
@@ -907,10 +1102,10 @@ function scanGitPrivacy(root) {
|
|
|
907
1102
|
if (result.status !== 0) return [];
|
|
908
1103
|
|
|
909
1104
|
const tracked = result.stdout.split('\n').filter(Boolean);
|
|
910
|
-
return summarizeGitPrivacyIssues(tracked);
|
|
1105
|
+
return summarizeGitPrivacyIssues(tracked, config);
|
|
911
1106
|
}
|
|
912
1107
|
|
|
913
|
-
function summarizeGitPrivacyIssues(tracked) {
|
|
1108
|
+
function summarizeGitPrivacyIssues(tracked, config) {
|
|
914
1109
|
const grouped = new Map();
|
|
915
1110
|
const singles = [];
|
|
916
1111
|
|
|
@@ -925,6 +1120,7 @@ function summarizeGitPrivacyIssues(tracked) {
|
|
|
925
1120
|
}
|
|
926
1121
|
|
|
927
1122
|
const issues = [];
|
|
1123
|
+
const agentRootSummaries = [];
|
|
928
1124
|
|
|
929
1125
|
for (const file of singles.sort()) {
|
|
930
1126
|
issues.push({
|
|
@@ -935,6 +1131,10 @@ function summarizeGitPrivacyIssues(tracked) {
|
|
|
935
1131
|
|
|
936
1132
|
for (const root of Array.from(grouped.keys()).sort()) {
|
|
937
1133
|
const files = grouped.get(root).slice().sort(compareGitPrivacyFiles);
|
|
1134
|
+
if (GIT_PRIVACY_AGENT_ROOTS.has(root)) {
|
|
1135
|
+
agentRootSummaries.push(`${root}/ (${files.length} files)`);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
938
1138
|
const samples = files.slice(0, 5);
|
|
939
1139
|
const hiddenCount = files.length - samples.length;
|
|
940
1140
|
const noun = files.length === 1 ? 'path' : 'paths';
|
|
@@ -947,6 +1147,21 @@ function summarizeGitPrivacyIssues(tracked) {
|
|
|
947
1147
|
issues.push({ issue, fix, details });
|
|
948
1148
|
}
|
|
949
1149
|
|
|
1150
|
+
if (agentRootSummaries.length) {
|
|
1151
|
+
issues.unshift({
|
|
1152
|
+
issue: `Tracked shared agent trees detected: ${agentRootSummaries.join(', ')}`,
|
|
1153
|
+
fix: config.doctor.allowTrackedAgentTrees
|
|
1154
|
+
? undefined
|
|
1155
|
+
: '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.',
|
|
1156
|
+
details: [
|
|
1157
|
+
config.doctor.allowTrackedAgentTrees
|
|
1158
|
+
? 'Allowed by `.nexus/config.json` because this repo intentionally versions shared agent trees.'
|
|
1159
|
+
: 'This can be normal in private repos that share agent protocols and memory in Git.',
|
|
1160
|
+
],
|
|
1161
|
+
ok: config.doctor.allowTrackedAgentTrees,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
950
1165
|
return issues;
|
|
951
1166
|
}
|
|
952
1167
|
|
|
@@ -969,6 +1184,26 @@ function gitPrivacyPriority(file) {
|
|
|
969
1184
|
return 2;
|
|
970
1185
|
}
|
|
971
1186
|
|
|
1187
|
+
function createColors() {
|
|
1188
|
+
const enabled = supportsColor();
|
|
1189
|
+
const wrap = (open, close) => (value) => enabled ? `\u001b[${open}m${value}\u001b[${close}m` : String(value);
|
|
1190
|
+
return {
|
|
1191
|
+
bold: wrap(1, 22),
|
|
1192
|
+
dim: wrap(2, 22),
|
|
1193
|
+
red: wrap(31, 39),
|
|
1194
|
+
green: wrap(32, 39),
|
|
1195
|
+
yellow: wrap(33, 39),
|
|
1196
|
+
blue: wrap(34, 39),
|
|
1197
|
+
cyan: wrap(36, 39),
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function supportsColor() {
|
|
1202
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') return true;
|
|
1203
|
+
if ('NO_COLOR' in process.env) return false;
|
|
1204
|
+
return Boolean(process.stdout && process.stdout.isTTY);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
972
1207
|
function scanGeneratedArtifacts(root) {
|
|
973
1208
|
const gitDir = join(root, '.git');
|
|
974
1209
|
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
|
+
}
|