@nerviq/cli 1.20.1 → 1.21.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.
- package/LICENSE +23 -23
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +4 -1
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +312 -67
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/workspace.js +375 -375
package/src/permission-rules.js
CHANGED
|
@@ -1,218 +1,218 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const PATH_ACTIONS = new Set(['read', 'write', 'edit', 'multiedit']);
|
|
5
|
-
const SECRET_PATH_RE = /(^|\/)(\.env(?:[^/]*)?|secrets?)(\/|$)/i;
|
|
6
|
-
|
|
7
|
-
function normalizeSlash(value) {
|
|
8
|
-
return String(value || '').replace(/\\/g, '/');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function stripWrappingQuotes(value) {
|
|
12
|
-
const trimmed = String(value || '').trim();
|
|
13
|
-
if (!trimmed) return '';
|
|
14
|
-
const first = trimmed[0];
|
|
15
|
-
const last = trimmed[trimmed.length - 1];
|
|
16
|
-
if ((first === '"' || first === "'") && first === last) {
|
|
17
|
-
return trimmed.slice(1, -1);
|
|
18
|
-
}
|
|
19
|
-
return trimmed;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getProjectRoot(rootDir) {
|
|
23
|
-
try {
|
|
24
|
-
return fs.realpathSync.native(rootDir);
|
|
25
|
-
} catch {
|
|
26
|
-
return path.resolve(rootDir);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function splitPatternSegments(rawPattern, isAbsolute) {
|
|
31
|
-
const normalized = normalizeSlash(rawPattern);
|
|
32
|
-
|
|
33
|
-
if (/^[A-Za-z]:\//.test(normalized)) {
|
|
34
|
-
return normalized.slice(3).split('/').filter(Boolean);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (isAbsolute && normalized.startsWith('/')) {
|
|
38
|
-
return normalized.slice(1).split('/').filter(Boolean);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return normalized.split('/').filter((segment) => segment && segment !== '.');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function hasGlob(segment) {
|
|
45
|
-
return /[*?[\]{}]/.test(segment);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function buildAbsolutePattern(rootDir, rawPattern) {
|
|
49
|
-
const normalized = stripWrappingQuotes(normalizeSlash(rawPattern).replace(/^file:\/\//i, ''));
|
|
50
|
-
if (!normalized) {
|
|
51
|
-
return {
|
|
52
|
-
absolutePattern: null,
|
|
53
|
-
normalizedInput: '',
|
|
54
|
-
isAbsolute: false,
|
|
55
|
-
traversalSegments: false,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const isAbsolute = /^[A-Za-z]:\//.test(normalized) || normalized.startsWith('/');
|
|
60
|
-
const traversalSegments = normalized.split('/').some((segment) => segment === '..');
|
|
61
|
-
const segments = splitPatternSegments(normalized, isAbsolute);
|
|
62
|
-
let current = isAbsolute ? path.parse(path.resolve(normalized)).root : getProjectRoot(rootDir);
|
|
63
|
-
|
|
64
|
-
for (const segment of segments) {
|
|
65
|
-
const candidate = path.join(current, segment);
|
|
66
|
-
if (hasGlob(segment)) {
|
|
67
|
-
current = candidate;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
current = fs.realpathSync.native(candidate);
|
|
73
|
-
} catch {
|
|
74
|
-
current = candidate;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
absolutePattern: current,
|
|
80
|
-
normalizedInput: normalized,
|
|
81
|
-
isAbsolute,
|
|
82
|
-
traversalSegments,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function normalizePathPayload(rawPayload, rootDir) {
|
|
87
|
-
const {
|
|
88
|
-
absolutePattern,
|
|
89
|
-
normalizedInput,
|
|
90
|
-
isAbsolute,
|
|
91
|
-
traversalSegments,
|
|
92
|
-
} = buildAbsolutePattern(rootDir, rawPayload);
|
|
93
|
-
|
|
94
|
-
if (!absolutePattern) {
|
|
95
|
-
return {
|
|
96
|
-
normalizedPath: '',
|
|
97
|
-
repoRelativePath: '',
|
|
98
|
-
outsideRepo: false,
|
|
99
|
-
invalid: true,
|
|
100
|
-
isAbsolute,
|
|
101
|
-
traversalSegments,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const projectRoot = getProjectRoot(rootDir);
|
|
106
|
-
const relativePath = normalizeSlash(path.relative(projectRoot, absolutePattern));
|
|
107
|
-
const outsideRepo = relativePath === '..' || relativePath.startsWith('../') || /^[A-Za-z]:\//.test(relativePath);
|
|
108
|
-
const repoRelativePath = outsideRepo ? null : relativePath || '.';
|
|
109
|
-
const normalizedPath = outsideRepo
|
|
110
|
-
? normalizeSlash(absolutePattern)
|
|
111
|
-
: `./${repoRelativePath}`;
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
normalizedPath,
|
|
115
|
-
repoRelativePath,
|
|
116
|
-
outsideRepo,
|
|
117
|
-
invalid: traversalSegments && outsideRepo && !isAbsolute,
|
|
118
|
-
isAbsolute,
|
|
119
|
-
traversalSegments,
|
|
120
|
-
normalizedInput,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function normalizeCommandPayload(rawPayload) {
|
|
125
|
-
return stripWrappingQuotes(rawPayload).replace(/\s+/g, ' ').trim();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function normalizePermissionRule(rule, rootDir) {
|
|
129
|
-
if (typeof rule !== 'string' || !rule.trim()) return null;
|
|
130
|
-
const trimmed = rule.trim();
|
|
131
|
-
const match = trimmed.match(/^([A-Za-z]+)\((.*)\)$/);
|
|
132
|
-
if (!match) {
|
|
133
|
-
return {
|
|
134
|
-
raw: trimmed,
|
|
135
|
-
action: null,
|
|
136
|
-
payload: trimmed,
|
|
137
|
-
normalized: trimmed,
|
|
138
|
-
dedupeKey: trimmed.toLowerCase(),
|
|
139
|
-
kind: 'raw',
|
|
140
|
-
invalid: false,
|
|
141
|
-
outsideRepo: false,
|
|
142
|
-
protectsSecrets: false,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const action = match[1];
|
|
147
|
-
const payload = match[2].trim();
|
|
148
|
-
const actionKey = action.toLowerCase();
|
|
149
|
-
|
|
150
|
-
if (PATH_ACTIONS.has(actionKey)) {
|
|
151
|
-
const details = normalizePathPayload(payload, rootDir);
|
|
152
|
-
const dedupeKey = `${actionKey}:${details.normalizedPath.toLowerCase()}`;
|
|
153
|
-
return {
|
|
154
|
-
raw: trimmed,
|
|
155
|
-
action,
|
|
156
|
-
payload,
|
|
157
|
-
normalized: `${action}(${details.normalizedPath})`,
|
|
158
|
-
normalizedPath: details.normalizedPath,
|
|
159
|
-
repoRelativePath: details.repoRelativePath,
|
|
160
|
-
dedupeKey,
|
|
161
|
-
kind: 'path',
|
|
162
|
-
invalid: details.invalid,
|
|
163
|
-
outsideRepo: details.outsideRepo,
|
|
164
|
-
traversalSegments: details.traversalSegments,
|
|
165
|
-
isAbsolute: details.isAbsolute,
|
|
166
|
-
protectsSecrets: !details.outsideRepo && SECRET_PATH_RE.test(details.repoRelativePath || ''),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const normalizedPayload = normalizeCommandPayload(payload);
|
|
171
|
-
return {
|
|
172
|
-
raw: trimmed,
|
|
173
|
-
action,
|
|
174
|
-
payload,
|
|
175
|
-
normalized: `${action}(${normalizedPayload})`,
|
|
176
|
-
dedupeKey: `${actionKey}:${normalizedPayload.toLowerCase()}`,
|
|
177
|
-
kind: 'command',
|
|
178
|
-
invalid: false,
|
|
179
|
-
outsideRepo: false,
|
|
180
|
-
protectsSecrets: false,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function normalizePermissionRules(rules, rootDir) {
|
|
185
|
-
const seen = new Set();
|
|
186
|
-
const normalized = [];
|
|
187
|
-
|
|
188
|
-
for (const rule of Array.isArray(rules) ? rules : []) {
|
|
189
|
-
const entry = normalizePermissionRule(rule, rootDir);
|
|
190
|
-
if (!entry || entry.invalid) continue;
|
|
191
|
-
if (seen.has(entry.dedupeKey)) continue;
|
|
192
|
-
seen.add(entry.dedupeKey);
|
|
193
|
-
normalized.push(entry);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return normalized;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function collectClaudeDenyRules(ctx) {
|
|
200
|
-
const shared = ctx.jsonFile('.claude/settings.json');
|
|
201
|
-
const local = ctx.jsonFile('.claude/settings.local.json');
|
|
202
|
-
const denyRules = []
|
|
203
|
-
.concat(shared?.permissions?.deny || [])
|
|
204
|
-
.concat(local?.permissions?.deny || []);
|
|
205
|
-
|
|
206
|
-
return normalizePermissionRules(denyRules, ctx.dir);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function hasSecretDenyRule(rules) {
|
|
210
|
-
return (Array.isArray(rules) ? rules : []).some((rule) => rule && rule.protectsSecrets);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
module.exports = {
|
|
214
|
-
collectClaudeDenyRules,
|
|
215
|
-
hasSecretDenyRule,
|
|
216
|
-
normalizePermissionRule,
|
|
217
|
-
normalizePermissionRules,
|
|
218
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const PATH_ACTIONS = new Set(['read', 'write', 'edit', 'multiedit']);
|
|
5
|
+
const SECRET_PATH_RE = /(^|\/)(\.env(?:[^/]*)?|secrets?)(\/|$)/i;
|
|
6
|
+
|
|
7
|
+
function normalizeSlash(value) {
|
|
8
|
+
return String(value || '').replace(/\\/g, '/');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function stripWrappingQuotes(value) {
|
|
12
|
+
const trimmed = String(value || '').trim();
|
|
13
|
+
if (!trimmed) return '';
|
|
14
|
+
const first = trimmed[0];
|
|
15
|
+
const last = trimmed[trimmed.length - 1];
|
|
16
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
17
|
+
return trimmed.slice(1, -1);
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getProjectRoot(rootDir) {
|
|
23
|
+
try {
|
|
24
|
+
return fs.realpathSync.native(rootDir);
|
|
25
|
+
} catch {
|
|
26
|
+
return path.resolve(rootDir);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function splitPatternSegments(rawPattern, isAbsolute) {
|
|
31
|
+
const normalized = normalizeSlash(rawPattern);
|
|
32
|
+
|
|
33
|
+
if (/^[A-Za-z]:\//.test(normalized)) {
|
|
34
|
+
return normalized.slice(3).split('/').filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isAbsolute && normalized.startsWith('/')) {
|
|
38
|
+
return normalized.slice(1).split('/').filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return normalized.split('/').filter((segment) => segment && segment !== '.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasGlob(segment) {
|
|
45
|
+
return /[*?[\]{}]/.test(segment);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildAbsolutePattern(rootDir, rawPattern) {
|
|
49
|
+
const normalized = stripWrappingQuotes(normalizeSlash(rawPattern).replace(/^file:\/\//i, ''));
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
return {
|
|
52
|
+
absolutePattern: null,
|
|
53
|
+
normalizedInput: '',
|
|
54
|
+
isAbsolute: false,
|
|
55
|
+
traversalSegments: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isAbsolute = /^[A-Za-z]:\//.test(normalized) || normalized.startsWith('/');
|
|
60
|
+
const traversalSegments = normalized.split('/').some((segment) => segment === '..');
|
|
61
|
+
const segments = splitPatternSegments(normalized, isAbsolute);
|
|
62
|
+
let current = isAbsolute ? path.parse(path.resolve(normalized)).root : getProjectRoot(rootDir);
|
|
63
|
+
|
|
64
|
+
for (const segment of segments) {
|
|
65
|
+
const candidate = path.join(current, segment);
|
|
66
|
+
if (hasGlob(segment)) {
|
|
67
|
+
current = candidate;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
current = fs.realpathSync.native(candidate);
|
|
73
|
+
} catch {
|
|
74
|
+
current = candidate;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
absolutePattern: current,
|
|
80
|
+
normalizedInput: normalized,
|
|
81
|
+
isAbsolute,
|
|
82
|
+
traversalSegments,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizePathPayload(rawPayload, rootDir) {
|
|
87
|
+
const {
|
|
88
|
+
absolutePattern,
|
|
89
|
+
normalizedInput,
|
|
90
|
+
isAbsolute,
|
|
91
|
+
traversalSegments,
|
|
92
|
+
} = buildAbsolutePattern(rootDir, rawPayload);
|
|
93
|
+
|
|
94
|
+
if (!absolutePattern) {
|
|
95
|
+
return {
|
|
96
|
+
normalizedPath: '',
|
|
97
|
+
repoRelativePath: '',
|
|
98
|
+
outsideRepo: false,
|
|
99
|
+
invalid: true,
|
|
100
|
+
isAbsolute,
|
|
101
|
+
traversalSegments,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const projectRoot = getProjectRoot(rootDir);
|
|
106
|
+
const relativePath = normalizeSlash(path.relative(projectRoot, absolutePattern));
|
|
107
|
+
const outsideRepo = relativePath === '..' || relativePath.startsWith('../') || /^[A-Za-z]:\//.test(relativePath);
|
|
108
|
+
const repoRelativePath = outsideRepo ? null : relativePath || '.';
|
|
109
|
+
const normalizedPath = outsideRepo
|
|
110
|
+
? normalizeSlash(absolutePattern)
|
|
111
|
+
: `./${repoRelativePath}`;
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
normalizedPath,
|
|
115
|
+
repoRelativePath,
|
|
116
|
+
outsideRepo,
|
|
117
|
+
invalid: traversalSegments && outsideRepo && !isAbsolute,
|
|
118
|
+
isAbsolute,
|
|
119
|
+
traversalSegments,
|
|
120
|
+
normalizedInput,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeCommandPayload(rawPayload) {
|
|
125
|
+
return stripWrappingQuotes(rawPayload).replace(/\s+/g, ' ').trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizePermissionRule(rule, rootDir) {
|
|
129
|
+
if (typeof rule !== 'string' || !rule.trim()) return null;
|
|
130
|
+
const trimmed = rule.trim();
|
|
131
|
+
const match = trimmed.match(/^([A-Za-z]+)\((.*)\)$/);
|
|
132
|
+
if (!match) {
|
|
133
|
+
return {
|
|
134
|
+
raw: trimmed,
|
|
135
|
+
action: null,
|
|
136
|
+
payload: trimmed,
|
|
137
|
+
normalized: trimmed,
|
|
138
|
+
dedupeKey: trimmed.toLowerCase(),
|
|
139
|
+
kind: 'raw',
|
|
140
|
+
invalid: false,
|
|
141
|
+
outsideRepo: false,
|
|
142
|
+
protectsSecrets: false,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const action = match[1];
|
|
147
|
+
const payload = match[2].trim();
|
|
148
|
+
const actionKey = action.toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (PATH_ACTIONS.has(actionKey)) {
|
|
151
|
+
const details = normalizePathPayload(payload, rootDir);
|
|
152
|
+
const dedupeKey = `${actionKey}:${details.normalizedPath.toLowerCase()}`;
|
|
153
|
+
return {
|
|
154
|
+
raw: trimmed,
|
|
155
|
+
action,
|
|
156
|
+
payload,
|
|
157
|
+
normalized: `${action}(${details.normalizedPath})`,
|
|
158
|
+
normalizedPath: details.normalizedPath,
|
|
159
|
+
repoRelativePath: details.repoRelativePath,
|
|
160
|
+
dedupeKey,
|
|
161
|
+
kind: 'path',
|
|
162
|
+
invalid: details.invalid,
|
|
163
|
+
outsideRepo: details.outsideRepo,
|
|
164
|
+
traversalSegments: details.traversalSegments,
|
|
165
|
+
isAbsolute: details.isAbsolute,
|
|
166
|
+
protectsSecrets: !details.outsideRepo && SECRET_PATH_RE.test(details.repoRelativePath || ''),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const normalizedPayload = normalizeCommandPayload(payload);
|
|
171
|
+
return {
|
|
172
|
+
raw: trimmed,
|
|
173
|
+
action,
|
|
174
|
+
payload,
|
|
175
|
+
normalized: `${action}(${normalizedPayload})`,
|
|
176
|
+
dedupeKey: `${actionKey}:${normalizedPayload.toLowerCase()}`,
|
|
177
|
+
kind: 'command',
|
|
178
|
+
invalid: false,
|
|
179
|
+
outsideRepo: false,
|
|
180
|
+
protectsSecrets: false,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizePermissionRules(rules, rootDir) {
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
const normalized = [];
|
|
187
|
+
|
|
188
|
+
for (const rule of Array.isArray(rules) ? rules : []) {
|
|
189
|
+
const entry = normalizePermissionRule(rule, rootDir);
|
|
190
|
+
if (!entry || entry.invalid) continue;
|
|
191
|
+
if (seen.has(entry.dedupeKey)) continue;
|
|
192
|
+
seen.add(entry.dedupeKey);
|
|
193
|
+
normalized.push(entry);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return normalized;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function collectClaudeDenyRules(ctx) {
|
|
200
|
+
const shared = ctx.jsonFile('.claude/settings.json');
|
|
201
|
+
const local = ctx.jsonFile('.claude/settings.local.json');
|
|
202
|
+
const denyRules = []
|
|
203
|
+
.concat(shared?.permissions?.deny || [])
|
|
204
|
+
.concat(local?.permissions?.deny || []);
|
|
205
|
+
|
|
206
|
+
return normalizePermissionRules(denyRules, ctx.dir);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function hasSecretDenyRule(rules) {
|
|
210
|
+
return (Array.isArray(rules) ? rules : []).some((rule) => rule && rule.protectsSecrets);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
collectClaudeDenyRules,
|
|
215
|
+
hasSecretDenyRule,
|
|
216
|
+
normalizePermissionRule,
|
|
217
|
+
normalizePermissionRules,
|
|
218
|
+
};
|