ark-runtime-kernel 1.3.0 → 1.5.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 +97 -7
- package/bin/ark-check.mjs +244 -42
- package/bin/ark-mcp.mjs +105 -2
- package/bin/ark-shared.mjs +51 -0
- 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 +48 -1
- 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 +48 -1
- 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 -4
- package/docs/ai-gates.md +68 -0
- package/package.json +8 -2
- package/server.json +7 -7
package/bin/ark-mcp.mjs
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
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';
|
|
@@ -39,10 +44,12 @@ function parseArgs(argv) {
|
|
|
39
44
|
configExplicit: false,
|
|
40
45
|
manifest: undefined,
|
|
41
46
|
hook: false,
|
|
47
|
+
sessionContext: false,
|
|
42
48
|
};
|
|
43
49
|
for (let i = 2; i < argv.length; i += 1) {
|
|
44
50
|
const a = argv[i];
|
|
45
51
|
if (a === '--hook') args.hook = true;
|
|
52
|
+
else if (a === '--session-context') args.sessionContext = true;
|
|
46
53
|
else if (a === '--root') args.root = path.resolve(argv[++i]);
|
|
47
54
|
else if (a === '--config') {
|
|
48
55
|
args.config = argv[++i];
|
|
@@ -168,7 +175,27 @@ function runHook(gate, config, args) {
|
|
|
168
175
|
const result = gate.validate(source, { layer, filePath });
|
|
169
176
|
if (result.valid) return;
|
|
170
177
|
|
|
171
|
-
|
|
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(
|
|
172
199
|
(violation) =>
|
|
173
200
|
`- [${violation.ruleId}] ${violation.message}${violation.line ? ` (line ${violation.line})` : ''}`
|
|
174
201
|
);
|
|
@@ -182,12 +209,65 @@ function runHook(gate, config, args) {
|
|
|
182
209
|
process.exitCode = 2;
|
|
183
210
|
}
|
|
184
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
|
+
|
|
185
257
|
async function main() {
|
|
186
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
|
+
|
|
187
268
|
const ark = await loadArk();
|
|
188
269
|
const ts = await loadOptionalTypeScript();
|
|
189
270
|
|
|
190
|
-
const configPath = resolveInRoot(args.root, args.config);
|
|
191
271
|
const config =
|
|
192
272
|
(configPath ? readJson(configPath, { required: args.configExplicit }) : undefined) ?? {
|
|
193
273
|
include: ['src'],
|
|
@@ -248,11 +328,28 @@ async function main() {
|
|
|
248
328
|
});
|
|
249
329
|
}
|
|
250
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
|
+
|
|
251
347
|
const gate = ark.createAICodeGate({
|
|
252
348
|
architectureProfile: profile,
|
|
253
349
|
intents,
|
|
254
350
|
enforceIntentAllowlist: intents.length > 0,
|
|
255
351
|
typescript: ts,
|
|
352
|
+
forbiddenGlobals,
|
|
256
353
|
});
|
|
257
354
|
|
|
258
355
|
if (args.hook) {
|
|
@@ -260,6 +357,11 @@ async function main() {
|
|
|
260
357
|
return;
|
|
261
358
|
}
|
|
262
359
|
|
|
360
|
+
if (args.sessionContext) {
|
|
361
|
+
printSessionContext(config, profile, forbiddenGlobals, args);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
263
365
|
const SERVER_INFO = { name: 'ark-runtime-kernel', version: ark.version };
|
|
264
366
|
const DEFAULT_PROTOCOL = '2024-11-05';
|
|
265
367
|
|
|
@@ -342,6 +444,7 @@ async function main() {
|
|
|
342
444
|
name: profile.name,
|
|
343
445
|
layers: profile.layers,
|
|
344
446
|
rules: profile.rules,
|
|
447
|
+
...(Object.keys(forbiddenGlobals).length > 0 ? { forbiddenGlobals } : {}),
|
|
345
448
|
...(suggestions.length > 0
|
|
346
449
|
? {
|
|
347
450
|
suggestedLayers: suggestions,
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -69,6 +69,13 @@ 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,11 +89,55 @@ export function createElevenLayerConfig(options = {}) {
|
|
|
82
89
|
),
|
|
83
90
|
intentPrefixes: entry.prefixes,
|
|
84
91
|
optional,
|
|
92
|
+
...(entry.layer === 'DomainModel'
|
|
93
|
+
? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
|
|
94
|
+
: {}),
|
|
85
95
|
})),
|
|
86
96
|
rules: DEFAULT_RULES,
|
|
87
97
|
};
|
|
88
98
|
}
|
|
89
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
|
+
|
|
90
141
|
const _regexpCache = new Map();
|
|
91
142
|
|
|
92
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":";AAoCA,SAAS,YAAY,IAAA,EAA+C;AAClE,EAAA,OAAO,OAAO,IAAA,EAAM,KAAA,KAAU,QAAA,GAAW,KAAK,KAAA,GAAQ,MAAA;AACxD;AAEA,SAAS,aAAa,IAAA,EAA+C;AACnE,EAAA,OAAO,IAAA,EAAM,IAAA,IAAQ,WAAA,CAAY,IAAI,CAAA;AACvC;AAEA,SAAS,mBAAmB,IAAA,EAAmC;AAC7D,EAAA,OAAO,YAAA,CAAa,IAAA,CAAK,MAAA,EAAQ,QAAQ,CAAA;AAC3C;AAEA,SAAS,cAAA,CAAe,MAA2B,IAAA,EAAmC;AACpF,EAAA,OAAO,IAAA,EAAM,YAAY,IAAA,CAAK,CAAC,aAAa,YAAA,CAAa,QAAA,CAAS,GAAG,CAAA,KAAM,IAAI,CAAA;AACjF;AAEA,SAAS,iBAAA,CAAkB,MAA2B,IAAA,EAAuB;AAC3E,EAAA,OAAO,cAAA,CAAe,IAAA,EAAM,IAAI,CAAA,KAAM,MAAA;AACxC;AAEA,SAAS,wBAAwB,IAAA,EAAoC;AACnE,EAAA,MAAM,QAAA,GAAW,cAAA,CAAe,IAAA,EAAM,UAAU,CAAA,EAAG,KAAA;AACnD,EAAA,OAAO,iBAAA,CAAkB,UAAU,QAAQ,CAAA;AAC7C;AAEA,SAAS,gBAAgB,KAAA,EAAwB;AAC/C,EAAA,OAAO,iIAAA,CAAkI,KAAK,KAAK,CAAA;AACrJ;AAEA,SAAS,aAAa,OAAA,EAA+B;AACnD,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,WAAA,IAAc,IAAK,EAAA;AAC5C,EAAA,MAAM,UAAA,GAAa,SAAS,KAAA,CAAM,IAAI,EAAE,IAAA,CAAK,GAAG,EAAE,WAAA,EAAY;AAC9D,EAAA,OAAO,WAAW,QAAA,CAAS,UAAU,CAAA,IAAK,UAAA,CAAW,SAAS,YAAY,CAAA;AAC5E;AAEA,SAAS,cAAc,SAAA,EAA4B;AACjD,EAAA,MAAM,UAAA,GAAa,UAAU,WAAA,EAAY;AACzC,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,UAAA;AAAA,IACA,gBAAA;AAAA,IACA,aAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,IACA,IAAA,CAAK,CAAC,UAAU,UAAA,CAAW,QAAA,CAAS,KAAK,CAAC,CAAA;AAC9C;AAEA,SAAS,cAAc,IAAA,EAAwB;AAC7C,EAAA,OAAO,kBAAA,CAAmB,IAAI,CAAA,KAAM,SAAA;AACtC;AAEO,IAAM,oBAAA,GAAgC;AAAA,EAC3C,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EAAa;AAAA,KACf;AAAA,IACA,QAAA,EAAU;AAAA,MACR,eAAA,EAAiB;AAAA,KACnB;AAAA,IACA,QAAQ;AAAC,GACX;AAAA,EACA,OAAO,OAAA,EAAS;AACd,IAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,KAAkB;AAC/B,MAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC5B,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,IAAA,CAAK,MAAM,CAAA;AACtC,MAAA,IAAI,MAAA,IAAU,aAAA,CAAc,MAAM,CAAA,EAAG;AACnC,QAAA,OAAA,CAAQ,MAAA,CAAO,EAAE,IAAA,EAAM,SAAA,EAAW,mBAAmB,CAAA;AAAA,MACvD;AAAA,IACF,CAAA;AAEA,IAAA,OAAO;AAAA,MACL,iBAAA,EAAmB,KAAA;AAAA,MACnB,sBAAA,EAAwB,KAAA;AAAA,MACxB,oBAAA,EAAsB;AAAA,KACxB;AAAA,EACF;AACF;AAEO,IAAM,iBAAA,GAA6B;AAAA,EACxC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EAAa;AAAA,KACf;AAAA,IACA,QAAA,EAAU;AAAA,MACR,UAAA,EAAY;AAAA,KACd;AAAA,IACA,QAAQ;AAAC,GACX;AAAA,EACA,OAAO,OAAA,EAAS;AACd,IAAA,OAAO;AAAA,MACL,eAAe,IAAA,EAAM;AACnB,QAAA,IAAI,CAAC,aAAA,CAAc,IAAI,CAAA,EAAG;AAC1B,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,GAAY,CAAC,CAAA;AACnC,QAAA,MAAM,UAAA,GAAa,YAAY,QAAQ,CAAA;AACvC,QAAA,IACE,cAAc,eAAA,CAAgB,UAAU,KACxC,iBAAA,CAAkB,QAAA,EAAU,QAAQ,CAAA,EACpC;AACA,UAAA,OAAA,CAAQ,MAAA,CAAO,EAAE,IAAA,EAAM,SAAA,EAAW,cAAc,CAAA;AAAA,QAClD;AAAA,MACF;AAAA,KACF;AAAA,EACF;AACF;AAEO,IAAM,oBAAA,GAAgC;AAAA,EAC3C,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EAAa;AAAA,KACf;AAAA,IACA,QAAA,EAAU;AAAA,MACR,aAAA,EAAe;AAAA,KACjB;AAAA,IACA,QAAQ;AAAC,GACX;AAAA,EACA,OAAO,OAAA,EAAS;AACd,IAAA,OAAO;AAAA,MACL,eAAe,IAAA,EAAM;AACnB,QAAA,IAAI,CAAC,aAAA,CAAc,IAAI,CAAA,EAAG;AAC1B,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,GAAY,CAAC,CAAA;AACnC,QAAA,MAAM,WAAA,GAAc,IAAA,CAAK,SAAA,GAAY,CAAC,CAAA;AACtC,QAAA,IAAI,wBAAwB,QAAQ,CAAA,IAAK,iBAAA,CAAkB,WAAA,EAAa,QAAQ,CAAA,EAAG;AACjF,UAAA;AAAA,QACF;AACA,QAAA,OAAA,CAAQ,MAAA,CAAO,EAAE,IAAA,EAAM,SAAA,EAAW,iBAAiB,CAAA;AAAA,MACrD;AAAA,KACF;AAAA,EACF;AACF;AAEA,IAAM,yBAAA,GAA4B,CAAC,OAAA,EAAS,SAAA,EAAW,YAAY,aAAa,CAAA;AAEzE,IAAM,kBAAA,GAA8B;AAAA,EACzC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,QAAA,EAAU;AAAA,MACR,eAAA,EAAiB;AAAA,KACnB;AAAA,IACA,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,OAAA,EAAS,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,UAAS;AAAE,SACtD;AAAA,QACA,oBAAA,EAAsB;AAAA;AACxB;AACF,GACF;AAAA,EACA,OAAO,OAAA,EAAS;AACd,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,GAAU,CAAC,CAAA;AAClC,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,MAAA,EAAQ,WAAW,yBAAyB,CAAA;AACpE,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,EAAe,IAAA,KAC7B,QAAQ,MAAA,CAAO,EAAE,IAAA,EAAM,SAAA,EAAW,iBAAA,EAAmB,IAAA,EAAM,EAAE,IAAA,IAAQ,CAAA;AAEvE,IAAA,OAAO;AAAA;AAAA;AAAA;AAAA,MAIL,iBAAiB,IAAA,EAAM;AACrB,QAAA,MAAM,OAAO,IAAA,CAAK,MAAA,EAAQ,SAAS,YAAA,GAAe,IAAA,CAAK,OAAO,IAAA,GAAO,MAAA;AACrE,QAAA,IAAI,CAAC,IAAA,EAAM;AACX,QAAA,MAAM,MAAA,GAAS,GAAG,IAAI,CAAA,CAAA,EAAI,aAAa,IAAA,CAAK,QAAQ,KAAK,EAAE,CAAA,CAAA;AAC3D,QAAA,IAAI,QAAQ,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,CAAO,MAAM,MAAM,CAAA;AAAA,aAAA,IACnC,QAAQ,GAAA,CAAI,IAAI,CAAA,EAAG,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,MAC/C,CAAA;AAAA,MACA,eAAe,IAAA,EAAM;AACnB,QAAA,MAAM,SAAS,IAAA,CAAK,MAAA,EAAQ,SAAS,YAAA,GAAe,IAAA,CAAK,OAAO,IAAA,GAAO,MAAA;AACvE,QAAA,IAAI,UAAU,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,CAAO,MAAM,MAAM,CAAA;AAAA,MACxD,CAAA;AAAA,MACA,cAAc,IAAA,EAAM;AAClB,QAAA,MAAM,SAAS,IAAA,CAAK,MAAA,EAAQ,SAAS,YAAA,GAAe,IAAA,CAAK,OAAO,IAAA,GAAO,MAAA;AACvE,QAAA,IAAI,UAAU,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,EAAG,MAAA,CAAO,MAAM,MAAM,CAAA;AAAA,MACxD;AAAA,KACF;AAAA,EACF;AACF;AAEA,IAAM,KAAA,GAAQ;AAAA,EACZ,yBAAA,EAA2B,oBAAA;AAAA,EAC3B,sBAAA,EAAwB,iBAAA;AAAA,EACxB,wBAAA,EAA0B,oBAAA;AAAA,EAC1B,sBAAA,EAAwB;AAC1B,CAAA;AAEA,IAAM,MAAA,GAA0B,EAAE,KAAA;AAElC,MAAA,CAAO,OAAA,GAAU;AAAA,EACf,WAAA,EAAa;AAAA,IACX,OAAA,EAAS,EAAE,GAAA,EAAK,MAAA,EAAO;AAAA,IACvB,KAAA,EAAO;AAAA,MACL,6BAAA,EAA+B,OAAA;AAAA,MAC/B,0BAAA,EAA4B,OAAA;AAAA,MAC5B,4BAAA,EAA8B;AAAA;AAChC;AAEJ,CAAA;AAGA,IAAO,cAAA,GAAQ","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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/version.ts
|
|
4
|
-
var version = "1.
|
|
4
|
+
var version = "1.5.0";
|
|
5
5
|
|
|
6
6
|
// src/kernel/intent/IntentRegistry.ts
|
|
7
7
|
var IntentRegistry = class {
|
|
@@ -2101,6 +2101,33 @@ function tsPublishSourceLiteral(ts, node) {
|
|
|
2101
2101
|
const rawMetadata = tsObjectPropertyValue(ts, firstArg, "metadata");
|
|
2102
2102
|
return tsStringLiteralText(ts, tsObjectPropertyValue(ts, rawMetadata, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, secondArg, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, thirdArg, "source"));
|
|
2103
2103
|
}
|
|
2104
|
+
function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
|
|
2105
|
+
const entries = new Set(forbidden);
|
|
2106
|
+
if (entries.size === 0) return [];
|
|
2107
|
+
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
2108
|
+
const violations = [];
|
|
2109
|
+
const flag = (name, node) => violations.push(
|
|
2110
|
+
violation("FORBIDDEN_GLOBAL", `${layer} must not use the ambient global "${name}".`, {
|
|
2111
|
+
line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
2112
|
+
filePath,
|
|
2113
|
+
target: name,
|
|
2114
|
+
fromLayer: layer,
|
|
2115
|
+
suggestion: "Inject the capability through a port (e.g. a Clock, IdGenerator, or HttpPort) instead of reaching for the ambient global."
|
|
2116
|
+
})
|
|
2117
|
+
);
|
|
2118
|
+
const visit = (node) => {
|
|
2119
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
2120
|
+
const dotted = `${node.expression.text}.${node.name.text}`;
|
|
2121
|
+
if (entries.has(dotted)) flag(dotted, node);
|
|
2122
|
+
else if (entries.has(node.expression.text)) flag(node.expression.text, node);
|
|
2123
|
+
} else if ((ts.isCallExpression(node) || ts.isNewExpression(node)) && node.expression && ts.isIdentifier(node.expression) && entries.has(node.expression.text)) {
|
|
2124
|
+
flag(node.expression.text, node);
|
|
2125
|
+
}
|
|
2126
|
+
ts.forEachChild(node, visit);
|
|
2127
|
+
};
|
|
2128
|
+
visit(sourceFile);
|
|
2129
|
+
return violations;
|
|
2130
|
+
}
|
|
2104
2131
|
function analyzePublishAst(ts, source, context, profile) {
|
|
2105
2132
|
const sourceFile = ts.createSourceFile(
|
|
2106
2133
|
"generated.ts",
|
|
@@ -2299,6 +2326,26 @@ function createAICodeGate(options = {}) {
|
|
|
2299
2326
|
}
|
|
2300
2327
|
}
|
|
2301
2328
|
}
|
|
2329
|
+
if (options.typescript && contextLayer && options.forbiddenGlobals?.[contextLayer]?.length) {
|
|
2330
|
+
try {
|
|
2331
|
+
violations.push(
|
|
2332
|
+
...analyzeForbiddenGlobals(
|
|
2333
|
+
options.typescript,
|
|
2334
|
+
source,
|
|
2335
|
+
filePath,
|
|
2336
|
+
contextLayer,
|
|
2337
|
+
options.forbiddenGlobals[contextLayer]
|
|
2338
|
+
)
|
|
2339
|
+
);
|
|
2340
|
+
} catch (err) {
|
|
2341
|
+
violations.push(
|
|
2342
|
+
violation(
|
|
2343
|
+
"AST_ANALYZER_ERROR",
|
|
2344
|
+
`TypeScript AST analyzer failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2345
|
+
)
|
|
2346
|
+
);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2302
2349
|
if (options.typescript) {
|
|
2303
2350
|
try {
|
|
2304
2351
|
violations.push(
|