ark-runtime-kernel 1.2.0 → 1.4.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/README.md +132 -8
- package/bin/ark-check.mjs +355 -48
- package/bin/ark-mcp.mjs +147 -3
- package/bin/ark-shared.mjs +53 -1
- package/dist/eslint/index.cjs +49 -1
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +4 -1
- package/dist/eslint/index.d.ts +4 -1
- package/dist/eslint/index.js +49 -2
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +50 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/nestjs/index.js.map +1 -1
- package/docs/agent-guide.md +9 -0
- package/docs/ai-gates.md +76 -0
- package/package.json +8 -2
- package/server.json +7 -7
- package/docs/blog/how-i-stopped-claude-from-breaking-my-architecture.md +0 -85
package/bin/ark-mcp.mjs
CHANGED
|
@@ -21,11 +21,21 @@
|
|
|
21
21
|
* stdin, validates the file content a Write/Edit/MultiEdit is about to produce, and exits
|
|
22
22
|
* 2 with the violations on stderr when the write must be blocked (0 otherwise). This is
|
|
23
23
|
* the copy-paste integration for agent runtimes whose hooks run shell commands.
|
|
24
|
+
*
|
|
25
|
+
* --session-context runs one-shot and prints a compact contract summary (layers, rule
|
|
26
|
+
* count, forbidden globals, baseline state, check command) to stdout. Bind it to a
|
|
27
|
+
* SessionStart hook so the agent has the architecture in context from the first token,
|
|
28
|
+
* instead of learning it by rejection.
|
|
24
29
|
*/
|
|
25
30
|
import fs from 'node:fs';
|
|
26
31
|
import path from 'node:path';
|
|
27
32
|
import readline from 'node:readline';
|
|
28
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
DEFAULT_INTENT_PREFIXES,
|
|
35
|
+
DEFAULT_LAYER_DIRECTORIES,
|
|
36
|
+
DEFAULT_RULES,
|
|
37
|
+
layerForFile,
|
|
38
|
+
} from './ark-shared.mjs';
|
|
29
39
|
|
|
30
40
|
function parseArgs(argv) {
|
|
31
41
|
const args = {
|
|
@@ -34,10 +44,12 @@ function parseArgs(argv) {
|
|
|
34
44
|
configExplicit: false,
|
|
35
45
|
manifest: undefined,
|
|
36
46
|
hook: false,
|
|
47
|
+
sessionContext: false,
|
|
37
48
|
};
|
|
38
49
|
for (let i = 2; i < argv.length; i += 1) {
|
|
39
50
|
const a = argv[i];
|
|
40
51
|
if (a === '--hook') args.hook = true;
|
|
52
|
+
else if (a === '--session-context') args.sessionContext = true;
|
|
41
53
|
else if (a === '--root') args.root = path.resolve(argv[++i]);
|
|
42
54
|
else if (a === '--config') {
|
|
43
55
|
args.config = argv[++i];
|
|
@@ -163,7 +175,27 @@ function runHook(gate, config, args) {
|
|
|
163
175
|
const result = gate.validate(source, { layer, filePath });
|
|
164
176
|
if (result.valid) return;
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
// Ratchet semantics (same philosophy as ark-check --baseline): an edit is blocked only
|
|
179
|
+
// when it ADDS violations relative to the file's current on-disk state. Otherwise a
|
|
180
|
+
// pre-existing violation — frozen in a baseline or predating Ark adoption — would make
|
|
181
|
+
// every subsequent edit to that file un-writable while CI passes. Keys ignore line
|
|
182
|
+
// numbers (edits shift them) and collapse duplicates, mirroring ark-check's baselineKey.
|
|
183
|
+
const violationKey = (violation) => `${violation.ruleId}|${violation.target ?? violation.message}`;
|
|
184
|
+
let existingKeys = new Set();
|
|
185
|
+
try {
|
|
186
|
+
const current = fs.readFileSync(filePath, 'utf8');
|
|
187
|
+
existingKeys = new Set(
|
|
188
|
+
gate.validate(current, { layer, filePath }).violations.map(violationKey)
|
|
189
|
+
);
|
|
190
|
+
} catch {
|
|
191
|
+
// New file: nothing pre-exists, every violation is new.
|
|
192
|
+
}
|
|
193
|
+
const newViolations = result.violations.filter(
|
|
194
|
+
(violation) => !existingKeys.has(violationKey(violation))
|
|
195
|
+
);
|
|
196
|
+
if (newViolations.length === 0) return;
|
|
197
|
+
|
|
198
|
+
const lines = newViolations.map(
|
|
167
199
|
(violation) =>
|
|
168
200
|
`- [${violation.ruleId}] ${violation.message}${violation.line ? ` (line ${violation.line})` : ''}`
|
|
169
201
|
);
|
|
@@ -177,12 +209,65 @@ function runHook(gate, config, args) {
|
|
|
177
209
|
process.exitCode = 2;
|
|
178
210
|
}
|
|
179
211
|
|
|
212
|
+
/**
|
|
213
|
+
* One-shot SessionStart context: a compact summary of the contract on stdout so the
|
|
214
|
+
* agent starts the session already knowing the architecture. Advisory — never blocks
|
|
215
|
+
* and never exits non-zero for missing optional inputs (e.g. no baseline file).
|
|
216
|
+
*/
|
|
217
|
+
function printSessionContext(config, profile, forbiddenGlobals, args) {
|
|
218
|
+
const lines = ['Ark architecture contract governs this project (ark.config.json is authoritative).'];
|
|
219
|
+
|
|
220
|
+
const configLayers = Array.isArray(config.layers) ? config.layers : [];
|
|
221
|
+
if (configLayers.length > 0) {
|
|
222
|
+
lines.push('Layers:');
|
|
223
|
+
for (const layer of configLayers) {
|
|
224
|
+
const globals = forbiddenGlobals[layer.name];
|
|
225
|
+
const globalsNote = globals ? ` — forbidden globals: ${globals.join(', ')}` : '';
|
|
226
|
+
lines.push(` - ${layer.name}: ${(layer.patterns ?? []).join(', ')}${globalsNote}`);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
lines.push(
|
|
230
|
+
`Layers: none configured — the default 11-layer profile applies to intent references.`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const denied = (profile.rules ?? []).filter((rule) => !rule.allowed).length;
|
|
235
|
+
lines.push(
|
|
236
|
+
`Rules: ${denied} denied layer edge(s). Full contract: ark://manifest MCP resource.`
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Advisory output: a malformed baseline must not abort the summary.
|
|
240
|
+
let baseline;
|
|
241
|
+
try {
|
|
242
|
+
baseline = readJson(path.join(args.root, '.ark-baseline.json'));
|
|
243
|
+
} catch {
|
|
244
|
+
baseline = undefined;
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(baseline?.violations)) {
|
|
247
|
+
lines.push(
|
|
248
|
+
`Baseline: ${baseline.violations.length} frozen violation(s) — only NEW violations fail; do not add to them.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
lines.push('After edits run: npx ark-check --root . --config ark.config.json --strict-config');
|
|
253
|
+
lines.push('If Ark reports violations, fix the architecture instead of weakening the gate.');
|
|
254
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
255
|
+
}
|
|
256
|
+
|
|
180
257
|
async function main() {
|
|
181
258
|
const args = parseArgs(process.argv);
|
|
259
|
+
const configPath = resolveInRoot(args.root, args.config);
|
|
260
|
+
|
|
261
|
+
// SessionStart contract injection is only meaningful in Ark-governed projects. Bail
|
|
262
|
+
// out silently (before loading dist) when there is no config, so the hook is safe
|
|
263
|
+
// even if a user installs it in their GLOBAL settings instead of per-project.
|
|
264
|
+
if (args.sessionContext && !(configPath && fs.existsSync(configPath))) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
182
268
|
const ark = await loadArk();
|
|
183
269
|
const ts = await loadOptionalTypeScript();
|
|
184
270
|
|
|
185
|
-
const configPath = resolveInRoot(args.root, args.config);
|
|
186
271
|
const config =
|
|
187
272
|
(configPath ? readJson(configPath, { required: args.configExplicit }) : undefined) ?? {
|
|
188
273
|
include: ['src'],
|
|
@@ -243,11 +328,28 @@ async function main() {
|
|
|
243
328
|
});
|
|
244
329
|
}
|
|
245
330
|
|
|
331
|
+
// Layer → forbidden ambient globals, straight from ark.config.json. Enforced by the
|
|
332
|
+
// gate only when the target file's layer is known (same data ark-check enforces in CI).
|
|
333
|
+
const forbiddenGlobals = Object.fromEntries(
|
|
334
|
+
configLayers
|
|
335
|
+
.filter(
|
|
336
|
+
(layer) =>
|
|
337
|
+
layer.name &&
|
|
338
|
+
Array.isArray(layer.forbiddenGlobals) &&
|
|
339
|
+
layer.forbiddenGlobals.some((entry) => typeof entry === 'string')
|
|
340
|
+
)
|
|
341
|
+
.map((layer) => [
|
|
342
|
+
layer.name,
|
|
343
|
+
layer.forbiddenGlobals.filter((entry) => typeof entry === 'string'),
|
|
344
|
+
])
|
|
345
|
+
);
|
|
346
|
+
|
|
246
347
|
const gate = ark.createAICodeGate({
|
|
247
348
|
architectureProfile: profile,
|
|
248
349
|
intents,
|
|
249
350
|
enforceIntentAllowlist: intents.length > 0,
|
|
250
351
|
typescript: ts,
|
|
352
|
+
forbiddenGlobals,
|
|
251
353
|
});
|
|
252
354
|
|
|
253
355
|
if (args.hook) {
|
|
@@ -255,6 +357,11 @@ async function main() {
|
|
|
255
357
|
return;
|
|
256
358
|
}
|
|
257
359
|
|
|
360
|
+
if (args.sessionContext) {
|
|
361
|
+
printSessionContext(config, profile, forbiddenGlobals, args);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
258
365
|
const SERVER_INFO = { name: 'ark-runtime-kernel', version: ark.version };
|
|
259
366
|
const DEFAULT_PROTOCOL = '2024-11-05';
|
|
260
367
|
|
|
@@ -297,6 +404,31 @@ async function main() {
|
|
|
297
404
|
},
|
|
298
405
|
];
|
|
299
406
|
|
|
407
|
+
// Layers from the 11-layer profile that this project has NOT declared, with their
|
|
408
|
+
// conventional directories: tells the agent where a new kind of code (a saga, a job,
|
|
409
|
+
// a read model, ...) belongs BEFORE it improvises a location the gate can't govern.
|
|
410
|
+
// A default layer is dropped when the project already claims any of its intent
|
|
411
|
+
// prefixes under another name (e.g. a `core` layer owning `Domain.`) — suggesting
|
|
412
|
+
// DomainModel there would tell the agent to create a second layer for the same
|
|
413
|
+
// prefix, making longest-prefix resolution ambiguous.
|
|
414
|
+
function suggestedLayers() {
|
|
415
|
+
const activeNames = new Set(profile.layers.map((layer) => layer.name));
|
|
416
|
+
const claimedPrefixes = new Set(
|
|
417
|
+
profile.layers.flatMap((layer) =>
|
|
418
|
+
(layer.prefixes ?? []).map((p) => (p.endsWith('.') ? p : `${p}.`))
|
|
419
|
+
)
|
|
420
|
+
);
|
|
421
|
+
return DEFAULT_INTENT_PREFIXES.filter(
|
|
422
|
+
(entry) =>
|
|
423
|
+
!activeNames.has(entry.layer) &&
|
|
424
|
+
!entry.prefixes.some((p) => claimedPrefixes.has(p.endsWith('.') ? p : `${p}.`))
|
|
425
|
+
).map((entry) => ({
|
|
426
|
+
layer: entry.layer,
|
|
427
|
+
intentPrefixes: entry.prefixes,
|
|
428
|
+
conventionalDirectories: DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? [],
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
|
|
300
432
|
function manifestText() {
|
|
301
433
|
if (projectManifest) {
|
|
302
434
|
return JSON.stringify(
|
|
@@ -305,12 +437,24 @@ async function main() {
|
|
|
305
437
|
2
|
|
306
438
|
);
|
|
307
439
|
}
|
|
440
|
+
const suggestions = suggestedLayers();
|
|
308
441
|
return JSON.stringify(
|
|
309
442
|
{
|
|
310
443
|
source: profile === ark.elevenLayerProfile ? 'strictDefaultElevenLayerProfile' : 'project',
|
|
311
444
|
name: profile.name,
|
|
312
445
|
layers: profile.layers,
|
|
313
446
|
rules: profile.rules,
|
|
447
|
+
...(Object.keys(forbiddenGlobals).length > 0 ? { forbiddenGlobals } : {}),
|
|
448
|
+
...(suggestions.length > 0
|
|
449
|
+
? {
|
|
450
|
+
suggestedLayers: suggestions,
|
|
451
|
+
suggestedLayersNote:
|
|
452
|
+
'Layers from the default 11-layer profile this project has not declared. ' +
|
|
453
|
+
'When creating a NEW kind of code that fits one of these, place it in a ' +
|
|
454
|
+
'conventional directory and add the layer to ark.config.json instead of ' +
|
|
455
|
+
'inventing an ungoverned location.',
|
|
456
|
+
}
|
|
457
|
+
: {}),
|
|
314
458
|
},
|
|
315
459
|
null,
|
|
316
460
|
2
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -69,23 +69,75 @@ export const DEFAULT_RULES = createStrictDenyRules(
|
|
|
69
69
|
DEFAULT_ALLOWED_FLOWS
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Default ambient globals forbidden in the domain layer: a pure domain does no I/O and is
|
|
74
|
+
* deterministic. `console` is deliberately omitted (too common during adoption); add it per
|
|
75
|
+
* project via the layer's `forbiddenGlobals` in ark.config.json.
|
|
76
|
+
*/
|
|
77
|
+
export const DEFAULT_DOMAIN_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];
|
|
78
|
+
|
|
72
79
|
export function createElevenLayerConfig(options = {}) {
|
|
73
80
|
const rootDir = options.rootDir ?? 'src';
|
|
74
81
|
const optional = options.optionalLayers ?? true;
|
|
82
|
+
const prefix = rootDir === '.' ? '' : `${rootDir}/`;
|
|
75
83
|
return {
|
|
76
84
|
include: options.include ?? [rootDir],
|
|
77
85
|
layers: DEFAULT_INTENT_PREFIXES.map((entry) => ({
|
|
78
86
|
name: entry.layer,
|
|
79
87
|
patterns: (DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? [entry.layer]).map(
|
|
80
|
-
(directory) => `${
|
|
88
|
+
(directory) => `${prefix}${directory}/**`
|
|
81
89
|
),
|
|
82
90
|
intentPrefixes: entry.prefixes,
|
|
83
91
|
optional,
|
|
92
|
+
...(entry.layer === 'DomainModel'
|
|
93
|
+
? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
|
|
94
|
+
: {}),
|
|
84
95
|
})),
|
|
85
96
|
rules: DEFAULT_RULES,
|
|
86
97
|
};
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Find uses of forbidden ambient globals in a TypeScript source file.
|
|
102
|
+
*
|
|
103
|
+
* Detection is deliberately positional, not scope-aware (kept in sync with
|
|
104
|
+
* `collectForbiddenGlobalUses` in src/kernel/ai-gate/AICodeGate.ts — the CLIs must not
|
|
105
|
+
* import from dist):
|
|
106
|
+
* - a dotted entry ("Date.now") flags `Date.now` property accesses
|
|
107
|
+
* - a bare entry ("console", "fetch") flags property accesses on it (`console.log`),
|
|
108
|
+
* direct calls (`fetch(...)`), and constructions (`new WebSocket(...)`)
|
|
109
|
+
* Bare identifier mentions in other positions (types, shadowed locals, import names) are
|
|
110
|
+
* NOT flagged, trading a little recall for near-zero false positives without a type checker.
|
|
111
|
+
*
|
|
112
|
+
* Returns [{ name, node }] where `name` is the matched forbidden entry.
|
|
113
|
+
*/
|
|
114
|
+
export function collectForbiddenGlobalUses(ts, sourceFile, forbidden) {
|
|
115
|
+
const entries = new Set(forbidden ?? []);
|
|
116
|
+
if (entries.size === 0) return [];
|
|
117
|
+
const uses = [];
|
|
118
|
+
|
|
119
|
+
const visit = (node) => {
|
|
120
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
121
|
+
const dotted = `${node.expression.text}.${node.name.text}`;
|
|
122
|
+
if (entries.has(dotted)) {
|
|
123
|
+
uses.push({ name: dotted, node });
|
|
124
|
+
} else if (entries.has(node.expression.text)) {
|
|
125
|
+
uses.push({ name: node.expression.text, node });
|
|
126
|
+
}
|
|
127
|
+
} else if (
|
|
128
|
+
(ts.isCallExpression(node) || ts.isNewExpression(node)) &&
|
|
129
|
+
node.expression &&
|
|
130
|
+
ts.isIdentifier(node.expression) &&
|
|
131
|
+
entries.has(node.expression.text)
|
|
132
|
+
) {
|
|
133
|
+
uses.push({ name: node.expression.text, node });
|
|
134
|
+
}
|
|
135
|
+
ts.forEachChild(node, visit);
|
|
136
|
+
};
|
|
137
|
+
visit(sourceFile);
|
|
138
|
+
return uses;
|
|
139
|
+
}
|
|
140
|
+
|
|
89
141
|
const _regexpCache = new Map();
|
|
90
142
|
|
|
91
143
|
function escapeLiteral(ch) {
|
package/dist/eslint/index.cjs
CHANGED
|
@@ -122,10 +122,57 @@ var requirePublishSource = {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
};
|
|
125
|
+
var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
|
|
126
|
+
var noForbiddenGlobals = {
|
|
127
|
+
meta: {
|
|
128
|
+
type: "problem",
|
|
129
|
+
docs: {
|
|
130
|
+
description: 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint "files" patterns.'
|
|
131
|
+
},
|
|
132
|
+
messages: {
|
|
133
|
+
forbiddenGlobal: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
|
|
134
|
+
},
|
|
135
|
+
schema: [
|
|
136
|
+
{
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
globals: { type: "array", items: { type: "string" } }
|
|
140
|
+
},
|
|
141
|
+
additionalProperties: false
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
create(context) {
|
|
146
|
+
const option = context.options?.[0];
|
|
147
|
+
const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);
|
|
148
|
+
const report = (node, name) => context.report({ node, messageId: "forbiddenGlobal", data: { name } });
|
|
149
|
+
return {
|
|
150
|
+
// Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a
|
|
151
|
+
// forbidden base (console.log, Date.now), direct calls, and constructions. Bare
|
|
152
|
+
// identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).
|
|
153
|
+
MemberExpression(node) {
|
|
154
|
+
const base = node.object?.type === "Identifier" ? node.object.name : void 0;
|
|
155
|
+
if (!base) return;
|
|
156
|
+
const dotted = `${base}.${propertyName(node.property) ?? ""}`;
|
|
157
|
+
if (globals.has(dotted)) report(node, dotted);
|
|
158
|
+
else if (globals.has(base)) report(node, base);
|
|
159
|
+
},
|
|
160
|
+
CallExpression(node) {
|
|
161
|
+
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
162
|
+
if (callee && globals.has(callee)) report(node, callee);
|
|
163
|
+
},
|
|
164
|
+
NewExpression(node) {
|
|
165
|
+
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
166
|
+
if (callee && globals.has(callee)) report(node, callee);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
};
|
|
125
171
|
var rules = {
|
|
126
172
|
"no-domain-infra-imports": noDomainInfraImports,
|
|
127
173
|
"no-raw-event-publish": noRawEventPublish,
|
|
128
|
-
"require-publish-source": requirePublishSource
|
|
174
|
+
"require-publish-source": requirePublishSource,
|
|
175
|
+
"no-forbidden-globals": noForbiddenGlobals
|
|
129
176
|
};
|
|
130
177
|
var plugin = { rules };
|
|
131
178
|
plugin.configs = {
|
|
@@ -142,6 +189,7 @@ var eslint_default = plugin;
|
|
|
142
189
|
|
|
143
190
|
exports.default = eslint_default;
|
|
144
191
|
exports.noDomainInfraImports = noDomainInfraImports;
|
|
192
|
+
exports.noForbiddenGlobals = noForbiddenGlobals;
|
|
145
193
|
exports.noRawEventPublish = noRawEventPublish;
|
|
146
194
|
exports.plugin = plugin;
|
|
147
195
|
exports.requirePublishSource = requirePublishSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\n};\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(value);\n}\n\nfunction isDomainFile(context: RuleContext): boolean {\n const filename = context.getFilename?.() ?? '';\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImport(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow importing infrastructure or adapters from domain files.',\n },\n messages: {\n forbiddenImport: 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const check = (node: AstNode) => {\n if (!isDomainFile(context)) return;\n const source = stringValue(node.source);\n if (source && isInfraImport(source)) {\n context.report({ node, messageId: 'forbiddenImport' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish: 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if (\n firstValue && looksLikeIntent(firstValue) ||\n objectHasProperty(firstArg, 'intent')\n ) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\n options?: unknown[];\n};\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n object?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(value);\n}\n\nfunction isDomainFile(context: RuleContext): boolean {\n const filename = context.getFilename?.() ?? '';\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImport(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow importing infrastructure or adapters from domain files.',\n },\n messages: {\n forbiddenImport: 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const check = (node: AstNode) => {\n if (!isDomainFile(context)) return;\n const source = stringValue(node.source);\n if (source && isInfraImport(source)) {\n context.report({ node, messageId: 'forbiddenImport' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish: 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if (\n firstValue && looksLikeIntent(firstValue) ||\n objectHasProperty(firstArg, 'intent')\n ) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nconst DEFAULT_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];\n\nexport const noForbiddenGlobals: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint \"files\" patterns.',\n },\n messages: {\n forbiddenGlobal: 'Ambient global \"{{name}}\" is forbidden here; inject the capability through a port instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n globals: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const option = context.options?.[0] as { globals?: string[] } | undefined;\n const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);\n const report = (node: AstNode, name: string) =>\n context.report({ node, messageId: 'forbiddenGlobal', data: { name } });\n\n return {\n // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a\n // forbidden base (console.log, Date.now), direct calls, and constructions. Bare\n // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).\n MemberExpression(node) {\n const base = node.object?.type === 'Identifier' ? node.object.name : undefined;\n if (!base) return;\n const dotted = `${base}.${propertyName(node.property) ?? ''}`;\n if (globals.has(dotted)) report(node, dotted);\n else if (globals.has(base)) report(node, base);\n },\n CallExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals.has(callee)) report(node, callee);\n },\n NewExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals.has(callee)) report(node, callee);\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n 'no-forbidden-globals': noForbiddenGlobals,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n"]}
|
package/dist/eslint/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
type RuleContext = {
|
|
2
2
|
report(descriptor: Record<string, unknown>): void;
|
|
3
3
|
getFilename?: () => string;
|
|
4
|
+
options?: unknown[];
|
|
4
5
|
};
|
|
5
6
|
type RuleListener = Record<string, (node: AstNode) => void>;
|
|
6
7
|
type AstNode = {
|
|
@@ -9,6 +10,7 @@ type AstNode = {
|
|
|
9
10
|
value?: unknown;
|
|
10
11
|
source?: AstNode;
|
|
11
12
|
callee?: AstNode;
|
|
13
|
+
object?: AstNode;
|
|
12
14
|
property?: AstNode;
|
|
13
15
|
key?: AstNode;
|
|
14
16
|
arguments?: AstNode[];
|
|
@@ -32,6 +34,7 @@ type ArkEslintPlugin = {
|
|
|
32
34
|
declare const noDomainInfraImports: ArkRule;
|
|
33
35
|
declare const noRawEventPublish: ArkRule;
|
|
34
36
|
declare const requirePublishSource: ArkRule;
|
|
37
|
+
declare const noForbiddenGlobals: ArkRule;
|
|
35
38
|
declare const plugin: ArkEslintPlugin;
|
|
36
39
|
|
|
37
|
-
export { plugin as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
|
|
40
|
+
export { plugin as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
type RuleContext = {
|
|
2
2
|
report(descriptor: Record<string, unknown>): void;
|
|
3
3
|
getFilename?: () => string;
|
|
4
|
+
options?: unknown[];
|
|
4
5
|
};
|
|
5
6
|
type RuleListener = Record<string, (node: AstNode) => void>;
|
|
6
7
|
type AstNode = {
|
|
@@ -9,6 +10,7 @@ type AstNode = {
|
|
|
9
10
|
value?: unknown;
|
|
10
11
|
source?: AstNode;
|
|
11
12
|
callee?: AstNode;
|
|
13
|
+
object?: AstNode;
|
|
12
14
|
property?: AstNode;
|
|
13
15
|
key?: AstNode;
|
|
14
16
|
arguments?: AstNode[];
|
|
@@ -32,6 +34,7 @@ type ArkEslintPlugin = {
|
|
|
32
34
|
declare const noDomainInfraImports: ArkRule;
|
|
33
35
|
declare const noRawEventPublish: ArkRule;
|
|
34
36
|
declare const requirePublishSource: ArkRule;
|
|
37
|
+
declare const noForbiddenGlobals: ArkRule;
|
|
35
38
|
declare const plugin: ArkEslintPlugin;
|
|
36
39
|
|
|
37
|
-
export { plugin as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
|
|
40
|
+
export { plugin as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
|
package/dist/eslint/index.js
CHANGED
|
@@ -118,10 +118,57 @@ var requirePublishSource = {
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
|
+
var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
|
|
122
|
+
var noForbiddenGlobals = {
|
|
123
|
+
meta: {
|
|
124
|
+
type: "problem",
|
|
125
|
+
docs: {
|
|
126
|
+
description: 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint "files" patterns.'
|
|
127
|
+
},
|
|
128
|
+
messages: {
|
|
129
|
+
forbiddenGlobal: 'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'
|
|
130
|
+
},
|
|
131
|
+
schema: [
|
|
132
|
+
{
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
globals: { type: "array", items: { type: "string" } }
|
|
136
|
+
},
|
|
137
|
+
additionalProperties: false
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
create(context) {
|
|
142
|
+
const option = context.options?.[0];
|
|
143
|
+
const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);
|
|
144
|
+
const report = (node, name) => context.report({ node, messageId: "forbiddenGlobal", data: { name } });
|
|
145
|
+
return {
|
|
146
|
+
// Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a
|
|
147
|
+
// forbidden base (console.log, Date.now), direct calls, and constructions. Bare
|
|
148
|
+
// identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).
|
|
149
|
+
MemberExpression(node) {
|
|
150
|
+
const base = node.object?.type === "Identifier" ? node.object.name : void 0;
|
|
151
|
+
if (!base) return;
|
|
152
|
+
const dotted = `${base}.${propertyName(node.property) ?? ""}`;
|
|
153
|
+
if (globals.has(dotted)) report(node, dotted);
|
|
154
|
+
else if (globals.has(base)) report(node, base);
|
|
155
|
+
},
|
|
156
|
+
CallExpression(node) {
|
|
157
|
+
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
158
|
+
if (callee && globals.has(callee)) report(node, callee);
|
|
159
|
+
},
|
|
160
|
+
NewExpression(node) {
|
|
161
|
+
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
162
|
+
if (callee && globals.has(callee)) report(node, callee);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
121
167
|
var rules = {
|
|
122
168
|
"no-domain-infra-imports": noDomainInfraImports,
|
|
123
169
|
"no-raw-event-publish": noRawEventPublish,
|
|
124
|
-
"require-publish-source": requirePublishSource
|
|
170
|
+
"require-publish-source": requirePublishSource,
|
|
171
|
+
"no-forbidden-globals": noForbiddenGlobals
|
|
125
172
|
};
|
|
126
173
|
var plugin = { rules };
|
|
127
174
|
plugin.configs = {
|
|
@@ -136,6 +183,6 @@ plugin.configs = {
|
|
|
136
183
|
};
|
|
137
184
|
var eslint_default = plugin;
|
|
138
185
|
|
|
139
|
-
export { eslint_default as default, noDomainInfraImports, noRawEventPublish, plugin, requirePublishSource };
|
|
186
|
+
export { eslint_default as default, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, plugin, requirePublishSource };
|
|
140
187
|
//# sourceMappingURL=index.js.map
|
|
141
188
|
//# sourceMappingURL=index.js.map
|
package/dist/eslint/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\n};\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(value);\n}\n\nfunction isDomainFile(context: RuleContext): boolean {\n const filename = context.getFilename?.() ?? '';\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImport(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow importing infrastructure or adapters from domain files.',\n },\n messages: {\n forbiddenImport: 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const check = (node: AstNode) => {\n if (!isDomainFile(context)) return;\n const source = stringValue(node.source);\n if (source && isInfraImport(source)) {\n context.report({ node, messageId: 'forbiddenImport' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish: 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if (\n firstValue && looksLikeIntent(firstValue) ||\n objectHasProperty(firstArg, 'intent')\n ) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/eslint/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["type RuleContext = {\n report(descriptor: Record<string, unknown>): void;\n getFilename?: () => string;\n options?: unknown[];\n};\n\ntype RuleListener = Record<string, (node: AstNode) => void>;\n\ntype AstNode = {\n type?: string;\n name?: string;\n value?: unknown;\n source?: AstNode;\n callee?: AstNode;\n object?: AstNode;\n property?: AstNode;\n key?: AstNode;\n arguments?: AstNode[];\n properties?: AstNode[];\n};\n\ntype ArkRule = {\n meta: {\n type: 'problem';\n docs: { description: string };\n messages: Record<string, string>;\n schema: unknown[];\n };\n create(context: RuleContext): RuleListener;\n};\n\ntype ArkEslintPlugin = {\n rules: Record<string, ArkRule>;\n configs?: Record<string, unknown>;\n};\n\nfunction stringValue(node: AstNode | undefined): string | undefined {\n return typeof node?.value === 'string' ? node.value : undefined;\n}\n\nfunction propertyName(node: AstNode | undefined): string | undefined {\n return node?.name ?? stringValue(node);\n}\n\nfunction calleePropertyName(node: AstNode): string | undefined {\n return propertyName(node.callee?.property);\n}\n\nfunction objectProperty(node: AstNode | undefined, name: string): AstNode | undefined {\n return node?.properties?.find((property) => propertyName(property.key) === name);\n}\n\nfunction objectHasProperty(node: AstNode | undefined, name: string): boolean {\n return objectProperty(node, name) !== undefined;\n}\n\nfunction objectHasMetadataSource(node: AstNode | undefined): boolean {\n const metadata = objectProperty(node, 'metadata')?.value as AstNode | undefined;\n return objectHasProperty(metadata, 'source');\n}\n\nfunction looksLikeIntent(value: string): boolean {\n return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\\.[A-Za-z0-9_.]+$/.test(value);\n}\n\nfunction isDomainFile(context: RuleContext): boolean {\n const filename = context.getFilename?.() ?? '';\n const normalized = filename.split('\\\\').join('/').toLowerCase();\n return normalized.includes('/domain/') || normalized.endsWith('/domain.ts');\n}\n\nfunction isInfraImport(specifier: string): boolean {\n const normalized = specifier.toLowerCase();\n return [\n 'adapter',\n 'adapters',\n 'infrastructure',\n 'persistence',\n 'repository',\n 'repositories',\n 'integration',\n 'database',\n 'db',\n ].some((token) => normalized.includes(token));\n}\n\nfunction isPublishCall(node: AstNode): boolean {\n return calleePropertyName(node) === 'publish';\n}\n\nexport const noDomainInfraImports: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow importing infrastructure or adapters from domain files.',\n },\n messages: {\n forbiddenImport: 'Domain code must not import infrastructure, adapters, repositories, or database modules.',\n },\n schema: [],\n },\n create(context) {\n const check = (node: AstNode) => {\n if (!isDomainFile(context)) return;\n const source = stringValue(node.source);\n if (source && isInfraImport(source)) {\n context.report({ node, messageId: 'forbiddenImport' });\n }\n };\n\n return {\n ImportDeclaration: check,\n ExportNamedDeclaration: check,\n ExportAllDeclaration: check,\n };\n },\n};\n\nexport const noRawEventPublish: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings.',\n },\n messages: {\n rawPublish: 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const firstValue = stringValue(firstArg);\n if (\n firstValue && looksLikeIntent(firstValue) ||\n objectHasProperty(firstArg, 'intent')\n ) {\n context.report({ node, messageId: 'rawPublish' });\n }\n },\n };\n },\n};\n\nexport const requirePublishSource: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require event bus publish calls to include source metadata.',\n },\n messages: {\n missingSource: 'Strict Ark publish calls must include metadata.source.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (!isPublishCall(node)) return;\n const firstArg = node.arguments?.[0];\n const metadataArg = node.arguments?.[2];\n if (objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, 'source')) {\n return;\n }\n context.report({ node, messageId: 'missingSource' });\n },\n };\n },\n};\n\nconst DEFAULT_FORBIDDEN_GLOBALS = ['fetch', 'process', 'Date.now', 'Math.random'];\n\nexport const noForbiddenGlobals: ArkRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow ambient globals (e.g. fetch, Date.now) in architecture-governed code; scope the rule to layer directories via ESLint \"files\" patterns.',\n },\n messages: {\n forbiddenGlobal: 'Ambient global \"{{name}}\" is forbidden here; inject the capability through a port instead.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n globals: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const option = context.options?.[0] as { globals?: string[] } | undefined;\n const globals = new Set(option?.globals ?? DEFAULT_FORBIDDEN_GLOBALS);\n const report = (node: AstNode, name: string) =>\n context.report({ node, messageId: 'forbiddenGlobal', data: { name } });\n\n return {\n // Same positional detection as ark-check's FORBIDDEN_GLOBAL: property accesses on a\n // forbidden base (console.log, Date.now), direct calls, and constructions. Bare\n // identifier mentions elsewhere are not flagged (avoids shadowed-local false positives).\n MemberExpression(node) {\n const base = node.object?.type === 'Identifier' ? node.object.name : undefined;\n if (!base) return;\n const dotted = `${base}.${propertyName(node.property) ?? ''}`;\n if (globals.has(dotted)) report(node, dotted);\n else if (globals.has(base)) report(node, base);\n },\n CallExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals.has(callee)) report(node, callee);\n },\n NewExpression(node) {\n const callee = node.callee?.type === 'Identifier' ? node.callee.name : undefined;\n if (callee && globals.has(callee)) report(node, callee);\n },\n };\n },\n};\n\nconst rules = {\n 'no-domain-infra-imports': noDomainInfraImports,\n 'no-raw-event-publish': noRawEventPublish,\n 'require-publish-source': requirePublishSource,\n 'no-forbidden-globals': noForbiddenGlobals,\n};\n\nconst plugin: ArkEslintPlugin = { rules };\n\nplugin.configs = {\n recommended: {\n plugins: { ark: plugin },\n rules: {\n 'ark/no-domain-infra-imports': 'error',\n 'ark/no-raw-event-publish': 'error',\n 'ark/require-publish-source': 'error',\n },\n },\n};\n\nexport { plugin };\nexport default plugin;\n"]}
|