mcp-nervous-system 1.3.0 → 1.5.0

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.
Files changed (4) hide show
  1. package/README.md +10 -16
  2. package/index.js +801 -7
  3. package/package.json +2 -2
  4. package/stdio.js +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  7 mechanically enforced rules that prevent the most common failure modes when LLMs have access to real infrastructure: context loss, silent failures, file damage, goal drift, and overreach.
6
6
 
7
- Built by [Arthur Palyan](https://www.levelsofself.com) at Palyan AI. 14 tools including task complexity classification and user intent parsing. Battle-tested on an 11-member AI family running 28 processes 24/7 on a single VPS. 58+ violations logged, 0 bypassed.
7
+ Built by [Arthur Palyan](https://www.levelsofself.com) at Palyan Family AI System. 12 tools including configuration drift detection and emergency kill switch. Battle-tested on an 11-member AI family running 28 processes 24/7 on a single VPS. 58+ violations logged, 0 bypassed.
8
8
 
9
9
  ## The Problem
10
10
 
@@ -60,17 +60,12 @@ Protocol: MCP 2024-11-05 (Streamable HTTP + SSE)
60
60
  Authentication: None required
61
61
  ```
62
62
 
63
- ## NEW in v1.3.0
63
+ ## NEW in v1.4.0
64
64
 
65
- **classify_task_complexity** (free tier)
66
- Analyzes any task across 6 dimensions (scope, judgment, risk, context depth, ambiguity, verification difficulty) and recommends the optimal model tier. Routes simple tasks to Haiku (~90% savings), standard tasks to Sonnet (~60% savings), and complex reasoning to Opus. Pass optional structured hints for higher accuracy.
65
+ **drift_audit** (free tier)
66
+ Configuration drift detection across 5 scopes: roles, versions, files, processes, and website. Scans source-of-truth files (family-roles.json, package.json, UNTOUCHABLE_FILES.txt) against all downstream references - HTML pages, JSON configs, markdown docs, and running PM2 processes. Change one file, drift_audit tells you everywhere else that needs updating. The closed loop that keeps your entire system consistent.
67
67
 
68
- **parse_user_intent** (free tier)
69
- Decomposes user requests into numbered deliverables with confidence scoring. Below 80% confidence: the model should clarify before executing, not guess. Detects ambiguity, unresolved pronouns, hedging language, and implicit expectations. The 80% rule: understand first, execute second.
70
-
71
- These tools work together. parse_user_intent breaks down what the person wants. classify_task_complexity routes each piece to the right model. The Nervous System enforces the rules while execution happens.
72
-
73
- **Positioning: Auto mode (launching March 12) decides what Claude CAN do. The Nervous System governs HOW it behaves while doing it.**
68
+ **Positioning: Auto mode decides what Claude CAN do. The Nervous System governs HOW it behaves while doing it.**
74
69
 
75
70
  ## The 7 Rules
76
71
 
@@ -84,7 +79,7 @@ These tools work together. parse_user_intent breaks down what the person wants.
84
79
  | 6 | **Ask Before Touching** | Unauthorized changes. Logic changes need human approval. |
85
80
  | 7 | **Hand Off** | Context loss. Written handoffs every 3-4 exchanges. |
86
81
 
87
- ## MCP Tools (14)
82
+ ## MCP Tools (12)
88
83
 
89
84
  | Tool | Description |
90
85
  |------|------------|
@@ -99,9 +94,7 @@ These tools work together. parse_user_intent breaks down what the person wants.
99
94
  | `emergency_kill_switch` | Emergency shutdown of all PM2 processes. Requires kill switch secret. Logs to tamper-evident audit trail |
100
95
  | `verify_audit_chain` | Walks the SHA-256 hash-chained audit log and verifies every entry. Returns chain integrity status |
101
96
  | `dispatch_to_llm` | Spawns a background LLM agent to handle a task. Checks RAM, enforces max 2 concurrent dispatches |
102
- | `classify_task_complexity` | Analyzes task across 6 dimensions, recommends optimal model tier (Haiku/Sonnet/Opus) |
103
- | `parse_user_intent` | Decomposes requests into numbered deliverables with confidence scoring. 80% gate |
104
- | `get_positioning` | Returns competitive positioning and differentiation messaging |
97
+ | `drift_audit` | Configuration drift detection across roles, versions, files, processes, and website. Finds stale values everywhere. |
105
98
 
106
99
  ## Kill Switch
107
100
 
@@ -134,16 +127,17 @@ The Nervous System provides practical compliance tools for the EU AI Act. See th
134
127
 
135
128
  https://api.100levelup.com/family/eu-ai-act.html
136
129
 
137
- ## Resources (4)
130
+ ## Resources (5)
138
131
 
139
132
  - `nervous-system://framework` - The complete framework
140
133
  - `nervous-system://quick-start` - Quick start guide
141
134
  - `nervous-system://rules` - The 7 core rules
142
135
  - `nervous-system://templates` - Templates for handoffs, worklogs, preflight
136
+ - `nervous-system://drift-audit` - Configuration drift detection
143
137
 
144
138
  ## Production Stats
145
139
 
146
- From the live Palyan AI deployment (Feb 28 - Mar 5, 2026):
140
+ From the live Palyan Family AI System deployment (Feb 28 - Mar 5, 2026):
147
141
 
148
142
  - **58+** violations caught
149
143
  - **29** edits blocked by preflight
package/index.js CHANGED
@@ -129,7 +129,7 @@ const MCP_VERSION = '2024-11-05';
129
129
  // Server info
130
130
  const SERVER_INFO = {
131
131
  name: 'nervous-system',
132
- version: '1.1.0'
132
+ version: '1.5.0'
133
133
  };
134
134
 
135
135
  // ============================================================
@@ -138,7 +138,7 @@ const SERVER_INFO = {
138
138
 
139
139
  const FRAMEWORK = {
140
140
  name: 'The Nervous System',
141
- version: '1.1.0',
141
+ version: '1.5.0',
142
142
  author: 'Arthur Palyan',
143
143
  tagline: 'Anthropic built the brain. Arthur built the nervous system that keeps it from hurting itself.',
144
144
  problem: 'LLMs lose context between sessions, loop on problems instead of dispatching, silently fail without progress notes, edit protected files, drift from the real problem, and solve instead of asking.',
@@ -600,6 +600,43 @@ const TOOLS = [
600
600
  },
601
601
  required: ['task']
602
602
  }
603
+ },
604
+ // NEW: Drift Audit
605
+ {
606
+ name: 'drift_audit',
607
+ annotations: { title: 'Configuration Drift Audit', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
608
+ description: 'Scans for configuration drift - finds files, docs, and configs that reference outdated values. Detects when a file is renamed but references are not updated, when roles change but downstream docs still show old values, or when running processes do not match documentation.',
609
+ inputSchema: {
610
+ type: 'object',
611
+ properties: {
612
+ scope: {
613
+ type: 'string',
614
+ enum: ['full', 'roles', 'versions', 'files', 'processes', 'website'],
615
+ description: 'What to audit. full=everything, roles=family role consistency, versions=NS version numbers, files=file reference integrity, processes=PM2 vs docs, website=HTML pages and configs for stale values'
616
+ }
617
+ }
618
+ }
619
+ },
620
+ // NEW: Security Audit
621
+ {
622
+ name: 'security_audit',
623
+ annotations: { title: 'Security Audit', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
624
+ description: 'Scans for security vulnerabilities - hardcoded passwords in HTML, exposed API keys, missing TLS, missing rate limiting, exposed bot tokens, and insecure file permissions.',
625
+ inputSchema: { type: 'object', properties: {} }
626
+ },
627
+ // NEW: Auto Propagate
628
+ {
629
+ name: 'auto_propagate',
630
+ annotations: { title: 'Auto Propagate', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
631
+ description: 'Runs all 3 propagators (role, version, content) and reports what changed vs what was already current. Ensures all downstream files match source-of-truth values.',
632
+ inputSchema: { type: 'object', properties: {} }
633
+ },
634
+ // NEW: Session Close
635
+ {
636
+ name: 'session_close',
637
+ annotations: { title: 'Session Close', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
638
+ description: 'One-call session close. Runs drift_audit scope=full, then all 3 propagators. Returns combined results. The end-of-session button.',
639
+ inputSchema: { type: 'object', properties: {} }
603
640
  }
604
641
  ];
605
642
 
@@ -608,9 +645,737 @@ const RESOURCES = [
608
645
  { uri: 'nervous-system://framework', name: 'The Nervous System Framework', description: 'Complete behavioral enforcement framework for LLM management', mimeType: 'text/plain' },
609
646
  { uri: 'nervous-system://quick-start', name: 'Quick Start Guide', description: 'How to implement the nervous system in your own LLM deployment', mimeType: 'text/plain' },
610
647
  { uri: 'nervous-system://rules', name: 'The 7 Core Rules', description: 'All 7 behavioral rules with explanations and enforcement', mimeType: 'text/plain' },
611
- { uri: 'nervous-system://templates', name: 'Templates', description: 'Ready-to-use templates for handoffs, worklogs, preflight, and untouchable lists', mimeType: 'text/plain' }
648
+ { uri: 'nervous-system://templates', name: 'Templates', description: 'Ready-to-use templates for handoffs, worklogs, preflight, and untouchable lists', mimeType: 'text/plain' },
649
+ { uri: 'nervous-system://drift-audit', name: 'Drift Audit', description: 'Configuration drift detection - checks roles, versions, file references, and running processes against source-of-truth files', mimeType: 'text/plain' }
612
650
  ];
613
651
 
652
+ // ============================================================
653
+ // DRIFT AUDIT ENGINE
654
+ // ============================================================
655
+
656
+ const { execSync } = require('child_process');
657
+
658
+ function safeReadJSON(filePath) {
659
+ try {
660
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
661
+ } catch (e) {
662
+ return null;
663
+ }
664
+ }
665
+
666
+ function safeReadFile(filePath) {
667
+ try {
668
+ return fs.readFileSync(filePath, 'utf8');
669
+ } catch (e) {
670
+ return null;
671
+ }
672
+ }
673
+
674
+ function auditRoles() {
675
+ const drifts = [];
676
+ let cleanChecks = 0;
677
+ const rolesFile = '/root/family-data/family-roles.json';
678
+ const roles = safeReadJSON(rolesFile);
679
+ if (!roles || !roles.members) {
680
+ drifts.push({ type: 'missing_source', source: rolesFile, target: '', field: '', expected: 'valid JSON with members array', found: 'missing or invalid' });
681
+ return { drifts, cleanChecks };
682
+ }
683
+
684
+ const sourceRoles = {};
685
+ for (const m of roles.members) {
686
+ sourceRoles[m.id] = { name: m.name, aka: m.aka, role: m.role };
687
+ }
688
+
689
+ // Check family-status.json
690
+ const statusFile = '/root/family-data/family-status.json';
691
+ const status = safeReadJSON(statusFile);
692
+ if (status && status.members) {
693
+ for (const m of status.members) {
694
+ const src = sourceRoles[m.id];
695
+ if (!src) continue;
696
+ if (m.aka && m.aka !== src.aka) {
697
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-status.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
698
+ } else { cleanChecks++; }
699
+ if (m.role && m.role !== src.role) {
700
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-status.json', field: `${m.id}.role`, expected: src.role, found: m.role });
701
+ } else { cleanChecks++; }
702
+ }
703
+ }
704
+
705
+ // Check system-config.json
706
+ const configFile = '/root/family-data/system-config.json';
707
+ const config = safeReadJSON(configFile);
708
+ if (config && config.family_members) {
709
+ for (const m of config.family_members) {
710
+ const src = sourceRoles[m.id];
711
+ if (!src) continue;
712
+ if (m.aka && m.aka !== src.aka) {
713
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'system-config.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
714
+ } else { cleanChecks++; }
715
+ if (m.role && m.role !== src.role) {
716
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'system-config.json', field: `${m.id}.role`, expected: src.role, found: m.role });
717
+ } else { cleanChecks++; }
718
+ }
719
+ }
720
+
721
+ // Check family-guide.json
722
+ const guideFile = '/root/family-data/family-guide.json';
723
+ const guide = safeReadJSON(guideFile);
724
+ if (guide && guide.members) {
725
+ for (const m of guide.members) {
726
+ const src = sourceRoles[m.id];
727
+ if (!src) continue;
728
+ if (m.aka && m.aka !== src.aka) {
729
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-guide.json', field: `${m.id}.aka`, expected: src.aka, found: m.aka });
730
+ } else { cleanChecks++; }
731
+ if (m.role && m.role !== src.role) {
732
+ drifts.push({ type: 'role_mismatch', source: 'family-roles.json', target: 'family-guide.json', field: `${m.id}.role`, expected: src.role, found: m.role });
733
+ } else { cleanChecks++; }
734
+ }
735
+ }
736
+
737
+ // Check HTML files for role references
738
+ const htmlFiles = [
739
+ { path: '/root/family-home/index.html', name: 'index.html' },
740
+ { path: '/root/family-home/explorer.html', name: 'explorer.html' },
741
+ { path: '/root/family-home/meet.html', name: 'meet.html' }
742
+ ];
743
+ for (const hf of htmlFiles) {
744
+ const content = safeReadFile(hf.path);
745
+ if (!content) continue;
746
+ for (const [id, src] of Object.entries(sourceRoles)) {
747
+ if (content.includes(src.name)) { cleanChecks++; }
748
+ }
749
+ }
750
+
751
+ // Check mcp-ops-server.js
752
+ const opsContent = safeReadFile('/root/mcp-ops-server.js');
753
+ if (opsContent) {
754
+ for (const [id, src] of Object.entries(sourceRoles)) {
755
+ if (opsContent.includes(`"${src.aka}"`) || opsContent.includes(`'${src.aka}'`)) {
756
+ cleanChecks++;
757
+ }
758
+ }
759
+ }
760
+
761
+ return { drifts, cleanChecks };
762
+ }
763
+
764
+ function auditVersions() {
765
+ const drifts = [];
766
+ let cleanChecks = 0;
767
+ const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
768
+ const pkg = safeReadJSON(pkgFile);
769
+ const expectedVersion = pkg ? pkg.version : null;
770
+ if (!expectedVersion) {
771
+ drifts.push({ type: 'missing_source', source: pkgFile, target: '', field: 'version', expected: 'valid version', found: 'missing' });
772
+ return { drifts, cleanChecks };
773
+ }
774
+
775
+ // Check SERVER_INFO.version and FRAMEWORK.version in index.js
776
+ const indexContent = safeReadFile('/root/github-repos/mcp-nervous-system/index.js');
777
+ if (indexContent) {
778
+ const siMatch = indexContent.match(/SERVER_INFO\s*=\s*\{[^}]*version:\s*'([^']+)'/);
779
+ if (siMatch) {
780
+ if (siMatch[1] !== expectedVersion) {
781
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js SERVER_INFO', field: 'version', expected: expectedVersion, found: siMatch[1] });
782
+ } else { cleanChecks++; }
783
+ }
784
+ const fwMatch = indexContent.match(/FRAMEWORK\s*=\s*\{[^}]*version:\s*'([^']+)'/);
785
+ if (fwMatch) {
786
+ if (fwMatch[1] !== expectedVersion) {
787
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js FRAMEWORK', field: 'version', expected: expectedVersion, found: fwMatch[1] });
788
+ } else { cleanChecks++; }
789
+ }
790
+ // Check health endpoint version
791
+ const healthMatch = indexContent.match(/version:\s*'([^']+)'.*?service:\s*'nervous-system/);
792
+ if (!healthMatch) {
793
+ const healthMatch2 = indexContent.match(/service:\s*'nervous-system-mcp',\s*version:\s*'([^']+)'/);
794
+ if (healthMatch2) {
795
+ if (healthMatch2[1] !== expectedVersion) {
796
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js health endpoint', field: 'version', expected: expectedVersion, found: healthMatch2[1] });
797
+ } else { cleanChecks++; }
798
+ }
799
+ }
800
+ // Check startup log version
801
+ const startupMatch = indexContent.match(/Nervous System v([0-9.]+) running/);
802
+ if (startupMatch) {
803
+ if (startupMatch[1] !== expectedVersion) {
804
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js startup log', field: 'version', expected: expectedVersion, found: startupMatch[1] });
805
+ } else { cleanChecks++; }
806
+ }
807
+ // Check root endpoint version
808
+ const rootMatch = indexContent.match(/name:\s*'The Nervous System MCP Server'[\s\S]*?version:\s*'([^']+)'/);
809
+ if (rootMatch) {
810
+ if (rootMatch[1] !== expectedVersion) {
811
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'index.js root endpoint', field: 'version', expected: expectedVersion, found: rootMatch[1] });
812
+ } else { cleanChecks++; }
813
+ }
814
+ }
815
+
816
+ // Check BUSINESS_BUILDER.md
817
+ const bbContent = safeReadFile('/root/family-data/BUSINESS_BUILDER.md');
818
+ if (bbContent) {
819
+ const bbMatch = bbContent.match(/[Nn]ervous [Ss]ystem.*?v?(\d+\.\d+\.\d+)/);
820
+ if (bbMatch && bbMatch[1] !== expectedVersion) {
821
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'BUSINESS_BUILDER.md', field: 'ns_version', expected: expectedVersion, found: bbMatch[1] });
822
+ } else if (bbMatch) { cleanChecks++; }
823
+ }
824
+
825
+ // Check gateway.html
826
+ const gwContent = safeReadFile('/root/family-home/gateway.html');
827
+ if (gwContent) {
828
+ const gwMatch = gwContent.match(/[Vv]ersion[:\s]*v?(\d+\.\d+\.\d+)/);
829
+ if (gwMatch && gwMatch[1] !== expectedVersion) {
830
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'gateway.html', field: 'version', expected: expectedVersion, found: gwMatch[1] });
831
+ } else if (gwMatch) { cleanChecks++; }
832
+ }
833
+
834
+ // Check README.md
835
+ const readmeContent = safeReadFile('/root/github-repos/mcp-nervous-system/README.md');
836
+ if (readmeContent) {
837
+ const rmMatch = readmeContent.match(/[Vv]ersion[:\s]*v?(\d+\.\d+\.\d+)/);
838
+ if (rmMatch && rmMatch[1] !== expectedVersion) {
839
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'README.md', field: 'version', expected: expectedVersion, found: rmMatch[1] });
840
+ } else if (rmMatch) { cleanChecks++; }
841
+ }
842
+
843
+ // Check family-roles.json stats
844
+ const roles = safeReadJSON('/root/family-data/family-roles.json');
845
+ if (roles && roles.stats) {
846
+ if (roles.stats.ns_version && roles.stats.ns_version !== expectedVersion) {
847
+ drifts.push({ type: 'version_mismatch', source: 'package.json', target: 'family-roles.json', field: 'stats.ns_version', expected: expectedVersion, found: roles.stats.ns_version });
848
+ } else if (roles.stats.ns_version) { cleanChecks++; }
849
+
850
+ // Check tool count
851
+ const actualToolCount = TOOLS.length;
852
+ if (roles.stats.ns_tools && roles.stats.ns_tools !== actualToolCount) {
853
+ drifts.push({ type: 'tool_count_mismatch', source: 'TOOLS array', target: 'family-roles.json', field: 'stats.ns_tools', expected: String(actualToolCount), found: String(roles.stats.ns_tools) });
854
+ } else if (roles.stats.ns_tools) { cleanChecks++; }
855
+ }
856
+
857
+ return { drifts, cleanChecks };
858
+ }
859
+
860
+ function auditFiles() {
861
+ const drifts = [];
862
+ let cleanChecks = 0;
863
+
864
+ // Check UNTOUCHABLE_FILES.txt - verify each file exists
865
+ const untouchableContent = safeReadFile('/root/family-data/UNTOUCHABLE_FILES.txt');
866
+ if (untouchableContent) {
867
+ const lines = untouchableContent.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
868
+ for (const filePath of lines) {
869
+ if (fs.existsSync(filePath)) {
870
+ cleanChecks++;
871
+ } else {
872
+ drifts.push({ type: 'missing_file', source: 'UNTOUCHABLE_FILES.txt', target: filePath, field: 'exists', expected: 'true', found: 'false' });
873
+ }
874
+ }
875
+ }
876
+
877
+ // Check LLM_STARTUP.md and BUSINESS_BUILDER.md for file references
878
+ const docsToCheck = [
879
+ { path: '/root/family-data/LLM_STARTUP.md', name: 'LLM_STARTUP.md' },
880
+ { path: '/root/family-data/BUSINESS_BUILDER.md', name: 'BUSINESS_BUILDER.md' }
881
+ ];
882
+
883
+ // Get PM2 running scripts
884
+ let pm2Scripts = {};
885
+ try {
886
+ const pm2Output = execSync('pm2 jlist', { timeout: 10000 }).toString();
887
+ const pm2List = JSON.parse(pm2Output);
888
+ for (const proc of pm2List) {
889
+ pm2Scripts[proc.name] = proc.pm2_env ? proc.pm2_env.pm_exec_path : (proc.script || '');
890
+ }
891
+ } catch (e) {}
892
+
893
+ for (const doc of docsToCheck) {
894
+ const content = safeReadFile(doc.path);
895
+ if (!content) continue;
896
+ // Look for .js file references
897
+ const jsRefs = content.match(/\/root\/[^\s)]+\.js/g) || [];
898
+ for (const ref of jsRefs) {
899
+ if (fs.existsSync(ref)) {
900
+ cleanChecks++;
901
+ // Check if PM2 is running something different
902
+ const basename = ref.split('/').pop();
903
+ for (const [procName, scriptPath] of Object.entries(pm2Scripts)) {
904
+ const procBasename = scriptPath.split('/').pop();
905
+ // If the doc references a versioned file like tamara-v5.js but PM2 runs tamara-v6.js
906
+ const refBase = basename.replace(/-v\d+/, '');
907
+ const procBase = procBasename.replace(/-v\d+/, '');
908
+ if (refBase === procBase && basename !== procBasename && ref !== scriptPath) {
909
+ drifts.push({ type: 'file_version_mismatch', source: doc.name, target: `PM2 process ${procName}`, field: refBase, expected: basename, found: procBasename });
910
+ }
911
+ }
912
+ } else {
913
+ drifts.push({ type: 'missing_file_ref', source: doc.name, target: ref, field: 'exists', expected: 'true', found: 'false' });
914
+ }
915
+ }
916
+ }
917
+
918
+ // Check system-config.json syntax_check_scripts
919
+ const config = safeReadJSON('/root/family-data/system-config.json');
920
+ if (config && config.syntax_check_scripts) {
921
+ for (const script of config.syntax_check_scripts) {
922
+ if (fs.existsSync(script)) {
923
+ cleanChecks++;
924
+ } else {
925
+ drifts.push({ type: 'missing_file', source: 'system-config.json syntax_check_scripts', target: script, field: 'exists', expected: 'true', found: 'false' });
926
+ }
927
+ }
928
+ }
929
+
930
+ return { drifts, cleanChecks };
931
+ }
932
+
933
+ function auditProcesses() {
934
+ const drifts = [];
935
+ let cleanChecks = 0;
936
+
937
+ let pm2Procs = [];
938
+ try {
939
+ const pm2Output = execSync('pm2 jlist', { timeout: 10000 }).toString();
940
+ pm2Procs = JSON.parse(pm2Output);
941
+ } catch (e) {
942
+ drifts.push({ type: 'pm2_error', source: 'pm2 jlist', target: '', field: '', expected: 'valid pm2 output', found: e.message });
943
+ return { drifts, cleanChecks };
944
+ }
945
+
946
+ const config = safeReadJSON('/root/family-data/system-config.json');
947
+ if (!config || !config.processes) {
948
+ // Try to compare against family-roles.json procs
949
+ const roles = safeReadJSON('/root/family-data/family-roles.json');
950
+ if (roles && roles.members) {
951
+ const expectedProcs = [];
952
+ for (const m of roles.members) {
953
+ if (m.procs) expectedProcs.push(...m.procs);
954
+ }
955
+ const runningNames = pm2Procs.map(p => p.name);
956
+ for (const ep of expectedProcs) {
957
+ if (runningNames.includes(ep)) {
958
+ cleanChecks++;
959
+ } else {
960
+ drifts.push({ type: 'missing_process', source: 'family-roles.json', target: 'pm2', field: ep, expected: 'running', found: 'not found in pm2' });
961
+ }
962
+ }
963
+ }
964
+ return { drifts, cleanChecks };
965
+ }
966
+
967
+ // Compare config.processes against pm2
968
+ const runningNames = pm2Procs.map(p => p.name);
969
+ if (Array.isArray(config.processes)) {
970
+ for (const ep of config.processes) {
971
+ const procName = typeof ep === 'string' ? ep : ep.name;
972
+ if (runningNames.includes(procName)) {
973
+ cleanChecks++;
974
+ } else {
975
+ drifts.push({ type: 'missing_process', source: 'system-config.json', target: 'pm2', field: procName, expected: 'running', found: 'not found in pm2' });
976
+ }
977
+ }
978
+ }
979
+
980
+ // Check script paths match
981
+ for (const proc of pm2Procs) {
982
+ const scriptPath = proc.pm2_env ? proc.pm2_env.pm_exec_path : '';
983
+ if (scriptPath && !fs.existsSync(scriptPath)) {
984
+ drifts.push({ type: 'broken_script_path', source: `pm2 process ${proc.name}`, target: scriptPath, field: 'exists', expected: 'true', found: 'false' });
985
+ } else if (scriptPath) {
986
+ cleanChecks++;
987
+ }
988
+ }
989
+
990
+ return { drifts, cleanChecks };
991
+ }
992
+
993
+ function auditWebsite() {
994
+ const drifts = [];
995
+ let cleanChecks = 0;
996
+
997
+ // Source of truth values
998
+ const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
999
+ const pkg = safeReadJSON(pkgFile);
1000
+ const expectedVersion = pkg ? pkg.version : '1.5.0';
1001
+ const actualToolCount = TOOLS.length;
1002
+ const actualResourceCount = RESOURCES.length;
1003
+
1004
+ const roles = safeReadJSON('/root/family-data/family-roles.json');
1005
+ const expectedMemberCount = roles && roles.stats ? roles.stats.member_count : 11;
1006
+ const expectedProcessCount = roles && roles.stats ? roles.stats.process_count : 28;
1007
+
1008
+ // Count protected files (non-comment, non-blank lines starting with /)
1009
+ const untouchableContent = safeReadFile('/root/family-data/UNTOUCHABLE_FILES.txt');
1010
+ let protectedFileCount = 0;
1011
+ if (untouchableContent) {
1012
+ protectedFileCount = untouchableContent.split('\n').filter(l => l.trim() && !l.trim().startsWith('#') && l.trim().startsWith('/')).length;
1013
+ }
1014
+
1015
+ // Get role names from family-roles.json
1016
+ const roleNames = roles && roles.members ? roles.members.map(m => m.name) : [];
1017
+
1018
+ // 1. Check all .html files in /root/family-home/
1019
+ const familyHomeDir = '/root/family-home/';
1020
+ let htmlFiles = [];
1021
+ try {
1022
+ htmlFiles = fs.readdirSync(familyHomeDir).filter(f => f.endsWith('.html')).map(f => familyHomeDir + f);
1023
+ } catch (e) {}
1024
+
1025
+ for (const htmlFile of htmlFiles) {
1026
+ const content = safeReadFile(htmlFile);
1027
+ if (!content) continue;
1028
+ const fname = htmlFile.split('/').pop();
1029
+
1030
+ // Check for old version references (not matching expected)
1031
+ const versionMatches = content.match(/v(\d+\.\d+\.\d+)/g) || [];
1032
+ for (const vm of versionMatches) {
1033
+ const ver = vm.substring(1);
1034
+ if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
1035
+ drifts.push({ type: 'stale_version', source: 'package.json', target: fname, field: 'version', expected: expectedVersion, found: ver });
1036
+ }
1037
+ }
1038
+
1039
+ // Check for stale tool count references
1040
+ const toolCountMatches = content.match(/(\d+)\s*(?:MCP\s+)?tools/gi) || [];
1041
+ for (const tcm of toolCountMatches) {
1042
+ const num = parseInt(tcm);
1043
+ if (num > 0 && num !== actualToolCount && num < 50) {
1044
+ drifts.push({ type: 'stale_tool_count', source: 'TOOLS array', target: fname, field: 'tool_count', expected: String(actualToolCount), found: String(num) });
1045
+ }
1046
+ }
1047
+
1048
+ // Check for stale agent/member count
1049
+ const agentMatches = content.match(/(\d+)\s*(?:AI\s+)?(?:family\s+)?(?:members|agents)/gi) || [];
1050
+ for (const am of agentMatches) {
1051
+ const num = parseInt(am);
1052
+ if (num > 0 && num !== expectedMemberCount && num < 50) {
1053
+ drifts.push({ type: 'stale_agent_count', source: 'family-roles.json', target: fname, field: 'member_count', expected: String(expectedMemberCount), found: String(num) });
1054
+ }
1055
+ }
1056
+
1057
+ // Check for stale protected file count
1058
+ const protMatches = content.match(/(\d+)\s*protected\s*files/gi) || [];
1059
+ for (const pm of protMatches) {
1060
+ const num = parseInt(pm);
1061
+ if (num > 0 && num !== protectedFileCount) {
1062
+ drifts.push({ type: 'stale_protected_count', source: 'UNTOUCHABLE_FILES.txt', target: fname, field: 'protected_files', expected: String(protectedFileCount), found: String(num) });
1063
+ }
1064
+ }
1065
+
1066
+ // Check for stale process count
1067
+ const procMatches = content.match(/(\d+)\s*(?:live\s+)?processes/gi) || [];
1068
+ for (const pcm of procMatches) {
1069
+ const num = parseInt(pcm);
1070
+ if (num > 0 && num !== expectedProcessCount && num < 100) {
1071
+ drifts.push({ type: 'stale_process_count', source: 'family-roles.json', target: fname, field: 'process_count', expected: String(expectedProcessCount), found: String(num) });
1072
+ }
1073
+ }
1074
+
1075
+ // If no drifts found for this file, count as clean
1076
+ if (!drifts.some(d => d.target === fname)) {
1077
+ cleanChecks++;
1078
+ }
1079
+ }
1080
+
1081
+ // 2. Check family-guide.json
1082
+ const guide = safeReadJSON('/root/family-data/family-guide.json');
1083
+ if (guide) {
1084
+ const guideStr = JSON.stringify(guide);
1085
+ // Check version refs
1086
+ const guideVersions = guideStr.match(/v(\d+\.\d+\.\d+)/g) || [];
1087
+ for (const gv of guideVersions) {
1088
+ const ver = gv.substring(1);
1089
+ if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
1090
+ drifts.push({ type: 'stale_version', source: 'package.json', target: 'family-guide.json', field: 'version', expected: expectedVersion, found: ver });
1091
+ }
1092
+ }
1093
+ // Check tool count refs
1094
+ const guideToolMatches = guideStr.match(/(\d+)\s*(?:MCP\s+)?tools/gi) || [];
1095
+ for (const gtm of guideToolMatches) {
1096
+ const num = parseInt(gtm);
1097
+ if (num > 0 && num !== actualToolCount && num < 50) {
1098
+ drifts.push({ type: 'stale_tool_count', source: 'TOOLS array', target: 'family-guide.json', field: 'tool_count', expected: String(actualToolCount), found: String(num) });
1099
+ }
1100
+ }
1101
+ // Check for references to removed tools
1102
+ if (guideStr.includes('classify_task_complexity') || guideStr.includes('parse_user_intent')) {
1103
+ drifts.push({ type: 'stale_tool_reference', source: 'TOOLS array', target: 'family-guide.json', field: 'removed_tools', expected: 'drift_audit', found: 'classify_task_complexity/parse_user_intent' });
1104
+ }
1105
+ } else { cleanChecks++; }
1106
+
1107
+ // 3. Check mcp-stripe-checkout.js for version refs
1108
+ const checkoutContent = safeReadFile('/root/mcp-stripe-checkout.js');
1109
+ if (checkoutContent) {
1110
+ const checkoutVersions = checkoutContent.match(/v(\d+\.\d+\.\d+)/g) || [];
1111
+ for (const cv of checkoutVersions) {
1112
+ const ver = cv.substring(1);
1113
+ if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
1114
+ drifts.push({ type: 'stale_version', source: 'package.json', target: 'mcp-stripe-checkout.js', field: 'version', expected: expectedVersion, found: ver });
1115
+ }
1116
+ }
1117
+ if (!checkoutVersions.length) cleanChecks++;
1118
+ }
1119
+
1120
+ // 4. Check system-config.json for version/tool counts
1121
+ const sysConfig = safeReadJSON('/root/family-data/system-config.json');
1122
+ if (sysConfig) {
1123
+ const scStr = JSON.stringify(sysConfig);
1124
+ const scVersions = scStr.match(/v(\d+\.\d+\.\d+)/g) || [];
1125
+ for (const sv of scVersions) {
1126
+ const ver = sv.substring(1);
1127
+ if (ver !== expectedVersion && /^1\.\d+\.\d+$/.test(ver)) {
1128
+ drifts.push({ type: 'stale_version', source: 'package.json', target: 'system-config.json', field: 'version', expected: expectedVersion, found: ver });
1129
+ }
1130
+ }
1131
+ if (!scVersions.length) cleanChecks++;
1132
+ }
1133
+
1134
+ // 5. Check FREE_TOOLS in mcp-api-middleware.js match actual tool names
1135
+ const middlewareContent = safeReadFile('/root/mcp-api-middleware.js');
1136
+ if (middlewareContent) {
1137
+ const freeToolsMatch = middlewareContent.match(/'nervous-system':\s*\[([^\]]+)\]/);
1138
+ if (freeToolsMatch) {
1139
+ const freeToolNames = freeToolsMatch[1].match(/'([^']+)'/g);
1140
+ if (freeToolNames) {
1141
+ const actualToolNames = TOOLS.map(t => t.name);
1142
+ for (const ft of freeToolNames) {
1143
+ const toolName = ft.replace(/'/g, '');
1144
+ if (actualToolNames.includes(toolName)) {
1145
+ cleanChecks++;
1146
+ } else {
1147
+ drifts.push({ type: 'invalid_free_tool', source: 'TOOLS array', target: 'mcp-api-middleware.js', field: 'FREE_TOOLS', expected: 'valid tool name', found: toolName });
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+
1154
+ // 6. Check sitemap.xml has all public pages
1155
+ const sitemapContent = safeReadFile('/root/family-home/sitemap.xml');
1156
+ if (sitemapContent && htmlFiles.length > 0) {
1157
+ const publicPages = htmlFiles.filter(f => {
1158
+ const name = f.split('/').pop();
1159
+ return !['404.html', 'arthur.html', 'aram-consent.html', 'explorer.html', 'checklist.html'].includes(name);
1160
+ });
1161
+ for (const page of publicPages) {
1162
+ const pageName = page.split('/').pop();
1163
+ if (pageName === 'index.html') {
1164
+ if (sitemapContent.includes('/family/')) cleanChecks++;
1165
+ } else {
1166
+ if (sitemapContent.includes(pageName)) {
1167
+ cleanChecks++;
1168
+ } else {
1169
+ drifts.push({ type: 'missing_from_sitemap', source: 'sitemap.xml', target: pageName, field: 'listed', expected: 'true', found: 'false' });
1170
+ }
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ return { drifts, cleanChecks };
1176
+ }
1177
+
1178
+ function runDriftAudit(scope) {
1179
+ const timestamp = new Date().toISOString();
1180
+ const allDrifts = [];
1181
+ let totalClean = 0;
1182
+ const scopes = scope === 'full' ? ['roles', 'versions', 'files', 'processes', 'website'] : [scope];
1183
+
1184
+ for (const s of scopes) {
1185
+ let result;
1186
+ switch (s) {
1187
+ case 'roles': result = auditRoles(); break;
1188
+ case 'versions': result = auditVersions(); break;
1189
+ case 'files': result = auditFiles(); break;
1190
+ case 'processes': result = auditProcesses(); break;
1191
+ case 'website': result = auditWebsite(); break;
1192
+ default: result = { drifts: [{ type: 'unknown_scope', source: '', target: '', field: s, expected: 'valid scope', found: 'unknown' }], cleanChecks: 0 };
1193
+ }
1194
+ allDrifts.push(...result.drifts);
1195
+ totalClean += result.cleanChecks;
1196
+ }
1197
+
1198
+ return {
1199
+ scope,
1200
+ timestamp,
1201
+ status: allDrifts.length === 0 ? 'clean' : 'drift_detected',
1202
+ drift_count: allDrifts.length,
1203
+ drifts: allDrifts,
1204
+ clean_checks: totalClean
1205
+ };
1206
+ }
1207
+
1208
+ // ============================================================
1209
+ // SECURITY AUDIT ENGINE
1210
+ // ============================================================
1211
+
1212
+ function runSecurityAudit() {
1213
+ const vulnerabilities = [];
1214
+ let checksPassed = 0;
1215
+
1216
+ // 1. Scan HTML files for hardcoded passwords/secrets
1217
+ const htmlDir = '/root/family-home/';
1218
+ const secretPatterns = [
1219
+ /Liarzhek1\$/g,
1220
+ /Levelsofself1\$/g,
1221
+ /\d{10}:AA[A-Za-z0-9_-]{30,}/g,
1222
+ /sk-ant-[a-zA-Z0-9_-]+/g,
1223
+ /npm_[A-Za-z0-9]{20,}/g,
1224
+ /ghp_[A-Za-z0-9]{20,}/g,
1225
+ /BOT_TOKEN\s*[:=]\s*['"][^'"]+['"]/gi,
1226
+ /PAPA_FULL\s*[:=]\s*['"][^'"]+['"]/gi,
1227
+ /PAPA_READ\s*[:=]\s*['"][^'"]+['"]/gi
1228
+ ];
1229
+ try {
1230
+ const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
1231
+ for (const hf of htmlFiles) {
1232
+ const content = safeReadFile(htmlDir + hf);
1233
+ if (!content) continue;
1234
+ let fileClean = true;
1235
+ for (const pat of secretPatterns) {
1236
+ pat.lastIndex = 0;
1237
+ const matches = content.match(pat);
1238
+ if (matches && matches.length > 0) {
1239
+ vulnerabilities.push({ type: 'hardcoded_secret', file: hf, pattern: pat.source, count: matches.length });
1240
+ fileClean = false;
1241
+ }
1242
+ }
1243
+ if (fileClean) checksPassed++;
1244
+ }
1245
+ } catch (e) {
1246
+ vulnerabilities.push({ type: 'scan_error', file: 'html_scan', detail: e.message });
1247
+ }
1248
+
1249
+ // 2. Check auth endpoints use server-side validation
1250
+ const serverContent = safeReadFile('/root/family-home/server.js');
1251
+ if (serverContent) {
1252
+ if (serverContent.includes('getSessionFromReq') || serverContent.includes('getAccessLevel')) {
1253
+ checksPassed++;
1254
+ } else {
1255
+ vulnerabilities.push({ type: 'missing_server_auth', file: 'server.js', detail: 'No server-side auth validation found' });
1256
+ }
1257
+ }
1258
+
1259
+ // 3. Verify GUEST_HIDDEN_FILES covers sensitive files
1260
+ if (serverContent) {
1261
+ const sensitiveFiles = ['api-credentials.json', 'family-roles.json', 'system-config.json', 'llm-providers.json'];
1262
+ for (const sf of sensitiveFiles) {
1263
+ if (serverContent.includes('"' + sf + '"') || serverContent.includes("'" + sf + "'")) {
1264
+ checksPassed++;
1265
+ } else {
1266
+ vulnerabilities.push({ type: 'unhidden_sensitive_file', file: sf, detail: 'Not in GUEST_HIDDEN_FILES' });
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // 4. Check Caddy TLS
1272
+ const caddyContent = safeReadFile('/etc/caddy/Caddyfile');
1273
+ if (caddyContent) {
1274
+ if (caddyContent.includes('tls') || caddyContent.includes('https://') || caddyContent.includes('100levelup.com')) {
1275
+ checksPassed++;
1276
+ } else {
1277
+ vulnerabilities.push({ type: 'missing_tls', file: 'Caddyfile', detail: 'No TLS configuration found' });
1278
+ }
1279
+ }
1280
+
1281
+ // 5. Check bridge rate limiting
1282
+ if (serverContent && serverContent.includes('rate') || fs.existsSync('/root/rate-limit.js') || fs.existsSync('/root/bridge-ratelimit.js')) {
1283
+ checksPassed++;
1284
+ } else {
1285
+ vulnerabilities.push({ type: 'missing_rate_limit', file: 'bridge', detail: 'No rate limiting found for bridge' });
1286
+ }
1287
+
1288
+ // 6. Check bot tokens not in public HTML
1289
+ try {
1290
+ const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
1291
+ let tokenFound = false;
1292
+ for (const hf of htmlFiles) {
1293
+ const content = safeReadFile(htmlDir + hf);
1294
+ if (!content) continue;
1295
+ const tokenMatch = content.match(/\d{10}:AA[A-Za-z0-9_-]{30,}/g);
1296
+ if (tokenMatch) {
1297
+ vulnerabilities.push({ type: 'exposed_bot_token', file: hf, count: tokenMatch.length });
1298
+ tokenFound = true;
1299
+ }
1300
+ }
1301
+ if (!tokenFound) checksPassed++;
1302
+ } catch (e) {}
1303
+
1304
+ // 7. Check api-credentials.json permissions
1305
+ try {
1306
+ const credFile = '/root/family-data/api-credentials.json';
1307
+ if (fs.existsSync(credFile)) {
1308
+ const stats = fs.statSync(credFile);
1309
+ const mode = (stats.mode & 0o777).toString(8);
1310
+ if (mode === '600') {
1311
+ checksPassed++;
1312
+ } else {
1313
+ vulnerabilities.push({ type: 'insecure_permissions', file: 'api-credentials.json', detail: 'Mode is ' + mode + ', should be 600' });
1314
+ }
1315
+ } else {
1316
+ checksPassed++; // No creds file = no risk
1317
+ }
1318
+ } catch (e) {}
1319
+
1320
+ // 8. Check for Telegram tokens, API keys, npm tokens in family-home
1321
+ try {
1322
+ const allFiles = fs.readdirSync(htmlDir);
1323
+ const dangerPatterns = [
1324
+ { name: 'telegram_token', pat: /\d{10}:AA[A-Za-z0-9_-]{30,}/g },
1325
+ { name: 'anthropic_key', pat: /sk-ant-[a-zA-Z0-9_-]{20,}/g },
1326
+ { name: 'npm_token', pat: /npm_[A-Za-z0-9]{20,}/g }
1327
+ ];
1328
+ for (const f of allFiles) {
1329
+ if (f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.json')) {
1330
+ const content = safeReadFile(htmlDir + f);
1331
+ if (!content) continue;
1332
+ for (const dp of dangerPatterns) {
1333
+ dp.pat.lastIndex = 0;
1334
+ const m = content.match(dp.pat);
1335
+ if (m) {
1336
+ vulnerabilities.push({ type: 'exposed_' + dp.name, file: f, count: m.length });
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ checksPassed++;
1342
+ } catch (e) {}
1343
+
1344
+ return {
1345
+ status: vulnerabilities.length === 0 ? 'secure' : 'vulnerabilities_found',
1346
+ vulnerability_count: vulnerabilities.length,
1347
+ checks_passed: checksPassed,
1348
+ vulnerabilities
1349
+ };
1350
+ }
1351
+
1352
+ // ============================================================
1353
+ // AUTO PROPAGATE ENGINE
1354
+ // ============================================================
1355
+
1356
+ function runAutoPropagators() {
1357
+ const results = [];
1358
+ const scripts = [
1359
+ { name: 'role', path: '/root/family-workers/role-propagator.js' },
1360
+ { name: 'version', path: '/root/family-workers/version-propagator.js' },
1361
+ { name: 'content', path: '/root/family-workers/content-propagator.js' }
1362
+ ];
1363
+ for (const script of scripts) {
1364
+ try {
1365
+ const out = execSync('node ' + script.path + ' 2>&1', { timeout: 15000 }).toString();
1366
+ const current = out.indexOf('Already current') !== -1;
1367
+ results.push({ propagator: script.name, status: current ? 'current' : 'updated', output: out.trim().substring(0, 500) });
1368
+ } catch (e) {
1369
+ results.push({ propagator: script.name, status: 'error', error: e.message.substring(0, 200) });
1370
+ }
1371
+ }
1372
+ return {
1373
+ timestamp: new Date().toISOString(),
1374
+ propagators_run: results.length,
1375
+ results
1376
+ };
1377
+ }
1378
+
614
1379
  // ============================================================
615
1380
  // Handle tool calls
616
1381
  // ============================================================
@@ -708,6 +1473,30 @@ function handleToolCall(name, args) {
708
1473
  return dispatchToLLM(args.task, args.max_turns);
709
1474
  }
710
1475
 
1476
+ case 'drift_audit': {
1477
+ const scope = args.scope || 'full';
1478
+ return runDriftAudit(scope);
1479
+ }
1480
+
1481
+ case 'security_audit': {
1482
+ return runSecurityAudit();
1483
+ }
1484
+
1485
+ case 'auto_propagate': {
1486
+ return runAutoPropagators();
1487
+ }
1488
+
1489
+ case 'session_close': {
1490
+ const driftResult = runDriftAudit('full');
1491
+ const propagateResult = runAutoPropagators();
1492
+ return {
1493
+ timestamp: new Date().toISOString(),
1494
+ drift_audit: driftResult,
1495
+ propagation: propagateResult,
1496
+ summary: driftResult.drift_count === 0 ? 'Session clean - no drifts, propagators run' : `${driftResult.drift_count} drifts found - review before closing`
1497
+ };
1498
+ }
1499
+
711
1500
  default:
712
1501
  return { error: 'Unknown tool' };
713
1502
  }
@@ -746,6 +1535,11 @@ ${FRAMEWORK.before_any_change.map(s => `- ${s}`).join('\n')}`;
746
1535
  case 'nervous-system://templates':
747
1536
  return `## SESSION HANDOFF TEMPLATE\n${SESSION_HANDOFF_TEMPLATE.template}\n\n---\n\n## WORKLOG FORMAT\n${WORKLOG_TEMPLATE.format}\n\n---\n\n## PREFLIGHT SCRIPT\n${PREFLIGHT_PATTERN.script_template}\n\n---\n\n## UNTOUCHABLE FILES TEMPLATE\n${PREFLIGHT_PATTERN.untouchable_template}`;
748
1537
 
1538
+ case 'nervous-system://drift-audit': {
1539
+ const result = runDriftAudit('full');
1540
+ return `## Drift Audit Report\nTimestamp: ${result.timestamp}\nStatus: ${result.status}\nDrifts found: ${result.drift_count}\nClean checks: ${result.clean_checks}\n\n${result.drifts.map(d => `- [${d.type}] ${d.source} -> ${d.target}: ${d.field} expected="${d.expected}" found="${d.found}"`).join('\n') || 'No drifts detected.'}`;
1541
+ }
1542
+
749
1543
  default:
750
1544
  return null;
751
1545
  }
@@ -824,7 +1618,7 @@ const server = http.createServer((req, res) => {
824
1618
  // Health check
825
1619
  if (req.method === 'GET' && url.pathname === '/health') {
826
1620
  res.writeHead(200, { 'Content-Type': 'application/json' });
827
- res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.1.0', protocol: MCP_VERSION }));
1621
+ res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.5.0', protocol: MCP_VERSION }));
828
1622
  return;
829
1623
  }
830
1624
 
@@ -941,7 +1735,7 @@ const server = http.createServer((req, res) => {
941
1735
  res.writeHead(200, { 'Content-Type': 'application/json' });
942
1736
  res.end(JSON.stringify({
943
1737
  name: 'The Nervous System MCP Server',
944
- version: '1.1.0',
1738
+ version: '1.5.0',
945
1739
  protocol: MCP_VERSION,
946
1740
  description: 'LLM behavioral enforcement framework. 7 core rules, preflight checks, session handoffs, worklogs, violation logging, kill switch, hash-chained audit, and forced reflection cycles. Built by Arthur Palyan.',
947
1741
  endpoints: {
@@ -963,8 +1757,8 @@ const server = http.createServer((req, res) => {
963
1757
  migrateExistingViolations();
964
1758
 
965
1759
  server.listen(PORT, '127.0.0.1', () => {
966
- console.error(`[MCP Server] Nervous System v1.1.0 running on port ${PORT}`);
1760
+ console.error(`[MCP Server] Nervous System v1.5.0 running on port ${PORT}`);
967
1761
  console.error(`[MCP Server] SSE: /sse | HTTP: /mcp | Health: /health | Kill: POST /kill | Audit: GET /audit/verify | Dispatches: GET /dispatches`);
968
1762
  console.error(`[MCP Server] Protocol: ${MCP_VERSION}`);
969
- console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch)`);
1763
+ console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch, drift audit)`);
970
1764
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-nervous-system",
3
- "version": "1.3.0",
4
- "description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 14 tools including task complexity classification and user intent parsing. MCP server for Claude Desktop, Claude Code, and any MCP-compatible client.",
3
+ "version": "1.5.0",
4
+ "description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 15 tools including kill switch, audit chain, dispatch, drift audit, security audit, and session close. MCP server for Claude Desktop, Claude Code, and any MCP-compatible client.",
5
5
  "main": "server.js",
6
6
  "bin": {
7
7
  "mcp-nervous-system": "./stdio.js"
package/stdio.js CHANGED
@@ -51,7 +51,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
51
51
  return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", description: "LLM Behavioral Enforcement Framework", rules: RULES, total_tools: 11, production_stats: { violations_caught: 56, rules_bypassed: 0, edits_blocked: 32, processes_monitored: 22 } }, null, 2) }] };
52
52
 
53
53
  case "get_nervous_system_info":
54
- return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", author: "Arthur Palyan", company: "Palyan AI / Levels of Self LLC", website: "https://www.levelsofself.com", demo: "https://api.100levelup.com/family/arthur.html?guest=1", github: "https://github.com/levelsofself/mcp-nervous-system", npm: "https://www.npmjs.com/package/mcp-nervous-system", tools: 11, rules: 7, production_stats: { violations: 56, bypasses: 0, blocked_edits: 32, uptime_days: 25, monthly_cost: "$12" } }, null, 2) }] };
54
+ return { content: [{ type: "text", text: JSON.stringify({ name: "The Nervous System", version: "1.1.1", author: "Arthur Palyan", company: "Palyan Family AI System / Levels of Self LLC", website: "https://www.levelsofself.com", demo: "https://api.100levelup.com/family/arthur.html?guest=1", github: "https://github.com/levelsofself/mcp-nervous-system", npm: "https://www.npmjs.com/package/mcp-nervous-system", tools: 11, rules: 7, production_stats: { violations: 56, bypasses: 0, blocked_edits: 32, uptime_days: 25, monthly_cost: "$12" } }, null, 2) }] };
55
55
 
56
56
  case "check_preflight":
57
57
  const fp = args?.file_path || "unknown";