@nerviq/cli 0.0.1 → 0.9.0-beta.2

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 (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,183 @@
1
+ const COMMENT_MARKER = '#';
2
+
3
+ function stripInlineComment(line) {
4
+ let inSingle = false;
5
+ let inDouble = false;
6
+
7
+ for (let i = 0; i < line.length; i++) {
8
+ const char = line[i];
9
+ const prev = i > 0 ? line[i - 1] : '';
10
+
11
+ if (char === "'" && !inDouble && prev !== '\\') {
12
+ inSingle = !inSingle;
13
+ continue;
14
+ }
15
+
16
+ if (char === '"' && !inSingle && prev !== '\\') {
17
+ inDouble = !inDouble;
18
+ continue;
19
+ }
20
+
21
+ if (char === COMMENT_MARKER && !inSingle && !inDouble) {
22
+ return line.slice(0, i).trimEnd();
23
+ }
24
+ }
25
+
26
+ return line;
27
+ }
28
+
29
+ function assignPath(target, pathParts, value) {
30
+ let cursor = target;
31
+ for (let i = 0; i < pathParts.length - 1; i++) {
32
+ const key = pathParts[i];
33
+ if (!cursor[key] || typeof cursor[key] !== 'object' || Array.isArray(cursor[key])) {
34
+ cursor[key] = {};
35
+ }
36
+ cursor = cursor[key];
37
+ }
38
+ cursor[pathParts[pathParts.length - 1]] = value;
39
+ }
40
+
41
+ function parseString(value) {
42
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
43
+ return value.slice(1, -1);
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function splitInlineItems(value, separator = ',') {
49
+ const items = [];
50
+ let current = '';
51
+ let inSingle = false;
52
+ let inDouble = false;
53
+ let depth = 0;
54
+
55
+ for (let i = 0; i < value.length; i++) {
56
+ const char = value[i];
57
+ const prev = i > 0 ? value[i - 1] : '';
58
+
59
+ if (char === "'" && !inDouble && prev !== '\\') {
60
+ inSingle = !inSingle;
61
+ } else if (char === '"' && !inSingle && prev !== '\\') {
62
+ inDouble = !inDouble;
63
+ } else if (!inSingle && !inDouble) {
64
+ if (char === '[' || char === '{') depth++;
65
+ if (char === ']' || char === '}') depth--;
66
+ if (char === separator && depth === 0) {
67
+ items.push(current.trim());
68
+ current = '';
69
+ continue;
70
+ }
71
+ }
72
+
73
+ current += char;
74
+ }
75
+
76
+ if (current.trim()) {
77
+ items.push(current.trim());
78
+ }
79
+
80
+ return items;
81
+ }
82
+
83
+ function parseValue(rawValue) {
84
+ const value = rawValue.trim();
85
+
86
+ const asString = parseString(value);
87
+ if (asString !== null) return asString;
88
+
89
+ if (value === 'true') return true;
90
+ if (value === 'false') return false;
91
+ if (/^-?\d+$/.test(value)) return Number.parseInt(value, 10);
92
+ if (/^-?\d+\.\d+$/.test(value)) return Number.parseFloat(value);
93
+
94
+ if (value.startsWith('[') && value.endsWith(']')) {
95
+ const inner = value.slice(1, -1).trim();
96
+ if (!inner) return [];
97
+ return splitInlineItems(inner).map(parseValue);
98
+ }
99
+
100
+ if (value.startsWith('{') && value.endsWith('}')) {
101
+ const inner = value.slice(1, -1).trim();
102
+ const result = {};
103
+ if (!inner) return result;
104
+
105
+ for (const entry of splitInlineItems(inner)) {
106
+ const idx = entry.indexOf('=');
107
+ if (idx === -1) {
108
+ throw new Error(`Invalid inline table entry: ${entry}`);
109
+ }
110
+ const key = entry.slice(0, idx).trim();
111
+ const entryValue = entry.slice(idx + 1).trim();
112
+ assignPath(result, key.split('.').map(part => part.trim()).filter(Boolean), parseValue(entryValue));
113
+ }
114
+ return result;
115
+ }
116
+
117
+ return value;
118
+ }
119
+
120
+ function parseToml(content) {
121
+ const root = {};
122
+ let currentSection = [];
123
+
124
+ const lines = content.split(/\r?\n/);
125
+ for (let i = 0; i < lines.length; i++) {
126
+ const rawLine = lines[i];
127
+ const line = stripInlineComment(rawLine).trim();
128
+ if (!line) continue;
129
+
130
+ if (line.startsWith('[') && line.endsWith(']')) {
131
+ const sectionName = line.slice(1, -1).trim();
132
+ if (!sectionName) {
133
+ throw new Error(`Line ${i + 1}: empty section header`);
134
+ }
135
+ currentSection = sectionName.split('.').map(part => part.trim()).filter(Boolean);
136
+ assignPath(root, currentSection, {});
137
+ continue;
138
+ }
139
+
140
+ const eqIndex = line.indexOf('=');
141
+ if (eqIndex === -1) {
142
+ throw new Error(`Line ${i + 1}: expected key = value`);
143
+ }
144
+
145
+ const key = line.slice(0, eqIndex).trim();
146
+ const rawValue = line.slice(eqIndex + 1).trim();
147
+ if (!key || !rawValue) {
148
+ throw new Error(`Line ${i + 1}: invalid key/value pair`);
149
+ }
150
+
151
+ const keyPath = key.split('.').map(part => part.trim()).filter(Boolean);
152
+ assignPath(root, [...currentSection, ...keyPath], parseValue(rawValue));
153
+ }
154
+
155
+ return root;
156
+ }
157
+
158
+ function tryParseToml(content) {
159
+ try {
160
+ return { ok: true, data: parseToml(content), error: null };
161
+ } catch (error) {
162
+ return { ok: false, data: null, error: error.message };
163
+ }
164
+ }
165
+
166
+ function getValueByPath(obj, dottedPath) {
167
+ if (!obj) return undefined;
168
+ const parts = dottedPath.split('.').filter(Boolean);
169
+ let cursor = obj;
170
+ for (const part of parts) {
171
+ if (cursor == null || typeof cursor !== 'object' || !(part in cursor)) {
172
+ return undefined;
173
+ }
174
+ cursor = cursor[part];
175
+ }
176
+ return cursor;
177
+ }
178
+
179
+ module.exports = {
180
+ parseToml,
181
+ tryParseToml,
182
+ getValueByPath,
183
+ };
@@ -0,0 +1,221 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { spawnSync } = require('child_process');
5
+ const { ProjectContext } = require('../context');
6
+ const { tryParseToml, getValueByPath } = require('./config-parser');
7
+
8
+ let codexVersionCache = null;
9
+
10
+ function detectCodexVersion() {
11
+ if (codexVersionCache !== null) {
12
+ return codexVersionCache;
13
+ }
14
+
15
+ try {
16
+ const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
17
+ const output = `${result.stdout || ''} ${result.stderr || ''}`.trim();
18
+ const match = output.match(/codex-cli\s+([^\s]+)/i);
19
+ codexVersionCache = match ? match[1] : (output || null);
20
+ return codexVersionCache;
21
+ } catch {
22
+ codexVersionCache = null;
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function listDirs(fullPath) {
28
+ try {
29
+ return fs.readdirSync(fullPath, { withFileTypes: true }).filter(entry => entry.isDirectory());
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ class CodexProjectContext extends ProjectContext {
36
+ configContent() {
37
+ return this.fileContent('.codex/config.toml');
38
+ }
39
+
40
+ globalConfigContent() {
41
+ const homeDir = os.homedir();
42
+ const globalPath = path.join(homeDir, '.codex', 'config.toml');
43
+ try {
44
+ return fs.readFileSync(globalPath, 'utf8');
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ agentsMdContent() {
51
+ const direct = this.fileContent('AGENTS.md');
52
+ if (direct) return direct;
53
+
54
+ const fallbackNames = this.projectDocFallbackFilenames();
55
+ for (const fileName of fallbackNames) {
56
+ const content = this.fileContent(fileName);
57
+ if (content) return content;
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ agentsMdPath() {
64
+ if (this.fileContent('AGENTS.md')) return 'AGENTS.md';
65
+ const fallbackNames = this.projectDocFallbackFilenames();
66
+ for (const fileName of fallbackNames) {
67
+ if (this.fileContent(fileName)) return fileName;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ agentsOverrideMdContent() {
73
+ return this.fileContent('AGENTS.override.md');
74
+ }
75
+
76
+ hasAgentsOverride() {
77
+ return Boolean(this.agentsOverrideMdContent());
78
+ }
79
+
80
+ configToml() {
81
+ const content = this.fileContent('.codex/config.toml');
82
+ if (!content) {
83
+ return { ok: false, data: null, error: 'missing project config', source: '.codex/config.toml' };
84
+ }
85
+ const parsed = tryParseToml(content);
86
+ return { ...parsed, source: '.codex/config.toml' };
87
+ }
88
+
89
+ globalConfigToml() {
90
+ const homeDir = os.homedir();
91
+ const globalPath = path.join(homeDir, '.codex', 'config.toml');
92
+ try {
93
+ const content = fs.readFileSync(globalPath, 'utf8');
94
+ const parsed = tryParseToml(content);
95
+ return { ...parsed, source: globalPath };
96
+ } catch {
97
+ return { ok: false, data: null, error: 'missing global config', source: globalPath };
98
+ }
99
+ }
100
+
101
+ configValue(dottedPath) {
102
+ const project = this.configToml();
103
+ if (project.ok) {
104
+ const projectValue = getValueByPath(project.data, dottedPath);
105
+ if (projectValue !== undefined) return projectValue;
106
+ }
107
+
108
+ const globalConfig = this.globalConfigToml();
109
+ if (globalConfig.ok) {
110
+ return getValueByPath(globalConfig.data, dottedPath);
111
+ }
112
+
113
+ return undefined;
114
+ }
115
+
116
+ projectDocFallbackFilenames() {
117
+ const configured = this.configValue('project_doc_fallback_filenames');
118
+ if (Array.isArray(configured) && configured.length > 0) {
119
+ return configured.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim());
120
+ }
121
+ return [];
122
+ }
123
+
124
+ hooksJson() {
125
+ return this.jsonFile('.codex/hooks.json');
126
+ }
127
+
128
+ hooksJsonContent() {
129
+ return this.fileContent('.codex/hooks.json');
130
+ }
131
+
132
+ hookFiles() {
133
+ const hooksDir = path.join(this.dir, '.codex', 'hooks');
134
+ return listDirs(hooksDir).map(entry => entry.name);
135
+ }
136
+
137
+ skillDirs() {
138
+ const skillsDir = path.join(this.dir, '.agents', 'skills');
139
+ return listDirs(skillsDir).map(entry => entry.name);
140
+ }
141
+
142
+ skillMetadata(name) {
143
+ return this.fileContent(path.join('.agents', 'skills', name, 'SKILL.md'));
144
+ }
145
+
146
+ customAgentFiles() {
147
+ return this.dirFiles('.codex/agents').filter(file => file.endsWith('.toml'));
148
+ }
149
+
150
+ customAgentConfig(fileName) {
151
+ const content = this.fileContent(path.join('.codex', 'agents', fileName));
152
+ if (!content) return { ok: false, data: null, error: 'missing agent config' };
153
+ return tryParseToml(content);
154
+ }
155
+
156
+ mcpServers() {
157
+ return this.configValue('mcp_servers') || {};
158
+ }
159
+
160
+ workflowFiles() {
161
+ return this.dirFiles('.github/workflows')
162
+ .filter(file => /\.ya?ml$/i.test(file))
163
+ .map(file => path.join('.github', 'workflows', file).replace(/\\/g, '/'));
164
+ }
165
+
166
+ ruleFiles() {
167
+ const candidateDirs = ['codex/rules', '.codex/rules'];
168
+ const files = [];
169
+
170
+ for (const dirPath of candidateDirs) {
171
+ for (const file of this.dirFiles(dirPath)) {
172
+ if (file.startsWith('.')) continue;
173
+ files.push(path.join(dirPath, file).replace(/\\/g, '/'));
174
+ }
175
+ }
176
+
177
+ return files;
178
+ }
179
+
180
+ isProjectTrusted() {
181
+ const content = this.globalConfigContent();
182
+ if (!content) return false;
183
+
184
+ const resolved = path.resolve(this.dir);
185
+ const variants = new Set([
186
+ resolved,
187
+ resolved.replace(/\\/g, '/'),
188
+ resolved.replace(/\//g, '\\'),
189
+ resolved.replace(/\\/g, '\\\\'),
190
+ ]);
191
+
192
+ for (const variant of variants) {
193
+ const escaped = variant.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
194
+ const patterns = [
195
+ new RegExp(`["']${escaped}["'][\\s\\S]{0,220}?trust_level\\s*=\\s*["']trusted["']`, 'i'),
196
+ new RegExp(`trust_level\\s*=\\s*["']trusted["'][\\s\\S]{0,220}?["']${escaped}["']`, 'i'),
197
+ new RegExp(`projects[\\s\\S]{0,1200}?["']${escaped}["'][\\s\\S]{0,220}?trust_level\\s*=\\s*["']trusted["']`, 'i'),
198
+ ];
199
+ if (patterns.some((pattern) => pattern.test(content))) {
200
+ return true;
201
+ }
202
+ }
203
+
204
+ return false;
205
+ }
206
+
207
+ static isCodexRepo(dir) {
208
+ try {
209
+ return fs.existsSync(path.join(dir, 'AGENTS.md')) ||
210
+ fs.existsSync(path.join(dir, '.codex')) ||
211
+ fs.existsSync(path.join(dir, '.agents'));
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+ }
217
+
218
+ module.exports = {
219
+ CodexProjectContext,
220
+ detectCodexVersion,
221
+ };