dsh-permission-rules 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.es.md +13 -7
- package/README.hi.md +13 -7
- package/README.md +13 -7
- package/README.pt.md +13 -7
- package/README.zh.md +13 -7
- package/cordis.patch.yml +3 -0
- package/docs/rules-format.en.md +4 -2
- package/docs/rules-format.md +4 -2
- package/lib/index.js +308 -81
- package/lib/types/config.d.ts +12 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +3 -0
- package/lib/types/config.js.map +1 -1
- package/lib/types/events.d.ts +25 -6
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +22 -4
- package/lib/types/events.js.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/index.js.map +1 -1
- package/lib/types/prose.d.ts +6 -0
- package/lib/types/prose.d.ts.map +1 -1
- package/lib/types/prose.js +25 -15
- package/lib/types/prose.js.map +1 -1
- package/lib/types/rules.d.ts +8 -2
- package/lib/types/rules.d.ts.map +1 -1
- package/lib/types/rules.js +8 -3
- package/lib/types/rules.js.map +1 -1
- package/lib/types/runtime.d.ts +79 -17
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +249 -52
- package/lib/types/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +15 -0
- package/src/events.ts +28 -6
- package/src/index.ts +1 -1
- package/src/prose.ts +31 -15
- package/src/rules.ts +10 -3
- package/src/runtime.ts +255 -49
package/src/config.ts
CHANGED
|
@@ -64,6 +64,17 @@ export interface Config {
|
|
|
64
64
|
* policy in production before enforcing it.
|
|
65
65
|
*/
|
|
66
66
|
enforce?: boolean
|
|
67
|
+
/**
|
|
68
|
+
* Whether to keep appending audit events to the session log on hosts
|
|
69
|
+
* whose `Session.append` predates the `ignorable` envelope marker
|
|
70
|
+
* (the `0.1.0-rc.6` line). Defaults to `false`: such hosts write the
|
|
71
|
+
* events UNMARKED, which makes the session unresumable on stricter
|
|
72
|
+
* hosts, so the runtime detects the host and disables session-log audit
|
|
73
|
+
* with a one-time warning instead. Set `true` to opt back into the
|
|
74
|
+
* in-session audit trail (and accept that those sessions may need
|
|
75
|
+
* `scripts/repair-session-logs.mjs` before loading on a newer harness).
|
|
76
|
+
*/
|
|
77
|
+
allowUnmarkedAudit?: boolean
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
/** Config after {@link resolveConfig}: every optional field has its explicit default. */
|
|
@@ -82,6 +93,7 @@ export interface ResolvedConfig {
|
|
|
82
93
|
readonly searchUp: boolean
|
|
83
94
|
readonly maxGlobStars: number
|
|
84
95
|
readonly enforce: boolean
|
|
96
|
+
readonly allowUnmarkedAudit: boolean
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
/** Schemastery schema: the loader validates and fills defaults before `apply`. */
|
|
@@ -100,6 +112,7 @@ export const Config: z<Config> = z.object({
|
|
|
100
112
|
searchUp: z.boolean().default(false),
|
|
101
113
|
maxGlobStars: z.number().default(2),
|
|
102
114
|
enforce: z.boolean().default(true),
|
|
115
|
+
allowUnmarkedAudit: z.boolean().default(false),
|
|
103
116
|
})
|
|
104
117
|
|
|
105
118
|
/**
|
|
@@ -142,6 +155,7 @@ export function resolveConfig(config: Config = {}): ResolvedConfig {
|
|
|
142
155
|
assertBoolean('searchUp', searchUp)
|
|
143
156
|
assertBoolean('caseInsensitivePaths', config.caseInsensitivePaths ?? process.platform === 'win32')
|
|
144
157
|
assertBoolean('enforce', config.enforce ?? true)
|
|
158
|
+
assertBoolean('allowUnmarkedAudit', config.allowUnmarkedAudit ?? false)
|
|
145
159
|
return {
|
|
146
160
|
rulesFile,
|
|
147
161
|
fallbackPath: config.fallbackPath,
|
|
@@ -157,6 +171,7 @@ export function resolveConfig(config: Config = {}): ResolvedConfig {
|
|
|
157
171
|
searchUp,
|
|
158
172
|
maxGlobStars,
|
|
159
173
|
enforce: config.enforce ?? true,
|
|
174
|
+
allowUnmarkedAudit: config.allowUnmarkedAudit ?? false,
|
|
160
175
|
}
|
|
161
176
|
}
|
|
162
177
|
|
package/src/events.ts
CHANGED
|
@@ -17,9 +17,16 @@
|
|
|
17
17
|
* downstream — an auditable "what would the policy have done" trail.
|
|
18
18
|
*
|
|
19
19
|
* The event is appended with the envelope's `ignorable: true` marker (see
|
|
20
|
-
* {@link AuditAppend})
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* {@link AuditAppend}). Harness builds that honor the marker (post-rc.6)
|
|
21
|
+
* stamp it on the envelope and skip unknown ignorable records when loading,
|
|
22
|
+
* so the audit can never refuse a session. Builds whose `Session.append`
|
|
23
|
+
* predates the marker — the `0.1.0-rc.6` line — silently DROP the options
|
|
24
|
+
* bag: the event then lands UNMARKED and makes the session unresumable on
|
|
25
|
+
* hosts with required-on-read semantics (`SessionFormatUnsupportedError`).
|
|
26
|
+
* The runtime detects this at first use (peer version pre-check plus a
|
|
27
|
+
* probe of the appended envelope) and disables session-log audit on such
|
|
28
|
+
* hosts with a one-time warning; `allowUnmarkedAudit: true` opts back in,
|
|
29
|
+
* and `scripts/repair-session-logs.mjs` repairs already-polluted logs.
|
|
23
30
|
* @module dsh-permission-rules/events
|
|
24
31
|
*/
|
|
25
32
|
|
|
@@ -77,12 +84,27 @@ export interface AuditDecision {
|
|
|
77
84
|
/**
|
|
78
85
|
* `Session.append` narrowed to this plugin's audit event. The options bag
|
|
79
86
|
* exists only on host builds that expose the `ignorable` envelope-marker
|
|
80
|
-
* surface (post-rc.6 `@deepseek-ai/dsh-session`); an rc.6 host accepts
|
|
81
|
-
*
|
|
82
|
-
* marker
|
|
87
|
+
* surface (post-rc.6 `@deepseek-ai/dsh-session`); an rc.6 host accepts the
|
|
88
|
+
* call but silently drops the third argument — the event is appended
|
|
89
|
+
* WITHOUT the marker, which is exactly what breaks later resume on stricter
|
|
90
|
+
* hosts. The runtime treats the marker as optional-but-probed: see
|
|
91
|
+
* {@link isMarkedAuditEvent}.
|
|
83
92
|
*/
|
|
84
93
|
export type AuditAppend = (
|
|
85
94
|
type: 'permissionRules/decision',
|
|
86
95
|
data: AuditDecision,
|
|
87
96
|
options?: { ignorable?: true },
|
|
88
97
|
) => unknown
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whether an `append` call actually honored the `ignorable` marker: the
|
|
101
|
+
* logged event returned by the host carries `ignorable === true` on
|
|
102
|
+
* marker-aware builds and nothing on pre-marker builds. `false` (or any
|
|
103
|
+
* non-event return) means the host dropped the marker and the event landed
|
|
104
|
+
* unmarked — the runtime then degrades instead of polluting further logs.
|
|
105
|
+
* @param result - the return value of the audit append.
|
|
106
|
+
* @returns true only when the marker is present on the returned envelope.
|
|
107
|
+
*/
|
|
108
|
+
export function isMarkedAuditEvent(result: unknown): boolean {
|
|
109
|
+
return typeof result === 'object' && result !== null && (result as { ignorable?: unknown }).ignorable === true
|
|
110
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const name = 'permission-rules'
|
|
|
22
22
|
export const inject = ['commands', 'tools']
|
|
23
23
|
|
|
24
24
|
export { apply }
|
|
25
|
-
export { PermissionRulesRuntime } from './runtime.ts'
|
|
25
|
+
export { PermissionRulesRuntime, isUnmarkedHostVersion } from './runtime.ts'
|
|
26
26
|
export * from './config.ts'
|
|
27
27
|
export * from './events.ts'
|
|
28
28
|
export * from './rules.ts'
|
package/src/prose.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface UiProse {
|
|
|
34
34
|
decisionsHeader: (shown: number, total: number) => string
|
|
35
35
|
/** Line shown when the session log holds no audit decisions. */
|
|
36
36
|
noDecisions: string
|
|
37
|
+
/** Notice appended to `/rules decisions` when session-log audit is disabled on this host (ignorable marker not honored). */
|
|
38
|
+
auditDisabledNotice: string
|
|
37
39
|
/** Rejection of a malformed decisions count. */
|
|
38
40
|
invalidDecisionsCount: (arg: string) => string
|
|
39
41
|
/** One audit-decision row (dry-run rows mark the actual outcome). */
|
|
@@ -48,6 +50,8 @@ export interface UiProse {
|
|
|
48
50
|
testUnknownFlag: (flag: string) => string
|
|
49
51
|
/** Rejection of a malformed `/rules test` flag (missing value, bad KEY=VALUE). */
|
|
50
52
|
testBadFlag: (flag: string) => string
|
|
53
|
+
/** Rejection of an unknown `--platform` value. */
|
|
54
|
+
testBadPlatform: (value: string) => string
|
|
51
55
|
/** Usage hint of `/rules test`. */
|
|
52
56
|
testUsage: string
|
|
53
57
|
/** Warning naming shadowed (unreachable) rules by 1-based number. */
|
|
@@ -68,6 +72,8 @@ export interface DescribeTokens {
|
|
|
68
72
|
platform: string
|
|
69
73
|
disabled: string
|
|
70
74
|
tags: string
|
|
75
|
+
/** Prefix of the per-rule source-file attribution. */
|
|
76
|
+
src: string
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
/** Truncate long reasons/descriptions in one-line displays. */
|
|
@@ -84,9 +90,10 @@ const EN: UiProse = {
|
|
|
84
90
|
lastReloadWarning: error => `Warning: the last reload failed (${error}); the rules listed above are the previous ones.`,
|
|
85
91
|
dryRunNotice: 'Dry-run mode (enforce: false): deny/ask hits are audit-logged only — every call is passed through.',
|
|
86
92
|
unknownArg: arg => `Unknown /rules argument "${arg}". ${EN.usage}`,
|
|
87
|
-
usage: 'Usage: /rules [reload | decisions [n] | test <tool> <json-args>]',
|
|
93
|
+
usage: 'Usage: /rules [list | reload | decisions [n] | test <tool> <json-args>]',
|
|
88
94
|
decisionsHeader: (shown, total) => `Last ${shown} of ${total} permission decision(s) for this session:`,
|
|
89
95
|
noDecisions: 'No permission decisions recorded in this session yet.',
|
|
96
|
+
auditDisabledNotice: 'Session-log audit is disabled on this host: its Session.append predates the ignorable marker and would make sessions unresumable elsewhere (set allowUnmarkedAudit: true to opt back in).',
|
|
90
97
|
invalidDecisionsCount: arg => `Invalid decisions count "${arg}": give a positive integer (default 10).`,
|
|
91
98
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
92
99
|
const rule = ruleIndex === undefined ? '' : ` (rule ${ruleIndex + 1})`
|
|
@@ -99,7 +106,8 @@ const EN: UiProse = {
|
|
|
99
106
|
testBadJson: text => `Invalid JSON arguments "${text}": the arguments must parse as JSON.`,
|
|
100
107
|
testUnknownFlag: flag => `Unknown /rules test flag "${flag}". ${EN.testUsage}`,
|
|
101
108
|
testBadFlag: flag => `Invalid /rules test flag "${flag}": expected a value. ${EN.testUsage}`,
|
|
102
|
-
|
|
109
|
+
testBadPlatform: value => `Unknown platform "${value}" in --platform: expected one of aix, android, darwin, freebsd, linux, openbsd, sunos, win32. ${EN.testUsage}`,
|
|
110
|
+
testUsage: 'Usage: /rules test [--cwd <dir>] [--env KEY=VALUE]... [--agent <selector>]... [--platform <name>] <tool> <json-args>, e.g. /rules test bash {"command":"git push origin main"}',
|
|
103
111
|
unreachableWarning: numbers => `Warning: rule${numbers.length > 1 ? 's' : ''} ${numbers.join(', ')} ${numbers.length > 1 ? 'are' : 'is'} unreachable (shadowed by an earlier catch-all rule).`,
|
|
104
112
|
emptySource: '(no rule file — empty rule set)',
|
|
105
113
|
}
|
|
@@ -113,9 +121,10 @@ const ZH: UiProse = {
|
|
|
113
121
|
lastReloadWarning: error => `警告:上次重载失败(${error});以上列出的是之前的规则。`,
|
|
114
122
|
dryRunNotice: '干跑模式(enforce: false):deny/ask 命中仅写入审计日志——所有调用照常透传。',
|
|
115
123
|
unknownArg: arg => `未知的 /rules 参数 "${arg}"。${ZH.usage}`,
|
|
116
|
-
usage: '用法:/rules [reload | decisions [n] | test <工具名> <json-参数>]',
|
|
124
|
+
usage: '用法:/rules [list | reload | decisions [n] | test <工具名> <json-参数>]',
|
|
117
125
|
decisionsHeader: (shown, total) => `本会话最近 ${shown}/${total} 条权限裁决:`,
|
|
118
126
|
noDecisions: '本会话还没有记录任何权限裁决。',
|
|
127
|
+
auditDisabledNotice: '本宿主上会话日志审计已停用:其 Session.append 早于 ignorable 标记支持,会令会话在其他宿主上无法恢复(设 allowUnmarkedAudit: true 可重新开启)。',
|
|
119
128
|
invalidDecisionsCount: arg => `无效的裁决条数 "${arg}":请给正整数(默认 10)。`,
|
|
120
129
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
121
130
|
const rule = ruleIndex === undefined ? '' : `(规则 ${ruleIndex + 1})`
|
|
@@ -128,7 +137,8 @@ const ZH: UiProse = {
|
|
|
128
137
|
testBadJson: text => `无效的 JSON 参数 "${text}":参数必须能被解析为 JSON。`,
|
|
129
138
|
testUnknownFlag: flag => `未知的 /rules test 标志 "${flag}"。${ZH.testUsage}`,
|
|
130
139
|
testBadFlag: flag => `无效的 /rules test 标志 "${flag}":缺少取值。${ZH.testUsage}`,
|
|
131
|
-
|
|
140
|
+
testBadPlatform: value => `未知的平台 "${value}"(--platform):应为 aix、android、darwin、freebsd、linux、openbsd、sunos、win32 之一。${ZH.testUsage}`,
|
|
141
|
+
testUsage: '用法:/rules test [--cwd <目录>] [--env 键=值]... [--agent <选择器>]... [--platform <平台名>] <工具名> <json-参数>,例如 /rules test bash {"command":"git push origin main"}',
|
|
132
142
|
unreachableWarning: numbers => `警告:规则 ${numbers.join('、')} 不可达(被前面的通配规则遮蔽)。`,
|
|
133
143
|
emptySource: '(无规则文件——空规则集)',
|
|
134
144
|
}
|
|
@@ -142,9 +152,10 @@ const ES: UiProse = {
|
|
|
142
152
|
lastReloadWarning: error => `Aviso: la última recarga falló (${error}); las reglas mostradas son las anteriores.`,
|
|
143
153
|
dryRunNotice: 'Modo simulación (enforce: false): los aciertos deny/ask solo se registran en auditoría — todas las llamadas pasan.',
|
|
144
154
|
unknownArg: arg => `Argumento de /rules desconocido "${arg}". ${ES.usage}`,
|
|
145
|
-
usage: 'Uso: /rules [reload | decisions [n] | test <herramienta> <json-args>]',
|
|
155
|
+
usage: 'Uso: /rules [list | reload | decisions [n] | test <herramienta> <json-args>]',
|
|
146
156
|
decisionsHeader: (shown, total) => `Últimas ${shown} de ${total} decisión(es) de permiso de esta sesión:`,
|
|
147
157
|
noDecisions: 'Esta sesión aún no registra decisiones de permiso.',
|
|
158
|
+
auditDisabledNotice: 'La auditoría del registro de sesión está desactivada en este host: su Session.append es anterior al marcador ignorable y haría las sesiones irrecuperables en otros hosts (establece allowUnmarkedAudit: true para reactivarla).',
|
|
148
159
|
invalidDecisionsCount: arg => `Cantidad de decisiones inválida "${arg}": indique un entero positivo (por defecto 10).`,
|
|
149
160
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
150
161
|
const rule = ruleIndex === undefined ? '' : ` (regla ${ruleIndex + 1})`
|
|
@@ -157,7 +168,8 @@ const ES: UiProse = {
|
|
|
157
168
|
testBadJson: text => `Argumentos JSON inválidos "${text}": los argumentos deben poderse analizar como JSON.`,
|
|
158
169
|
testUnknownFlag: flag => `Bandera de /rules test desconocida "${flag}". ${ES.testUsage}`,
|
|
159
170
|
testBadFlag: flag => `Bandera de /rules test inválida "${flag}": falta un valor. ${ES.testUsage}`,
|
|
160
|
-
|
|
171
|
+
testBadPlatform: value => `Plataforma desconocida "${value}" en --platform: se esperaba una de aix, android, darwin, freebsd, linux, openbsd, sunos, win32. ${ES.testUsage}`,
|
|
172
|
+
testUsage: 'Uso: /rules test [--cwd <dir>] [--env CLAVE=VALOR]... [--agent <selector>]... [--platform <nombre>] <herramienta> <json-args>, p. ej. /rules test bash {"command":"git push origin main"}',
|
|
161
173
|
unreachableWarning: numbers => `Aviso: ${numbers.length > 1 ? 'las reglas' : 'la regla'} ${numbers.join(', ')} ${numbers.length > 1 ? 'son inalcanzables' : 'es inalcanzable'} (tapada por una regla general anterior).`,
|
|
162
174
|
emptySource: '(sin archivo de reglas — conjunto vacío)',
|
|
163
175
|
}
|
|
@@ -171,9 +183,10 @@ const PT: UiProse = {
|
|
|
171
183
|
lastReloadWarning: error => `Aviso: a última recarga falhou (${error}); as regras listadas são as anteriores.`,
|
|
172
184
|
dryRunNotice: 'Modo simulação (enforce: false): acertos deny/ask são apenas registrados na auditoria — todas as chamadas passam.',
|
|
173
185
|
unknownArg: arg => `Argumento de /rules desconhecido "${arg}". ${PT.usage}`,
|
|
174
|
-
usage: 'Uso: /rules [reload | decisions [n] | test <ferramenta> <json-args>]',
|
|
186
|
+
usage: 'Uso: /rules [list | reload | decisions [n] | test <ferramenta> <json-args>]',
|
|
175
187
|
decisionsHeader: (shown, total) => `Últimas ${shown} de ${total} decisão(ões) de permissão desta sessão:`,
|
|
176
188
|
noDecisions: 'Esta sessão ainda não registrou decisões de permissão.',
|
|
189
|
+
auditDisabledNotice: 'A auditoria do registro da sessão está desativada neste host: seu Session.append é anterior ao marcador ignorable e tornaria as sessões irrecuperáveis em outros hosts (defina allowUnmarkedAudit: true para reativar).',
|
|
177
190
|
invalidDecisionsCount: arg => `Quantidade de decisões inválida "${arg}": informe um inteiro positivo (padrão 10).`,
|
|
178
191
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
179
192
|
const rule = ruleIndex === undefined ? '' : ` (regra ${ruleIndex + 1})`
|
|
@@ -186,7 +199,8 @@ const PT: UiProse = {
|
|
|
186
199
|
testBadJson: text => `Argumentos JSON inválidos "${text}": os argumentos precisam ser JSON analisável.`,
|
|
187
200
|
testUnknownFlag: flag => `Sinalizador de /rules test desconhecido "${flag}". ${PT.testUsage}`,
|
|
188
201
|
testBadFlag: flag => `Sinalizador de /rules test inválido "${flag}": falta um valor. ${PT.testUsage}`,
|
|
189
|
-
|
|
202
|
+
testBadPlatform: value => `Plataforma desconhecida "${value}" em --platform: esperava-se uma de aix, android, darwin, freebsd, linux, openbsd, sunos, win32. ${PT.testUsage}`,
|
|
203
|
+
testUsage: 'Uso: /rules test [--cwd <dir>] [--env CHAVE=VALOR]... [--agent <seletor>]... [--platform <nome>] <ferramenta> <json-args>, ex.: /rules test bash {"command":"git push origin main"}',
|
|
190
204
|
unreachableWarning: numbers => `Aviso: ${numbers.length > 1 ? 'as regras' : 'a regra'} ${numbers.join(', ')} ${numbers.length > 1 ? 'são inalcançáveis' : 'é inalcançável'} (encoberta por uma regra geral anterior).`,
|
|
191
205
|
emptySource: '(sem arquivo de regras — conjunto vazio)',
|
|
192
206
|
}
|
|
@@ -200,9 +214,10 @@ const HI: UiProse = {
|
|
|
200
214
|
lastReloadWarning: error => `चेतावनी: अंतिम पुनः लोड विफल रहा (${error}); ऊपर सूचीबद्ध नियम पिछले वाले हैं।`,
|
|
201
215
|
dryRunNotice: 'ड्राई-रन मोड (enforce: false): deny/ask हिट केवल ऑडिट में दर्ज होते हैं — हर कॉल पास हो जाती है।',
|
|
202
216
|
unknownArg: arg => `अज्ञात /rules तर्क "${arg}"। ${HI.usage}`,
|
|
203
|
-
usage: 'उपयोग: /rules [reload | decisions [n] | test <टूल> <json-args>]',
|
|
217
|
+
usage: 'उपयोग: /rules [list | reload | decisions [n] | test <टूल> <json-args>]',
|
|
204
218
|
decisionsHeader: (shown, total) => `इस सत्र के अंतिम ${shown}/${total} अनुमति निर्णय:`,
|
|
205
219
|
noDecisions: 'इस सत्र में अभी कोई अनुमति निर्णय दर्ज नहीं है।',
|
|
220
|
+
auditDisabledNotice: 'इस होस्ट पर सत्र-लॉग ऑडिट अक्षम है: इसका Session.append ignorable मार्कर से पुराना है और सत्र अन्य होस्ट पर अप्राप्य बन जाते (पुनः चालू करने के लिए allowUnmarkedAudit: true सेट करें)।',
|
|
206
221
|
invalidDecisionsCount: arg => `अमान्य निर्णय-संख्या "${arg}": एक धनात्मक पूर्णांक दें (डिफ़ॉल्ट 10)।`,
|
|
207
222
|
decisionLine: (seq, action, toolName, ruleIndex, reason, dryRun, outcome) => {
|
|
208
223
|
const rule = ruleIndex === undefined ? '' : ` (नियम ${ruleIndex + 1})`
|
|
@@ -215,7 +230,8 @@ const HI: UiProse = {
|
|
|
215
230
|
testBadJson: text => `अमान्य JSON तर्क "${text}": तर्क JSON के रूप में पार्स होने चाहिए।`,
|
|
216
231
|
testUnknownFlag: flag => `अज्ञात /rules test फ़्लैग "${flag}"। ${HI.testUsage}`,
|
|
217
232
|
testBadFlag: flag => `अमान्य /rules test फ़्लैग "${flag}": मान गायब है। ${HI.testUsage}`,
|
|
218
|
-
|
|
233
|
+
testBadPlatform: value => `अज्ञात प्लेटफ़ॉर्म "${value}" (--platform): aix, android, darwin, freebsd, linux, openbsd, sunos, win32 में से एक अपेक्षित। ${HI.testUsage}`,
|
|
234
|
+
testUsage: 'उपयोग: /rules test [--cwd <dir>] [--env KEY=मान]... [--agent <चयनकर्ता>]... [--platform <नाम>] <टूल> <json-args>, जैसे /rules test bash {"command":"git push origin main"}',
|
|
219
235
|
unreachableWarning: numbers => `चेतावनी: नियम ${numbers.join(', ')} अप्राप्य हैं (पहले वाले सर्व-मिलान नियम से ढके हुए)।`,
|
|
220
236
|
emptySource: '(कोई नियम फ़ाइल नहीं — खाली नियम-समूह)',
|
|
221
237
|
}
|
|
@@ -225,9 +241,9 @@ export const UI_PROSE: Readonly<Record<UiLanguage, UiProse>> = { en: EN, zh: ZH,
|
|
|
225
241
|
|
|
226
242
|
/** `describeRule` match-dimension tokens by language. */
|
|
227
243
|
export const DESCRIBE_TOKENS: Readonly<Record<UiLanguage, DescribeTokens>> = {
|
|
228
|
-
en: { allTools: 'all tools', tools: 'tools', agents: 'agents', params: 'params', paths: 'paths', absent: 'absent', when: 'when', platform: 'platform', disabled: 'disabled', tags: 'tags' },
|
|
229
|
-
zh: { allTools: '全部工具', tools: '工具', agents: '代理', params: '参数', paths: '路径', absent: '缺省键', when: '条件', platform: '平台', disabled: '已禁用', tags: '标签' },
|
|
230
|
-
es: { allTools: 'todas las herramientas', tools: 'herramientas', agents: 'agentes', params: 'parámetros', paths: 'rutas', absent: 'ausentes', when: 'cuándo', platform: 'plataforma', disabled: 'deshabilitada', tags: 'etiquetas' },
|
|
231
|
-
pt: { allTools: 'todas as ferramentas', tools: 'ferramentas', agents: 'agentes', params: 'parâmetros', paths: 'caminhos', absent: 'ausentes', when: 'quando', platform: 'plataforma', disabled: 'desativada', tags: 'etiquetas' },
|
|
232
|
-
hi: { allTools: 'सभी टूल', tools: 'टूल', agents: 'एजेंट', params: 'पैरामीटर', paths: 'पथ', absent: 'अनुपस्थित', when: 'शर्त', platform: 'प्लेटफ़ॉर्म', disabled: 'अक्षम', tags: 'टैग' },
|
|
244
|
+
en: { allTools: 'all tools', tools: 'tools', agents: 'agents', params: 'params', paths: 'paths', absent: 'absent', when: 'when', platform: 'platform', disabled: 'disabled', tags: 'tags', src: 'src' },
|
|
245
|
+
zh: { allTools: '全部工具', tools: '工具', agents: '代理', params: '参数', paths: '路径', absent: '缺省键', when: '条件', platform: '平台', disabled: '已禁用', tags: '标签', src: '来源' },
|
|
246
|
+
es: { allTools: 'todas las herramientas', tools: 'herramientas', agents: 'agentes', params: 'parámetros', paths: 'rutas', absent: 'ausentes', when: 'cuándo', platform: 'plataforma', disabled: 'deshabilitada', tags: 'etiquetas', src: 'origen' },
|
|
247
|
+
pt: { allTools: 'todas as ferramentas', tools: 'ferramentas', agents: 'agentes', params: 'parâmetros', paths: 'caminhos', absent: 'ausentes', when: 'quando', platform: 'plataforma', disabled: 'desativada', tags: 'etiquetas', src: 'origem' },
|
|
248
|
+
hi: { allTools: 'सभी टूल', tools: 'टूल', agents: 'एजेंट', params: 'पैरामीटर', paths: 'पथ', absent: 'अनुपस्थित', when: 'शर्त', platform: 'प्लेटफ़ॉर्म', disabled: 'अक्षम', tags: 'टैग', src: 'स्रोत' },
|
|
233
249
|
}
|
package/src/rules.ts
CHANGED
|
@@ -321,12 +321,16 @@ function isCatchAll(rule: CompiledRule): boolean {
|
|
|
321
321
|
/**
|
|
322
322
|
* One-line human summary of a compiled rule, for `/rules` output. The
|
|
323
323
|
* 1-based number, action, match dimensions, and reason render first; a
|
|
324
|
-
* disabled marker, tags, and a truncated description follow.
|
|
324
|
+
* disabled marker, tags, and a truncated description follow. When `source`
|
|
325
|
+
* is provided (multi-file chains), the rule's origin file is attributed on
|
|
326
|
+
* the line.
|
|
325
327
|
* @param rule - the compiled rule.
|
|
326
328
|
* @param tokens - dimension-prefix vocabulary for the configured language.
|
|
329
|
+
* @param source - display path of the rule's source file, when the listing
|
|
330
|
+
* spans several files.
|
|
327
331
|
* @returns a single display line.
|
|
328
332
|
*/
|
|
329
|
-
export function describeRule(rule: CompiledRule, tokens: DescribeTokens): string {
|
|
333
|
+
export function describeRule(rule: CompiledRule, tokens: DescribeTokens, source?: string): string {
|
|
330
334
|
const parts: string[] = []
|
|
331
335
|
if (rule.source.match.tools.length > 0) parts.push(`${tokens.tools}:${rule.source.match.tools.join(',')}`)
|
|
332
336
|
if (rule.source.match.agents.length > 0) parts.push(`${tokens.agents}:${rule.source.match.agents.join(',')}`)
|
|
@@ -343,9 +347,10 @@ export function describeRule(rule: CompiledRule, tokens: DescribeTokens): string
|
|
|
343
347
|
if (whenParts.length > 0 || platformPart.length > 0) parts.push(`${tokens.when}:${whenParts}${whenParts.length > 0 && platformPart.length > 0 ? ' ' : ''}${platformPart}`)
|
|
344
348
|
const match = parts.length > 0 ? `[${parts.join(' ')}]` : `[${tokens.allTools}]`
|
|
345
349
|
const disabled = rule.enabled ? '' : ` (${tokens.disabled})`
|
|
350
|
+
const sourcePart = source !== undefined && source.length > 0 ? ` [${tokens.src}:${source}]` : ''
|
|
346
351
|
const description = rule.description !== undefined ? ` (${truncate(rule.description, DISPLAY_TRUNCATE)})` : ''
|
|
347
352
|
const tags = rule.tags.length > 0 ? ` [${tokens.tags}:${rule.tags.join(',')}]` : ''
|
|
348
|
-
return `${rule.index + 1}. ${rule.action}${disabled} ${match}: ${truncate(rule.reason, DISPLAY_TRUNCATE)}${description}${tags}`
|
|
353
|
+
return `${rule.index + 1}. ${rule.action}${disabled} ${match}${sourcePart}: ${truncate(rule.reason, DISPLAY_TRUNCATE)}${description}${tags}`
|
|
349
354
|
}
|
|
350
355
|
|
|
351
356
|
/** The localized dimension-prefix vocabulary {@link describeRule} renders. */
|
|
@@ -360,6 +365,8 @@ export interface DescribeTokens {
|
|
|
360
365
|
readonly platform: string
|
|
361
366
|
readonly disabled: string
|
|
362
367
|
readonly tags: string
|
|
368
|
+
/** Prefix of the per-rule source-file attribution. */
|
|
369
|
+
readonly src: string
|
|
363
370
|
}
|
|
364
371
|
|
|
365
372
|
/** Cut a display string at `limit` characters, marking truncation with `…`. */
|