@nexrall/code-core 1.4.75 → 1.4.77

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 (121) hide show
  1. package/dist/agent/askOnce.d.ts +10 -0
  2. package/dist/agent/askOnce.d.ts.map +1 -1
  3. package/dist/agent/askOnce.js +10 -2
  4. package/dist/agent/goal.d.ts +55 -0
  5. package/dist/agent/goal.d.ts.map +1 -0
  6. package/dist/agent/goal.js +120 -0
  7. package/dist/agent/handoffBrief.d.ts +82 -0
  8. package/dist/agent/handoffBrief.d.ts.map +1 -0
  9. package/dist/agent/handoffBrief.js +121 -0
  10. package/dist/agent/modelCatalogue.d.ts +23 -0
  11. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  12. package/dist/agent/modelCatalogue.js +28 -0
  13. package/dist/agent/outputStyles.d.ts +27 -0
  14. package/dist/agent/outputStyles.d.ts.map +1 -0
  15. package/dist/agent/outputStyles.js +174 -0
  16. package/dist/agent/planMode.d.ts.map +1 -1
  17. package/dist/agent/planMode.js +16 -3
  18. package/dist/agent/skills.d.ts.map +1 -1
  19. package/dist/agent/skills.js +7 -0
  20. package/dist/api/billingLines.d.ts +11 -0
  21. package/dist/api/billingLines.d.ts.map +1 -0
  22. package/dist/api/billingLines.js +56 -0
  23. package/dist/api/client.d.ts +2 -0
  24. package/dist/api/client.d.ts.map +1 -1
  25. package/dist/api/scheduleLines.d.ts +53 -0
  26. package/dist/api/scheduleLines.d.ts.map +1 -0
  27. package/dist/api/scheduleLines.js +150 -0
  28. package/dist/context/contextFiles.d.ts +81 -0
  29. package/dist/context/contextFiles.d.ts.map +1 -0
  30. package/dist/context/contextFiles.js +310 -0
  31. package/dist/diff/collect.d.ts +25 -0
  32. package/dist/diff/collect.d.ts.map +1 -0
  33. package/dist/diff/collect.js +169 -0
  34. package/dist/diff/diffView.d.ts +57 -0
  35. package/dist/diff/diffView.d.ts.map +1 -0
  36. package/dist/diff/diffView.js +231 -0
  37. package/dist/diff/reviewQueue.d.ts +53 -0
  38. package/dist/diff/reviewQueue.d.ts.map +1 -0
  39. package/dist/diff/reviewQueue.js +349 -0
  40. package/dist/events/recap.d.ts +54 -0
  41. package/dist/events/recap.d.ts.map +1 -0
  42. package/dist/events/recap.js +161 -0
  43. package/dist/events/sessionEvents.d.ts +54 -0
  44. package/dist/events/sessionEvents.d.ts.map +1 -0
  45. package/dist/events/sessionEvents.js +324 -0
  46. package/dist/events/sessionStats.d.ts +89 -0
  47. package/dist/events/sessionStats.d.ts.map +1 -0
  48. package/dist/events/sessionStats.js +264 -0
  49. package/dist/import/claudeImport.d.ts +44 -0
  50. package/dist/import/claudeImport.d.ts.map +1 -0
  51. package/dist/import/claudeImport.js +337 -0
  52. package/dist/index.d.ts +34 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +34 -0
  55. package/dist/permissions/autoMode.d.ts +89 -0
  56. package/dist/permissions/autoMode.d.ts.map +1 -0
  57. package/dist/permissions/autoMode.js +346 -0
  58. package/dist/permissions/rules.d.ts +6 -0
  59. package/dist/permissions/rules.d.ts.map +1 -1
  60. package/dist/permissions/rules.js +33 -0
  61. package/dist/project/projectCommands.d.ts +33 -0
  62. package/dist/project/projectCommands.d.ts.map +1 -0
  63. package/dist/project/projectCommands.js +246 -0
  64. package/dist/search/conversationTurns.d.ts +53 -0
  65. package/dist/search/conversationTurns.d.ts.map +1 -0
  66. package/dist/search/conversationTurns.js +170 -0
  67. package/dist/search/globalSearch.d.ts +144 -0
  68. package/dist/search/globalSearch.d.ts.map +1 -0
  69. package/dist/search/globalSearch.js +350 -0
  70. package/dist/share/sessionChannel.d.ts +136 -0
  71. package/dist/share/sessionChannel.d.ts.map +1 -0
  72. package/dist/share/sessionChannel.js +373 -0
  73. package/dist/share/sessionShare.d.ts +104 -0
  74. package/dist/share/sessionShare.d.ts.map +1 -0
  75. package/dist/share/sessionShare.js +336 -0
  76. package/dist/ui/configCmd.d.ts +48 -0
  77. package/dist/ui/configCmd.d.ts.map +1 -0
  78. package/dist/ui/configCmd.js +146 -0
  79. package/dist/ui/deployPanel.d.ts +91 -0
  80. package/dist/ui/deployPanel.d.ts.map +1 -0
  81. package/dist/ui/deployPanel.js +442 -0
  82. package/dist/ui/mergeAssistant.d.ts +120 -0
  83. package/dist/ui/mergeAssistant.d.ts.map +1 -0
  84. package/dist/ui/mergeAssistant.js +391 -0
  85. package/dist/ui/notify.d.ts +114 -0
  86. package/dist/ui/notify.d.ts.map +1 -0
  87. package/dist/ui/notify.js +235 -0
  88. package/dist/ui/promptSuggest.d.ts +56 -0
  89. package/dist/ui/promptSuggest.d.ts.map +1 -0
  90. package/dist/ui/promptSuggest.js +160 -0
  91. package/dist/ui/taskGraph.d.ts +44 -0
  92. package/dist/ui/taskGraph.d.ts.map +1 -0
  93. package/dist/ui/taskGraph.js +289 -0
  94. package/dist/util/addDir.d.ts +31 -0
  95. package/dist/util/addDir.d.ts.map +1 -0
  96. package/dist/util/addDir.js +124 -0
  97. package/dist/util/batch.d.ts +16 -0
  98. package/dist/util/batch.d.ts.map +1 -0
  99. package/dist/util/batch.js +92 -0
  100. package/dist/util/cellWidth.d.ts +12 -0
  101. package/dist/util/cellWidth.d.ts.map +1 -0
  102. package/dist/util/cellWidth.js +161 -0
  103. package/dist/util/duration.d.ts +8 -0
  104. package/dist/util/duration.d.ts.map +1 -0
  105. package/dist/util/duration.js +50 -0
  106. package/dist/util/loopJobs.d.ts +67 -0
  107. package/dist/util/loopJobs.d.ts.map +1 -0
  108. package/dist/util/loopJobs.js +169 -0
  109. package/dist/util/settingsFile.d.ts +14 -0
  110. package/dist/util/settingsFile.d.ts.map +1 -0
  111. package/dist/util/settingsFile.js +99 -0
  112. package/dist/util/toolLabel.d.ts +2 -0
  113. package/dist/util/toolLabel.d.ts.map +1 -0
  114. package/dist/util/toolLabel.js +17 -0
  115. package/dist/verify/failureTimeline.d.ts +100 -0
  116. package/dist/verify/failureTimeline.d.ts.map +1 -0
  117. package/dist/verify/failureTimeline.js +253 -0
  118. package/dist/verify/verification.d.ts +75 -0
  119. package/dist/verify/verification.d.ts.map +1 -0
  120. package/dist/verify/verification.js +315 -0
  121. package/package.json +5 -4
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ // S20 — auto-mode config + the classifier's prompt/parse (see the design doc
3
+ // nexrall/docs/AUTO_MODE_CLASSIFIER.md).
4
+ //
5
+ // The pure half: where `autoMode` settings may come from (user + managed +
6
+ // --settings ONLY — never project/local, so a cloned repo cannot loosen the
7
+ // gate), how tiers merge, the built-in prose limits, the classifier prompt and
8
+ // the reply parser. The model CALL and the runtime (cache, budget, fast paths)
9
+ // live in the hosts — cli/src/permissions/autoClassify.ts for the CLI.
10
+ //
11
+ // The lists below are PROSE FOR A JUDGE, not regexes: they say what the
12
+ // classifier must protect, in the same words a human would, because the check
13
+ // is a model call and not another denylist (the destructive-token guard is
14
+ // that — and it stays the code-level floor, unchanged, underneath this).
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.AUTO_MODE_UNAVAILABLE = exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = exports.AUTO_MODE_DEFAULTS_SENTINEL = exports.AUTO_MODE_SOFT_DENY_DEFAULTS = exports.AUTO_MODE_HARD_DENY_DEFAULTS = void 0;
50
+ exports.loadAutoModeConfig = loadAutoModeConfig;
51
+ exports.classifyPrompt = classifyPrompt;
52
+ exports.parseClassifierReply = parseClassifierReply;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ const os = __importStar(require("os"));
56
+ const rules_1 = require("./rules");
57
+ /**
58
+ * The built-in hard limits (CC's `$defaults` for `hard_deny`): unconditional —
59
+ * user intent does NOT clear these. Keep this list SHORT and beyond argument;
60
+ * it exists for the actions no request should ever authorize.
61
+ */
62
+ exports.AUTO_MODE_HARD_DENY_DEFAULTS = [
63
+ {
64
+ label: 'Data exfiltration',
65
+ text: 'Sending file contents, repository contents, environment variables, credentials or tokens to any network ' +
66
+ 'destination the user has not explicitly named (e.g. curl/wget with a body built from local files or env).',
67
+ },
68
+ {
69
+ label: 'Credential and system files',
70
+ text: 'Writing to or modifying security-sensitive locations outside the project: ~/.ssh, ~/.aws, ~/.config/gh, ' +
71
+ '~/.netrc, ~/.npmrc credential entries, shell rc files (.zshrc/.bashrc), /etc, or the OS keychain.',
72
+ },
73
+ {
74
+ label: 'Safety-system tampering',
75
+ text: "Disabling or weakening this session's own safety controls: editing permission rules or autoMode settings to " +
76
+ 'grant more access, clearing audit trails, or killing the permission gate.',
77
+ },
78
+ ];
79
+ /**
80
+ * The built-in soft limits (CC's `$defaults` for `soft_deny`): block UNLESS the
81
+ * user's own latest message directly and specifically asks for this exact
82
+ * action, or an allow exception matches.
83
+ */
84
+ exports.AUTO_MODE_SOFT_DENY_DEFAULTS = [
85
+ {
86
+ label: 'Git history rewrite',
87
+ text: 'Rewriting shared history: git push --force/--force-with-lease to a shared branch, or reset --hard that ' +
88
+ 'discards committed work.',
89
+ },
90
+ {
91
+ label: 'Database and infra destruction',
92
+ text: 'Destructive database or infrastructure operations against shared or production systems: DROP/DELETE/TRUNCATE ' +
93
+ 'on non-scratch data, terraform destroy, kubectl delete of shared resources, deleting cloud buckets.',
94
+ },
95
+ {
96
+ label: 'Mass deletion outside scratch space',
97
+ text: 'Deleting many files or whole directories outside the project workspace and scratch/temp locations.',
98
+ },
99
+ ];
100
+ /** The `$defaults` sentinel (CC's spelling) inside soft_deny / hard_deny lists. */
101
+ exports.AUTO_MODE_DEFAULTS_SENTINEL = '$defaults';
102
+ exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = 400;
103
+ exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = 0.25;
104
+ function readTier(file) {
105
+ try {
106
+ const obj = JSON.parse(fs.readFileSync(file, 'utf-8'));
107
+ const am = obj?.autoMode;
108
+ return am && typeof am === 'object' && !Array.isArray(am) ? am : null;
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /** Accept CC's snake_case spellings and our camelCase ones. */
115
+ function pickKey(am, ...names) {
116
+ for (const n of names) {
117
+ if (Object.prototype.hasOwnProperty.call(am, n))
118
+ return { key: n, value: am[n] };
119
+ }
120
+ return null;
121
+ }
122
+ function asProseList(value, key, warnings) {
123
+ if (!Array.isArray(value)) {
124
+ warnings.push(`autoMode.${key} must be an array of strings — ignored`);
125
+ return null;
126
+ }
127
+ return value
128
+ .filter((v) => typeof v === 'string')
129
+ .map((v) => v.trim())
130
+ .filter(Boolean);
131
+ }
132
+ function asBool(value, key, warnings) {
133
+ if (typeof value === 'boolean')
134
+ return value;
135
+ if (value === 'true')
136
+ return true;
137
+ if (value === 'false')
138
+ return false;
139
+ warnings.push(`autoMode.${key} must be true or false — ignored`);
140
+ return null;
141
+ }
142
+ function asPositiveNumber(value, key, warnings) {
143
+ const n = typeof value === 'number' ? value : Number(value);
144
+ if (!Number.isFinite(n) || n < 0) {
145
+ warnings.push(`autoMode.${key} must be a non-negative number — ignored`);
146
+ return null;
147
+ }
148
+ return n;
149
+ }
150
+ /** Expand `$defaults` in a list, or supply defaults when the key was never set. */
151
+ function expandDefaults(list, defaults) {
152
+ if (list === null)
153
+ return defaults.map((d) => `[${d.label}] ${d.text}`);
154
+ const out = [];
155
+ for (const item of list) {
156
+ if (item === exports.AUTO_MODE_DEFAULTS_SENTINEL)
157
+ out.push(...defaults.map((d) => `[${d.label}] ${d.text}`));
158
+ else
159
+ out.push(item);
160
+ }
161
+ return out;
162
+ }
163
+ /**
164
+ * Load the effective auto-mode config.
165
+ *
166
+ * Tier order — LOW to HIGH precedence (a higher tier that defines a key wins
167
+ * WHOLESALE for that key): user < managed < session (`--settings`). Project and
168
+ * settings.local are read ONLY to report that their `autoMode` is ignored;
169
+ * they can never contribute a value. That asymmetry is the whole security
170
+ * model: settings travel with a checkout, and a checkout that could grant
171
+ * itself trust would make the classifier theatre.
172
+ */
173
+ function loadAutoModeConfig(workDir) {
174
+ const warnings = [];
175
+ const sources = [];
176
+ const ignoredProjectFiles = [];
177
+ const userFile = path.join(os.homedir(), '.nexrall', 'settings.json');
178
+ const managedFile = (0, rules_1.managedSettingsPath)();
179
+ const tiers = [
180
+ { file: userFile, autoMode: readTier(userFile) },
181
+ { file: managedFile, autoMode: readTier(managedFile) },
182
+ { file: '--settings', autoMode: (0, rules_1.getSessionSettings)()?.autoMode ?? null },
183
+ ];
184
+ // A non-object there (e.g. the string "off") is still an attempt we must not
185
+ // swallow silently.
186
+ for (const cand of [
187
+ path.join(workDir, '.nexrall', 'settings.json'),
188
+ path.join(workDir, '.nexrall', 'settings.local.json'),
189
+ ]) {
190
+ try {
191
+ const obj = JSON.parse(fs.readFileSync(cand, 'utf-8'));
192
+ if (obj && Object.prototype.hasOwnProperty.call(obj, 'autoMode'))
193
+ ignoredProjectFiles.push(cand);
194
+ }
195
+ catch {
196
+ /* no file / unreadable — nothing ignored */
197
+ }
198
+ }
199
+ // Per-key merge, low → high.
200
+ let enabled = null;
201
+ let classifierModel;
202
+ let environment = null;
203
+ let allow = null;
204
+ let softList = null;
205
+ let hardList = null;
206
+ let classifyAllShell = null;
207
+ let maxClassifications = null;
208
+ let maxBudgetUsd = null;
209
+ for (const tier of tiers) {
210
+ if (!tier.autoMode)
211
+ continue;
212
+ const am = tier.autoMode;
213
+ const keys = [];
214
+ const take = (r, key) => {
215
+ if (r !== null && r !== undefined)
216
+ keys.push(key);
217
+ return r;
218
+ };
219
+ const en = pickKey(am, 'classifier', 'enabled');
220
+ if (en)
221
+ enabled = take(asBool(en.value, 'classifier', warnings) ?? enabled, en.key);
222
+ const cm = pickKey(am, 'classifierModel', 'classifier_model');
223
+ if (cm && typeof cm.value === 'string' && cm.value.trim()) {
224
+ classifierModel = cm.value.trim();
225
+ keys.push(cm.key);
226
+ }
227
+ const env = pickKey(am, 'environment');
228
+ if (env)
229
+ environment = take(asProseList(env.value, 'environment', warnings) ?? environment, 'environment');
230
+ const al = pickKey(am, 'allow');
231
+ if (al)
232
+ allow = take(asProseList(al.value, 'allow', warnings) ?? allow, 'allow');
233
+ const sd = pickKey(am, 'soft_deny', 'softDeny');
234
+ if (sd)
235
+ softList = take(asProseList(sd.value, sd.key, warnings) ?? softList, sd.key);
236
+ const hd = pickKey(am, 'hard_deny', 'hardDeny');
237
+ if (hd)
238
+ hardList = take(asProseList(hd.value, hd.key, warnings) ?? hardList, hd.key);
239
+ const ca = pickKey(am, 'classifyAllShell', 'classify_all_shell');
240
+ if (ca)
241
+ classifyAllShell = take(asBool(ca.value, ca.key, warnings) ?? classifyAllShell, ca.key);
242
+ const mc = pickKey(am, 'maxClassifications', 'max_classifications');
243
+ if (mc)
244
+ maxClassifications = take(asPositiveNumber(mc.value, mc.key, warnings) ?? maxClassifications, mc.key);
245
+ const mb = pickKey(am, 'maxBudgetUsd', 'max_budget_usd');
246
+ if (mb)
247
+ maxBudgetUsd = take(asPositiveNumber(mb.value, mb.key, warnings) ?? maxBudgetUsd, mb.key);
248
+ if (keys.length)
249
+ sources.push({ file: tier.file, keys });
250
+ }
251
+ return {
252
+ enabled: enabled ?? true,
253
+ ...(classifierModel ? { classifierModel } : {}),
254
+ environment: environment ?? [],
255
+ allow: allow ?? [],
256
+ softDeny: expandDefaults(softList, exports.AUTO_MODE_SOFT_DENY_DEFAULTS),
257
+ hardDeny: expandDefaults(hardList, exports.AUTO_MODE_HARD_DENY_DEFAULTS),
258
+ classifyAllShell: classifyAllShell ?? false,
259
+ maxClassifications: maxClassifications ?? exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS,
260
+ maxBudgetUsd: maxBudgetUsd ?? exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD,
261
+ sources,
262
+ ignoredProjectFiles,
263
+ warnings,
264
+ };
265
+ }
266
+ // ─── The classifier's prompt + reply ─────────────────────────────────────────
267
+ /** Clip helper local to this module (the prompt must never grow unbounded). */
268
+ function clip(text, max) {
269
+ return text.length > max ? text.slice(0, max - 1) + '\u2026' : text;
270
+ }
271
+ /**
272
+ * The review request. Deliberately one flat prompt — the classifier is a fresh,
273
+ * tool-less call (askModelOnce), so everything it may consult must be here:
274
+ * the limits, the environment, the action, and the human's actual words.
275
+ */
276
+ function classifyPrompt(a) {
277
+ const list = (items) => (items.length ? items.map((t) => `- ${t}`).join('\n') : ' (none)');
278
+ const env = [
279
+ `The project directory ${a.workDir}${a.extraDirs?.length ? ` and the added directories ${a.extraDirs.join(', ')}` : ''}`,
280
+ "the current git repository's own configured remotes",
281
+ ...a.cfg.environment,
282
+ ];
283
+ const intent = a.intent?.length
284
+ ? a.intent.map((t, i) => `[${i + 1}] ${clip(t.replace(/\s+/g, ' ').trim(), 300)}`).join('\n')
285
+ : ' (none)';
286
+ let action;
287
+ try {
288
+ action = clip(JSON.stringify(a.input), 2000);
289
+ }
290
+ catch {
291
+ action = '(unserializable input)';
292
+ }
293
+ return ('You are the AUTO-MODE SAFETY CLASSIFIER for a coding agent.\n' +
294
+ 'The agent runs in "auto" mode: it will execute the action below WITHOUT asking the human, unless you block it.\n' +
295
+ "Judge the ACTION's real-world effect, not its wording.\n\n" +
296
+ 'HARD LIMITS — block unconditionally, even if the user asked:\n' +
297
+ list(a.cfg.hardDeny) +
298
+ "\n\nSOFT LIMITS — block UNLESS the user's latest message directly and specifically asks for this exact action, " +
299
+ 'or an ALLOW EXCEPTION matches:\n' +
300
+ list(a.cfg.softDeny) +
301
+ '\n\nALLOW EXCEPTIONS (pre-approved by the user; clear SOFT limits, never hard ones):\n' +
302
+ list(a.cfg.allow) +
303
+ '\n\nTRUSTED ENVIRONMENT (routine here; staying inside it is not a limit violation):\n' +
304
+ list(env) +
305
+ '\n\nTHE ACTION:\ntool: ' +
306
+ a.tool +
307
+ '\ninput: ' +
308
+ action +
309
+ '\n\nRECENT USER MESSAGES (intent; the LAST one is the current instruction):\n' +
310
+ intent +
311
+ '\n\nReply with EXACTLY ONE line, one of:\n' +
312
+ 'ALLOW\n' +
313
+ 'BLOCK: <one short reason>\n' +
314
+ 'BLOCK-HARD: <one short reason>\n' +
315
+ 'Use BLOCK only for a soft limit without clear user intent, BLOCK-HARD for a hard limit. ' +
316
+ 'For a routine, reversible action inside the workspace, reply ALLOW. No other text.');
317
+ }
318
+ /** A local guard failed us (or the host never called in) — see the design doc. */
319
+ exports.AUTO_MODE_UNAVAILABLE = {
320
+ action: 'unavailable',
321
+ reason: 'classifier could not review the action',
322
+ };
323
+ /**
324
+ * Parse one line of classifier output. Anything that is not a clear ALLOW is
325
+ * NOT an allow — a mangled or empty reply fails closed (`unavailable`).
326
+ */
327
+ function parseClassifierReply(text) {
328
+ if (typeof text !== 'string')
329
+ return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'no reply from the classifier' };
330
+ const first = text
331
+ .split('\n')
332
+ .find((l) => l.trim().length > 0)
333
+ ?.trim() ?? '';
334
+ if (!first)
335
+ return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'empty reply from the classifier' };
336
+ const hard = /^BLOCK[\s_-]*HARD\b[:\s-]*(.*)$/i.exec(first);
337
+ if (hard)
338
+ return { action: 'block-hard', reason: hard[1].trim() || 'hard limit' };
339
+ const soft = /^BLOCK\b[:\s-]*(.*)$/i.exec(first);
340
+ if (soft)
341
+ return { action: 'block-soft', reason: soft[1].trim() || 'soft limit' };
342
+ if (/^ALLOW\b/i.test(first))
343
+ return { action: 'allow' };
344
+ return { ...exports.AUTO_MODE_UNAVAILABLE, reason: `unrecognized classifier reply: ${clip(first, 80)}` };
345
+ }
346
+ //# sourceMappingURL=autoMode.js.map
@@ -9,11 +9,17 @@ export interface MergedSettings {
9
9
  /** Raw merged object (for hooks, allowedTools, etc.). */
10
10
  raw: Record<string, unknown>;
11
11
  }
