@inkobytes/nexus 1.0.1 → 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 +8 -0
- package/README.md +14 -0
- package/package.json +1 -1
- package/src/commands/doctor.js +200 -20
- package/src/commands/init.js +2 -0
- package/src/commands/status.js +5 -0
- package/src/lib/config.js +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3 - 2026-06-03
|
|
4
|
+
|
|
5
|
+
- Collapsed large `nexus doctor` Git Privacy floods into grouped per-root summaries with sample paths.
|
|
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
|
+
|
|
3
11
|
## 1.0.1 - 2026-06-02
|
|
4
12
|
|
|
5
13
|
- Added colorized `nexus help` output for a more readable CLI experience.
|
package/README.md
CHANGED
|
@@ -166,6 +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, with shared agent dirs collapsed into one concise note
|
|
170
|
+
- colorized action buckets so fixes and informational lock notes are easier to scan
|
|
169
171
|
- stale nexus locks
|
|
170
172
|
- missing agent instructions specifically for nexus
|
|
171
173
|
- missing continuity and memory scaffolds
|
|
@@ -175,6 +177,18 @@ With `--fix`, Nexus creates safe missing scaffolds and updates managed protocol
|
|
|
175
177
|
|
|
176
178
|
With `--json`, Nexus prints the same health sections as structured JSON for tools such as Inkobytes reports.
|
|
177
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
|
+
|
|
178
192
|
Use `doctor` for audit or repair. Do not make it the normal first command for every agent session.
|
|
179
193
|
|
|
180
194
|
### `nexus soul [--file <path>] [--status | --remove]`
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -93,6 +93,8 @@ This project uses Nexus for multi-agent coordination.
|
|
|
93
93
|
### Nexus Rules
|
|
94
94
|
|
|
95
95
|
- Claim before editing shared project files: \`nexus claim <path> @Agent "intent"\`.
|
|
96
|
+
- Nexus is agent-native and file-native, not feature-native: commit and release by file as soon as the file reaches a coherent checkpoint.
|
|
97
|
+
- Do not hold claims while waiting to bundle related files into one feature commit; that blocks other agents who may need the file next.
|
|
96
98
|
- Release finished work through Nexus: \`nexus release <path> "commit message"\`.
|
|
97
99
|
- Use \`nexus next @Agent\` for the next safe queue task.
|
|
98
100
|
- Do not free-roam into unassigned or \`Auto-flow: no\` work without user approval.
|
|
@@ -345,6 +347,7 @@ export default function doctor(args) {
|
|
|
345
347
|
const fix = args.includes('--fix');
|
|
346
348
|
const json = args.includes('--json');
|
|
347
349
|
const root = cwd();
|
|
350
|
+
const colors = createColors();
|
|
348
351
|
const sections = {
|
|
349
352
|
'Nexus Files': [],
|
|
350
353
|
'Agent Instructions': [],
|
|
@@ -363,8 +366,8 @@ export default function doctor(args) {
|
|
|
363
366
|
const config = getConfig(root);
|
|
364
367
|
|
|
365
368
|
if (!json) {
|
|
366
|
-
console.log(`Nexus doctor${fix ? ' --fix' : ''}`);
|
|
367
|
-
console.log(
|
|
369
|
+
console.log(colors.bold(colors.cyan(`Nexus doctor${fix ? ' --fix' : ''}`)));
|
|
370
|
+
console.log(`${colors.dim('Repo:')} ${root}\n`);
|
|
368
371
|
}
|
|
369
372
|
|
|
370
373
|
const nexusProtocolFiles = ['_NEXUS_CONSTITUTION.md', '_NEXUS_QUEUE.md', '_NEXUS_STANDUP.md'];
|
|
@@ -410,7 +413,7 @@ export default function doctor(args) {
|
|
|
410
413
|
sections['Package Privacy'].push(issue);
|
|
411
414
|
}
|
|
412
415
|
|
|
413
|
-
for (const issue of scanGitPrivacy(root)) {
|
|
416
|
+
for (const issue of scanGitPrivacy(root, config)) {
|
|
414
417
|
sections['Git Privacy'].push(issue);
|
|
415
418
|
}
|
|
416
419
|
|
|
@@ -656,35 +659,95 @@ export default function doctor(args) {
|
|
|
656
659
|
}
|
|
657
660
|
|
|
658
661
|
if (changes.length) {
|
|
659
|
-
console.log('Applied fixes
|
|
660
|
-
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}`);
|
|
661
664
|
console.log('');
|
|
662
665
|
}
|
|
663
666
|
|
|
664
667
|
let problemCount = 0;
|
|
665
668
|
for (const [title, entries] of Object.entries(sections)) {
|
|
666
|
-
console.log(`[${title}]`);
|
|
669
|
+
console.log(colors.bold(colors.cyan(`[${title}]`)));
|
|
667
670
|
if (!entries.length) {
|
|
668
|
-
console.log('
|
|
671
|
+
console.log(` ${colors.green('OK')}`);
|
|
669
672
|
console.log('');
|
|
670
673
|
continue;
|
|
671
674
|
}
|
|
672
675
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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);
|
|
679
682
|
console.log('');
|
|
680
683
|
}
|
|
681
684
|
|
|
682
685
|
if (problemCount) {
|
|
683
|
-
console.log('Some issues need attention.
|
|
686
|
+
console.log(colors.bold(colors.yellow('Some issues need attention.')));
|
|
687
|
+
console.log(colors.dim('Safe scaffold fixes: `nexus doctor --fix`.'));
|
|
684
688
|
return;
|
|
685
689
|
}
|
|
686
690
|
|
|
687
|
-
console.log('All checked Nexus categories are ready.');
|
|
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 ? ' ' : ' ';
|
|
704
|
+
const prefix = entry.ok ? '-' : '!';
|
|
705
|
+
console.log(`${baseIndent}${markerColor(prefix)} ${entry.issue}`);
|
|
706
|
+
if (entry.details) {
|
|
707
|
+
for (const detail of entry.details) {
|
|
708
|
+
console.log(`${detailIndent}${colors.dim(detail)}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (entry.fix) {
|
|
712
|
+
console.log(`${detailIndent}${colors.bold('Fix:')} ${colors.dim(entry.fix)}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
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];
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (sectionTitle === 'Queue Authorship') {
|
|
746
|
+
const taskMatch = issue.match(/^Task "([^"]+)"/);
|
|
747
|
+
if (taskMatch) return taskMatch[1];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return '';
|
|
688
751
|
}
|
|
689
752
|
|
|
690
753
|
function extractReadyQueueSection(content) {
|
|
@@ -843,6 +906,27 @@ const PRIVATE_GIT_PATHS = [
|
|
|
843
906
|
'USER.md',
|
|
844
907
|
];
|
|
845
908
|
|
|
909
|
+
const GIT_PRIVACY_COLLAPSE_ROOTS = [
|
|
910
|
+
'.agent-session-logs',
|
|
911
|
+
'.agent-*',
|
|
912
|
+
'.agy',
|
|
913
|
+
'.antigravitycli',
|
|
914
|
+
'.claude',
|
|
915
|
+
'.codex',
|
|
916
|
+
'.gemini',
|
|
917
|
+
'.nexus/local',
|
|
918
|
+
'docs-priv',
|
|
919
|
+
'scratch',
|
|
920
|
+
'session-logs',
|
|
921
|
+
];
|
|
922
|
+
|
|
923
|
+
const GIT_PRIVACY_AGENT_ROOTS = new Set([
|
|
924
|
+
'.agy',
|
|
925
|
+
'.claude',
|
|
926
|
+
'.codex',
|
|
927
|
+
'.gemini',
|
|
928
|
+
]);
|
|
929
|
+
|
|
846
930
|
function scanPackagePrivacy(root) {
|
|
847
931
|
const packagePath = join(root, 'package.json');
|
|
848
932
|
if (!existsSync(packagePath)) return [];
|
|
@@ -874,7 +958,7 @@ function scanPackagePrivacy(root) {
|
|
|
874
958
|
return issues;
|
|
875
959
|
}
|
|
876
960
|
|
|
877
|
-
function scanGitPrivacy(root) {
|
|
961
|
+
function scanGitPrivacy(root, config) {
|
|
878
962
|
const gitDir = join(root, '.git');
|
|
879
963
|
if (!existsSync(gitDir)) return [];
|
|
880
964
|
|
|
@@ -886,10 +970,106 @@ function scanGitPrivacy(root) {
|
|
|
886
970
|
if (result.status !== 0) return [];
|
|
887
971
|
|
|
888
972
|
const tracked = result.stdout.split('\n').filter(Boolean);
|
|
889
|
-
return tracked
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
973
|
+
return summarizeGitPrivacyIssues(tracked, config);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function summarizeGitPrivacyIssues(tracked, config) {
|
|
977
|
+
const grouped = new Map();
|
|
978
|
+
const singles = [];
|
|
979
|
+
|
|
980
|
+
for (const file of tracked) {
|
|
981
|
+
const root = gitPrivacyRoot(file);
|
|
982
|
+
if (!root) {
|
|
983
|
+
singles.push(file);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (!grouped.has(root)) grouped.set(root, []);
|
|
987
|
+
grouped.get(root).push(file);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const issues = [];
|
|
991
|
+
const agentRootSummaries = [];
|
|
992
|
+
|
|
993
|
+
for (const file of singles.sort()) {
|
|
994
|
+
issues.push({
|
|
995
|
+
issue: `Git tracks private/local path: ${file}`,
|
|
996
|
+
fix: 'Untrack it without deleting local files: `git rm --cached -r -- <path>`, then add an ignore rule.',
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
for (const root of Array.from(grouped.keys()).sort()) {
|
|
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
|
+
}
|
|
1006
|
+
const samples = files.slice(0, 5);
|
|
1007
|
+
const hiddenCount = files.length - samples.length;
|
|
1008
|
+
const noun = files.length === 1 ? 'path' : 'paths';
|
|
1009
|
+
const issue = `Git tracks private/local ${noun} under ${root}/ (${files.length} files)`;
|
|
1010
|
+
const fix = 'Review the sample paths below. If the tree is intentionally tracked in this repo, keep it. Otherwise untrack it without deleting local files: `git rm --cached -r -- <path>`, then add an ignore rule.';
|
|
1011
|
+
const details = samples.map((file) => `sample: ${file}`);
|
|
1012
|
+
if (hiddenCount > 0) {
|
|
1013
|
+
details.push(`...and ${hiddenCount} more tracked paths`);
|
|
1014
|
+
}
|
|
1015
|
+
issues.push({ issue, fix, details });
|
|
1016
|
+
}
|
|
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
|
+
|
|
1033
|
+
return issues;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function gitPrivacyRoot(file) {
|
|
1037
|
+
for (const root of GIT_PRIVACY_COLLAPSE_ROOTS) {
|
|
1038
|
+
if (matchesPrivatePath(file, root)) {
|
|
1039
|
+
return root.replace(/\/$/, '');
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return '';
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function compareGitPrivacyFiles(a, b) {
|
|
1046
|
+
return gitPrivacyPriority(a) - gitPrivacyPriority(b) || a.localeCompare(b);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function gitPrivacyPriority(file) {
|
|
1050
|
+
if (/\/(CONTINUITY\.md|memories\/)/.test(file)) return 0;
|
|
1051
|
+
if (/\/(AGENTS\.md|CLAUDE\.md|GEMINI\.md)$/.test(file)) return 1;
|
|
1052
|
+
return 2;
|
|
1053
|
+
}
|
|
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);
|
|
893
1073
|
}
|
|
894
1074
|
|
|
895
1075
|
function scanGeneratedArtifacts(root) {
|
package/src/commands/init.js
CHANGED
|
@@ -379,6 +379,8 @@ This project uses Nexus for multi-agent coordination.
|
|
|
379
379
|
### Nexus Rules
|
|
380
380
|
|
|
381
381
|
- Claim before editing shared project files: \`nexus claim <path> @Agent "intent"\`.
|
|
382
|
+
- Nexus is agent-native and file-native, not feature-native: commit and release by file as soon as the file reaches a coherent checkpoint.
|
|
383
|
+
- Do not hold claims while waiting to bundle related files into one feature commit; that blocks other agents who may need the file next.
|
|
382
384
|
- Release finished work through Nexus: \`nexus release <path> "commit message"\`.
|
|
383
385
|
- Use \`nexus next @Agent\` for the next safe queue task.
|
|
384
386
|
- Do not free-roam into unassigned or \`Auto-flow: no\` work without user approval.
|
package/src/commands/status.js
CHANGED
|
@@ -7,6 +7,8 @@ import { listLocks } from '../lib/lockManager.js';
|
|
|
7
7
|
import { getConfig } from '../lib/config.js';
|
|
8
8
|
import { spawnSync } from 'child_process';
|
|
9
9
|
|
|
10
|
+
const FILE_FLOW_WARNING = 'Stale claim. Commit this file now.';
|
|
11
|
+
|
|
10
12
|
export default function status(args) {
|
|
11
13
|
const config = getConfig();
|
|
12
14
|
const locks = listLocks();
|
|
@@ -36,6 +38,9 @@ export default function status(args) {
|
|
|
36
38
|
const staleTag = stale ? ' ⚠️ STALE' : '';
|
|
37
39
|
console.log(` 🔒 ${lock.target}`);
|
|
38
40
|
console.log(` Agent: ${agent} | Age: ${ageStr}${staleTag}`);
|
|
41
|
+
if (stale) {
|
|
42
|
+
console.log(` Warning: ${FILE_FLOW_WARNING}`);
|
|
43
|
+
}
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
console.log('');
|
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
|
+
}
|