arkgate 2.6.1 → 2.8.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/CHANGELOG.md +62 -0
- package/README.md +8 -3
- package/bin/ark-check.mjs +19 -993
- package/bin/ark-layer-match.mjs +148 -171
- package/bin/ark-shared.mjs +9 -159
- package/bin/lib/agent-gates.mjs +48 -228
- package/bin/lib/architecture-scan.mjs +299 -0
- package/bin/lib/ast-scan.mjs +427 -0
- package/bin/lib/baseline-key.mjs +23 -0
- package/bin/lib/codex-home.mjs +320 -0
- package/bin/lib/config-warnings.mjs +228 -0
- package/bin/lib/doctor-plan.mjs +2 -0
- package/bin/lib/graph-cycles.mjs +56 -0
- package/bin/lib/remediation.mjs +182 -0
- package/bin/lib/scan-files.mjs +69 -0
- package/bin/lib/ts-resolve.mjs +216 -0
- package/bin/lib/violations.mjs +3 -9
- package/dist/eslint/index.cjs +21 -3
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +5 -3
- package/dist/eslint/index.d.ts +5 -3
- package/dist/eslint/index.js +21 -3
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -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.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +3080 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2998 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/{types-DpdVN7Lm.d.cts → types-CP3KkwZt.d.cts} +1 -1
- package/dist/{types-DpdVN7Lm.d.ts → types-CP3KkwZt.d.ts} +1 -1
- package/docs/agent-guide.md +5 -2
- package/docs/ai-gates.md +41 -7
- package/docs/brownfield-adoption.md +7 -0
- package/docs/demos/03-copilot-autopilot.md +3 -2
- package/docs/enthusiast/reference-commands.md +2 -2
- package/docs/migrate-from-ark-runtime-kernel.md +4 -2
- package/docs/package-surface.md +72 -0
- package/docs/production-hardening.md +3 -0
- package/package.json +12 -1
- package/server.json +2 -2
- package/templates/skills/ark-explain.md +3 -2
- package/templates/skills/ark-loop.md +2 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST helpers for publish checks, type-only edges, and module specifiers.
|
|
3
|
+
* Extracted from ark-check entry (R3).
|
|
4
|
+
*/
|
|
5
|
+
import { looksLikeIntent } from '../ark-shared.mjs';
|
|
6
|
+
|
|
7
|
+
export function lineOf(sourceFile, pos) {
|
|
8
|
+
return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function textOfModuleSpecifier(node) {
|
|
12
|
+
return node.moduleSpecifier && typeof node.moduleSpecifier.text === 'string'
|
|
13
|
+
? node.moduleSpecifier.text
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// True when an import/export edge carries ONLY types (`import type …`, or a named import
|
|
18
|
+
// where every binding is `type`-qualified). Type-only edges are erased at compile time —
|
|
19
|
+
// they create no runtime coupling, only a design/type-placement dependency — so callers can
|
|
20
|
+
// rank them below real value imports in a burn-down. A side-effect import (`import "x"`) or
|
|
21
|
+
// any default/namespace/value binding is NOT type-only.
|
|
22
|
+
export function isTypeOnlyModuleReference(ts, node) {
|
|
23
|
+
if (ts.isImportDeclaration(node)) {
|
|
24
|
+
const clause = node.importClause;
|
|
25
|
+
if (!clause) return false; // side-effect import — runtime edge
|
|
26
|
+
if (clause.isTypeOnly) return true; // `import type …`
|
|
27
|
+
const named = clause.namedBindings;
|
|
28
|
+
if (named && ts.isNamedImports(named) && named.elements.length > 0) {
|
|
29
|
+
return named.elements.every((element) => element.isTypeOnly);
|
|
30
|
+
}
|
|
31
|
+
return false; // default or namespace binding of a value
|
|
32
|
+
}
|
|
33
|
+
if (ts.isExportDeclaration(node)) {
|
|
34
|
+
if (node.isTypeOnly) return true;
|
|
35
|
+
const clause = node.exportClause;
|
|
36
|
+
if (clause && ts.isNamedExports(clause) && clause.elements.length > 0) {
|
|
37
|
+
return clause.elements.every((element) => element.isTypeOnly);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* True when a module is a pure type-surface file: only type/interface exports and
|
|
46
|
+
* type-only imports. Conservative false (→ judgment) when:
|
|
47
|
+
* - any top-level runtime statement (value decls, expression stmts, side-effect imports)
|
|
48
|
+
* - ambiguous `export { X }` without type keyword, export *, default/export=
|
|
49
|
+
* Used so static value-syntax `import { T }` of a pure-type module can be mechanical-safe
|
|
50
|
+
* (convert to `import type`). Never trust this for require()/import() edges.
|
|
51
|
+
*/
|
|
52
|
+
function hasExportModifier(ts, node) {
|
|
53
|
+
return (
|
|
54
|
+
Array.isArray(node.modifiers) &&
|
|
55
|
+
node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function sourceFileExportsOnlyTypes(ts, sourceFile) {
|
|
60
|
+
let sawTypeExport = false;
|
|
61
|
+
|
|
62
|
+
for (const stmt of sourceFile.statements) {
|
|
63
|
+
// Type-only imports OK; value or side-effect imports mean runtime load of deps.
|
|
64
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
65
|
+
if (!isTypeOnlyModuleReference(ts, stmt)) return false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (typeof ts.isImportEqualsDeclaration === 'function' && ts.isImportEqualsDeclaration(stmt)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (ts.isExportDeclaration(stmt)) {
|
|
72
|
+
if (stmt.isTypeOnly) {
|
|
73
|
+
sawTypeExport = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// export * from '…' can re-export values — not provably type-only.
|
|
77
|
+
if (!stmt.exportClause) return false;
|
|
78
|
+
if (ts.isNamespaceExport(stmt.exportClause)) return false;
|
|
79
|
+
if (ts.isNamedExports(stmt.exportClause)) {
|
|
80
|
+
if (stmt.exportClause.elements.length === 0) return false;
|
|
81
|
+
for (const el of stmt.exportClause.elements) {
|
|
82
|
+
if (!el.isTypeOnly) return false; // bare `export { X }` — ambiguous without checker
|
|
83
|
+
}
|
|
84
|
+
sawTypeExport = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (ts.isExportAssignment(stmt)) return false; // export = / export default expr
|
|
90
|
+
if (ts.isTypeAliasDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) {
|
|
91
|
+
if (hasExportModifier(ts, stmt)) sawTypeExport = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Any other top-level statement (const/fn/class/enum, console.log, if, …) is runtime.
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return sawTypeExport;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Names that exist in the *value* export space of this module (runtime bindings).
|
|
102
|
+
* Used to subtract dual-space names (e.g. `export type Foo` + `export const Foo`) from
|
|
103
|
+
* type-only export sets so converting `import { Foo }` to `import type` never drops a
|
|
104
|
+
* runtime binding.
|
|
105
|
+
*/
|
|
106
|
+
function collectBindingIdentifiers(ts, nameNode, into) {
|
|
107
|
+
if (!nameNode) return;
|
|
108
|
+
if (ts.isIdentifier(nameNode)) {
|
|
109
|
+
into.add(nameNode.text);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (ts.isObjectBindingPattern(nameNode) || ts.isArrayBindingPattern(nameNode)) {
|
|
113
|
+
for (const el of nameNode.elements) {
|
|
114
|
+
if (ts.isOmittedExpression(el)) continue;
|
|
115
|
+
if (ts.isBindingElement(el)) collectBindingIdentifiers(ts, el.name, into);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function valueExportNames(ts, sourceFile) {
|
|
121
|
+
const names = new Set();
|
|
122
|
+
const add = (n) => {
|
|
123
|
+
if (n) names.add(n);
|
|
124
|
+
};
|
|
125
|
+
for (const stmt of sourceFile.statements) {
|
|
126
|
+
// export const/let/var Foo = …
|
|
127
|
+
if (ts.isVariableStatement(stmt) && hasExportModifier(ts, stmt)) {
|
|
128
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
129
|
+
collectBindingIdentifiers(ts, decl.name, names);
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// export function Foo / export async function Foo
|
|
134
|
+
if (ts.isFunctionDeclaration(stmt) && hasExportModifier(ts, stmt) && stmt.name) {
|
|
135
|
+
add(stmt.name.text);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// export class Foo — value + type space; treat as value so never auto import-type
|
|
139
|
+
if (ts.isClassDeclaration(stmt) && hasExportModifier(ts, stmt) && stmt.name) {
|
|
140
|
+
add(stmt.name.text);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// export enum Foo — value + type
|
|
144
|
+
if (ts.isEnumDeclaration(stmt) && hasExportModifier(ts, stmt) && stmt.name) {
|
|
145
|
+
add(stmt.name.text);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// export namespace Foo — value + type
|
|
149
|
+
if (ts.isModuleDeclaration(stmt) && hasExportModifier(ts, stmt) && stmt.name && ts.isIdentifier(stmt.name)) {
|
|
150
|
+
add(stmt.name.text);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!ts.isExportDeclaration(stmt)) continue;
|
|
154
|
+
// export * from '…' — unknown value surface; cannot prove type-only names alone
|
|
155
|
+
if (!stmt.exportClause) {
|
|
156
|
+
// star re-export can introduce values; flag as opaque by adding a sentinel? callers
|
|
157
|
+
// only check named bindings against explicit type-only sets — leave empty for star.
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (ts.isNamespaceExport(stmt.exportClause)) continue;
|
|
161
|
+
if (!ts.isNamedExports(stmt.exportClause)) continue;
|
|
162
|
+
// bare `export { Foo }` / `export { Foo } from '…'` without type keyword — value (or dual)
|
|
163
|
+
if (!stmt.isTypeOnly) {
|
|
164
|
+
for (const el of stmt.exportClause.elements) {
|
|
165
|
+
if (el.isTypeOnly) continue;
|
|
166
|
+
const local = el.propertyName && 'text' in el.propertyName ? el.propertyName.text : el.name?.text;
|
|
167
|
+
add(local);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return names;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* True when an expression may run runtime work if the module is evaluated.
|
|
176
|
+
* Conservative: any call/new/await/tagged-template (or nested) is impure.
|
|
177
|
+
* Literals, identifiers, pure object/array/as/parenthesized trees are pure.
|
|
178
|
+
*/
|
|
179
|
+
export function expressionMayHaveSideEffects(ts, expr) {
|
|
180
|
+
if (!expr) return false;
|
|
181
|
+
if (
|
|
182
|
+
ts.isCallExpression(expr) ||
|
|
183
|
+
ts.isNewExpression(expr) ||
|
|
184
|
+
ts.isAwaitExpression(expr) ||
|
|
185
|
+
ts.isTaggedTemplateExpression(expr) ||
|
|
186
|
+
ts.isYieldExpression?.(expr)
|
|
187
|
+
) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
// Walk children; short-circuit on first impure.
|
|
191
|
+
let impure = false;
|
|
192
|
+
const visit = (node) => {
|
|
193
|
+
if (impure) return;
|
|
194
|
+
if (
|
|
195
|
+
ts.isCallExpression(node) ||
|
|
196
|
+
ts.isNewExpression(node) ||
|
|
197
|
+
ts.isAwaitExpression(node) ||
|
|
198
|
+
ts.isTaggedTemplateExpression(node) ||
|
|
199
|
+
(typeof ts.isYieldExpression === 'function' && ts.isYieldExpression(node))
|
|
200
|
+
) {
|
|
201
|
+
impure = true;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
ts.forEachChild(node, visit);
|
|
205
|
+
};
|
|
206
|
+
ts.forEachChild(expr, visit);
|
|
207
|
+
return impure;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* True when evaluating this module may run non-trivial top-level work.
|
|
212
|
+
* Covers: expression statements, bare side-effect imports, control-flow,
|
|
213
|
+
* any top-level var initializer that call/new/await (exported or not),
|
|
214
|
+
* export-default impure expr, and class static field calls (export-agnostic).
|
|
215
|
+
* Converting `import { Type }` → `import type` would skip those effects — not auto-safe.
|
|
216
|
+
*/
|
|
217
|
+
export function sourceFileHasTopLevelSideEffects(ts, sourceFile) {
|
|
218
|
+
for (const stmt of sourceFile.statements) {
|
|
219
|
+
if (ts.isExpressionStatement(stmt)) return true;
|
|
220
|
+
if (ts.isImportDeclaration(stmt) && !stmt.importClause) return true; // import './x'
|
|
221
|
+
if (
|
|
222
|
+
ts.isIfStatement(stmt) ||
|
|
223
|
+
ts.isForStatement(stmt) ||
|
|
224
|
+
ts.isForInStatement(stmt) ||
|
|
225
|
+
ts.isForOfStatement(stmt) ||
|
|
226
|
+
ts.isWhileStatement(stmt) ||
|
|
227
|
+
ts.isDoStatement(stmt) ||
|
|
228
|
+
ts.isSwitchStatement(stmt) ||
|
|
229
|
+
ts.isTryStatement(stmt) ||
|
|
230
|
+
ts.isThrowStatement(stmt) ||
|
|
231
|
+
ts.isWithStatement?.(stmt)
|
|
232
|
+
) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
// Top-level const/let/var x = <maybe impure> — including non-exported.
|
|
236
|
+
// `const db = connect(); export type Row = …` still runs connect on module load;
|
|
237
|
+
// converting `import { Row }` → `import type` would skip that work (R6 honesty).
|
|
238
|
+
if (ts.isVariableStatement(stmt)) {
|
|
239
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
240
|
+
if (decl.initializer && expressionMayHaveSideEffects(ts, decl.initializer)) return true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// export default <expr>
|
|
244
|
+
if (ts.isExportAssignment(stmt) && !stmt.isExportEquals) {
|
|
245
|
+
if (stmt.expression && expressionMayHaveSideEffects(ts, stmt.expression)) return true;
|
|
246
|
+
}
|
|
247
|
+
// Class with static field initializers that call — class body evaluates at load
|
|
248
|
+
// whether or not the class is exported.
|
|
249
|
+
if (ts.isClassDeclaration(stmt)) {
|
|
250
|
+
for (const member of stmt.members ?? []) {
|
|
251
|
+
if (
|
|
252
|
+
ts.isPropertyDeclaration(member) &&
|
|
253
|
+
member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) &&
|
|
254
|
+
member.initializer &&
|
|
255
|
+
expressionMayHaveSideEffects(ts, member.initializer)
|
|
256
|
+
) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Names that are provably type-only exports of this module (erased at runtime).
|
|
267
|
+
* Conservative: class/enum/namespace/const/function exports are excluded even when they
|
|
268
|
+
* also introduce a type. Dual-space names (`export type Foo` + `export const Foo`) are
|
|
269
|
+
* subtracted — converting those to `import type` would drop a runtime binding.
|
|
270
|
+
* Used so `import { Row }` of a type alias from a mixed module can be mechanical-safe.
|
|
271
|
+
*/
|
|
272
|
+
export function typeOnlyExportNames(ts, sourceFile) {
|
|
273
|
+
const names = new Set();
|
|
274
|
+
for (const stmt of sourceFile.statements) {
|
|
275
|
+
if (ts.isTypeAliasDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) {
|
|
276
|
+
if (hasExportModifier(ts, stmt) && stmt.name) names.add(stmt.name.text);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (!ts.isExportDeclaration(stmt)) continue;
|
|
280
|
+
const clause = stmt.exportClause;
|
|
281
|
+
if (!clause || !ts.isNamedExports(clause)) continue;
|
|
282
|
+
for (const el of clause.elements) {
|
|
283
|
+
// `export type { X }` or `export { type X }` — type-only re-exports.
|
|
284
|
+
if (stmt.isTypeOnly || el.isTypeOnly) {
|
|
285
|
+
const local = el.propertyName && 'text' in el.propertyName ? el.propertyName.text : el.name?.text;
|
|
286
|
+
if (local) names.add(local);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Subtract any name that also has a value export (dual-space / value re-export).
|
|
291
|
+
const values = valueExportNames(ts, sourceFile);
|
|
292
|
+
for (const v of values) names.delete(v);
|
|
293
|
+
return [...names];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Local names of named import/export bindings on a module edge, or null when the edge
|
|
298
|
+
* is not a pure named list (default import, namespace, side-effect, export *, export =).
|
|
299
|
+
* PropertyName is preferred so `import { Row as R }` still checks target export `Row`.
|
|
300
|
+
*/
|
|
301
|
+
export function namedModuleBindings(ts, node) {
|
|
302
|
+
if (ts.isImportDeclaration(node)) {
|
|
303
|
+
const clause = node.importClause;
|
|
304
|
+
if (!clause) return null; // side-effect
|
|
305
|
+
if (clause.name) return null; // default import (possibly with named — still not pure-named-only)
|
|
306
|
+
const named = clause.namedBindings;
|
|
307
|
+
if (!named || !ts.isNamedImports(named) || named.elements.length === 0) return null;
|
|
308
|
+
return named.elements.map((el) => {
|
|
309
|
+
const prop = el.propertyName && 'text' in el.propertyName ? el.propertyName.text : null;
|
|
310
|
+
return prop || el.name.text;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (ts.isExportDeclaration(node)) {
|
|
314
|
+
const clause = node.exportClause;
|
|
315
|
+
if (!clause || !ts.isNamedExports(clause) || clause.elements.length === 0) return null;
|
|
316
|
+
return clause.elements.map((el) => {
|
|
317
|
+
const prop = el.propertyName && 'text' in el.propertyName ? el.propertyName.text : null;
|
|
318
|
+
return prop || el.name.text;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function propertyName(ts, node) {
|
|
325
|
+
if (!node) return undefined;
|
|
326
|
+
if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) return node.text;
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function objectProperty(ts, node, name) {
|
|
331
|
+
if (!node || !ts.isObjectLiteralExpression(node)) return undefined;
|
|
332
|
+
return node.properties.find((property) => {
|
|
333
|
+
if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
return propertyName(ts, property.name) === name;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function objectHasProperty(ts, node, name) {
|
|
341
|
+
return objectProperty(ts, node, name) !== undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function objectPropertyValue(ts, node, name) {
|
|
345
|
+
const property = objectProperty(ts, node, name);
|
|
346
|
+
return property && ts.isPropertyAssignment(property)
|
|
347
|
+
? property.initializer
|
|
348
|
+
: undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function objectHasMetadataSource(ts, node) {
|
|
352
|
+
const metadata = objectPropertyValue(ts, node, 'metadata');
|
|
353
|
+
return objectHasProperty(ts, metadata, 'source');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function stringLiteralText(ts, node) {
|
|
357
|
+
return node && ts.isStringLiteralLike(node) ? node.text : undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function isPublishCall(ts, node) {
|
|
361
|
+
if (!ts.isCallExpression(node)) return false;
|
|
362
|
+
const expression = node.expression;
|
|
363
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
364
|
+
return expression.name.text === 'publish';
|
|
365
|
+
}
|
|
366
|
+
return ts.isIdentifier(expression) && expression.text === 'publish';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function looksLikeIntentCreatorExpression(ts, node) {
|
|
370
|
+
if (!node) return false;
|
|
371
|
+
if (ts.isIdentifier(node)) {
|
|
372
|
+
return /^[A-Z]/.test(node.text);
|
|
373
|
+
}
|
|
374
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
375
|
+
return looksLikeIntentCreatorExpression(ts, node.name);
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function isArkPublishCandidate(ts, node) {
|
|
381
|
+
if (!ts.isCallExpression(node)) return false;
|
|
382
|
+
const firstArg = node.arguments[0];
|
|
383
|
+
const rawIntent = stringLiteralText(ts, firstArg);
|
|
384
|
+
return (
|
|
385
|
+
(rawIntent !== undefined && looksLikeIntent(rawIntent)) ||
|
|
386
|
+
objectHasProperty(ts, firstArg, 'intent') ||
|
|
387
|
+
looksLikeIntentCreatorExpression(ts, firstArg)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function publishSourceLiteral(ts, node) {
|
|
392
|
+
if (!ts.isCallExpression(node)) return undefined;
|
|
393
|
+
const [firstArg, secondArg, thirdArg] = node.arguments;
|
|
394
|
+
const rawMetadata = objectPropertyValue(ts, firstArg, 'metadata');
|
|
395
|
+
return (
|
|
396
|
+
stringLiteralText(ts, objectPropertyValue(ts, rawMetadata, 'source')) ??
|
|
397
|
+
stringLiteralText(ts, objectPropertyValue(ts, secondArg, 'source')) ??
|
|
398
|
+
stringLiteralText(ts, objectPropertyValue(ts, thirdArg, 'source'))
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function publishHasSource(ts, node) {
|
|
403
|
+
if (!ts.isCallExpression(node)) return false;
|
|
404
|
+
const [firstArg, secondArg, thirdArg] = node.arguments;
|
|
405
|
+
return (
|
|
406
|
+
objectHasMetadataSource(ts, firstArg) ||
|
|
407
|
+
objectHasProperty(ts, secondArg, 'source') ||
|
|
408
|
+
objectHasProperty(ts, thirdArg, 'source')
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
export function moduleSpecifierFromCall(ts, node) {
|
|
412
|
+
if (!ts.isCallExpression(node)) return undefined;
|
|
413
|
+
|
|
414
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
415
|
+
const first = node.arguments[0];
|
|
416
|
+
const value = stringLiteralText(ts, first);
|
|
417
|
+
return value ? { value, kind: 'dynamic-import' } : undefined;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
421
|
+
const first = node.arguments[0];
|
|
422
|
+
const value = stringLiteralText(ts, first);
|
|
423
|
+
return value ? { value, kind: 'require' } : undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/baselineKey.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/baseline-key.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stable key used by `--baseline` / `--update-baseline` to match frozen debt.
|
|
13
|
+
* Field order and empty-string fallbacks are part of the CLI contract.
|
|
14
|
+
*/
|
|
15
|
+
export function baselineKey(violation) {
|
|
16
|
+
return [
|
|
17
|
+
violation.ruleId,
|
|
18
|
+
violation.file,
|
|
19
|
+
violation.fromLayer ?? '',
|
|
20
|
+
violation.toLayer ?? '',
|
|
21
|
+
violation.target ?? '',
|
|
22
|
+
].join('|');
|
|
23
|
+
}
|