12
+ export declare function managedSettingsPath(): string;
12
13
  /** Replace the rules supplied for this process by flags. Not persisted anywhere. */
13
14
  export declare function setSessionRules(r: Partial<PermissionRules>): void;
14
15
  export declare function setSessionSettings(obj: Record<string, unknown> | null): void;
15
16
  export declare function getSessionSettings(): Record<string, unknown> | null;
16
17
  export declare function loadSettings(workDir: string): MergedSettings;
18
+ /** The first ALLOW rule that matches this call, or null. S20's classifier gate
19
+ * reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
20
+ * (suspended while the classifier is on — see isBroadAllowRule). */
21
+ export declare function matchedAllowRule(rules: PermissionRules, tool: string, input: Record<string, unknown>, workDir: string): string | null;
22
+ export declare function isBroadAllowRule(rule: string): boolean;
17
23
  /**
18
24
  * Evaluate a tool call against merged rules. Returns the matching decision, or
19
25
  * null when no rule applies (caller decides the default, usually "ask").
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AA6BD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEjE;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,CAE5E;AACD,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8B5D;AAqJD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,IAAI,CAK3B"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAsBD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEjE;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,CAE5E;AACD,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8B5D;AAqJD;;oEAEoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAEf;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,IAAI,CAK3B"}
@@ -33,15 +33,22 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.managedSettingsPath = managedSettingsPath;
36
37
  exports.setSessionRules = setSessionRules;
37
38
  exports.setSessionSettings = setSessionSettings;
38
39
  exports.getSessionSettings = getSessionSettings;
39
40
  exports.loadSettings = loadSettings;
41
+ exports.matchedAllowRule = matchedAllowRule;
42
+ exports.isBroadAllowRule = isBroadAllowRule;
40
43
  exports.evaluatePermission = evaluatePermission;
41
44
  const fs = __importStar(require("fs"));
42
45
  const path = __importStar(require("path"));
43
46
  const os = __importStar(require("os"));
44
47
  function managedSettingsPath() {
48
+ // Tests (and a sandboxed doctor run) point this at a fixture. Checked first so it
49
+ // can never be shadowed by a real machine-level file.
50
+ if (process.env.NEXRALL_MANAGED_SETTINGS)
51
+ return process.env.NEXRALL_MANAGED_SETTINGS;
45
52
  if (process.platform === 'darwin')
46
53
  return '/Library/Application Support/Nexrall/managed-settings.json';
47
54
  if (process.platform === 'win32')
@@ -258,6 +265,32 @@ function ruleMatches(rule, tool, input, workDir, opts) {
258
265
  return true;
259
266
  return matchValues(tool, input, workDir).some((v) => matchPattern(pattern, v));
260
267
  }
268
+ /** The first ALLOW rule that matches this call, or null. S20's classifier gate
269
+ * reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
270
+ * (suspended while the classifier is on — see isBroadAllowRule). */
271
+ function matchedAllowRule(rules, tool, input, workDir) {
272
+ return rules.allow.find((r) => ruleMatches(r, tool, input, workDir, { forAllow: true })) ?? null;
273
+ }
274
+ /**
275
+ * Is this allow rule a BLANKET pre-approval ("anything this tool can do")?
276
+ * Those stop meaning anything once an auto-mode classifier exists: `Bash(*)`
277
+ * is exactly the rule a prompt-injected agent would wish for, and CC suspends
278
+ * the same set in their auto mode. Broad = bare tool name / `(*)`, or a bash
279
+ * rule that hands control to an interpreter (`Bash(python:*)`, `node -e`…).
280
+ * Narrow rules (`Bash(npm run test:*)`, `Read(src/**)`) keep resolving first.
281
+ */
282
+ const INTERPRETER_HEADS = /^(?:\S*\/)?(python3?|node|bun|deno|ruby|perl|php|bash|sh|zsh|fish|pwsh|powershell|osascript|cmd)(\s|:|$)/i;
283
+ function isBroadAllowRule(rule) {
284
+ const { tool, pattern } = parseRule(rule);
285
+ if (pattern === null || pattern === '' || pattern === '*')
286
+ return true;
287
+ const isBashRule = ['bash', 'run_command', 'execute_command', 'Bash'].includes(tool);
288
+ if (!isBashRule)
289
+ return false;
290
+ if (!/[*:]$/.test(pattern))
291
+ return false;
292
+ return INTERPRETER_HEADS.test(pattern.replace(/[:*]+$/, '').trim());
293
+ }
261
294
  /**
262
295
  * Evaluate a tool call against merged rules. Returns the matching decision, or
263
296
  * null when no rule applies (caller decides the default, usually "ask").
@@ -0,0 +1,33 @@
1
+ export interface ProjectCommand {
2
+ /** What the user types after `/run`: an npm script name, a make target, … */
3
+ name: string;
4
+ /** The full shell command. */
5
+ command: string;
6
+ /** The file it came from, shown in the picker and in errors. */
7
+ source: string;
8
+ }
9
+ /** The package manager whose scripts this project's lockfile asks for. */
10
+ export declare function packageManager(workDir: string): 'npm' | 'pnpm' | 'yarn' | 'bun';
11
+ /**
12
+ * Every command this folder can run, in a stable order (package.json first — the most
13
+ * common source — then the rest alphabetically). Missing files are simply absent: a
14
+ * project with no package.json is not an error, it is a Cargo project.
15
+ */
16
+ export declare function detectProjectCommands(workDir: string): ProjectCommand[];
17
+ /**
18
+ * The commands a shortcut should run: everything in the best tier it finds, best
19
+ * first. More than one means the caller asks which (a repo with `test` and
20
+ * `test:integration` should not silently pick one — running the wrong suite and
21
+ * calling it "the tests" is worse than one extra keystroke).
22
+ */
23
+ export declare function resolveShortcut<T extends {
24
+ name: string;
25
+ }>(cmds: T[], want: string): T[];
26
+ /**
27
+ * Runs `git` with the user's arguments: `/git` alone means `git status`, `/git log -5`
28
+ * means exactly that. Not a list of allowed subcommands — the read-only gate in front
29
+ * of this (core's isReadOnlyBash) is what decides whether it may run unprompted, so
30
+ * `/git commit` works in a normal session and is refused under `--readonly`.
31
+ */
32
+ export declare function gitCommand(arg: string): string;
33
+ //# sourceMappingURL=projectCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectCommands.d.ts","sourceRoot":"","sources":["../../src/project/projectCommands.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAkCD,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAM/E;AAsED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAiEvE;AAsBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,CAKxF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C"}