arkgate 2.6.0 → 2.7.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 +97 -0
- package/README.md +98 -70
- package/bin/ark-check.mjs +240 -1001
- package/bin/ark-layer-match.mjs +153 -147
- package/bin/ark-mcp.mjs +102 -5
- package/bin/ark-shared.mjs +304 -165
- package/bin/ark.mjs +44 -34
- package/bin/lib/agent-gates.mjs +448 -15
- package/bin/lib/architecture-scan.mjs +279 -0
- package/bin/lib/ast-scan.mjs +199 -0
- package/bin/lib/baseline-key.mjs +23 -0
- package/bin/lib/config-warnings.mjs +228 -0
- package/bin/lib/doctor-plan.mjs +11 -4
- package/bin/lib/graph-cycles.mjs +56 -0
- package/bin/lib/presets.mjs +75 -4
- package/bin/lib/remediation.mjs +150 -0
- package/bin/lib/scan-files.mjs +69 -0
- package/bin/lib/ts-resolve.mjs +215 -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 +67 -1
- 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 +11 -1
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +43 -87
- package/templates/skills/ark-autopilot.md +39 -77
- package/templates/skills/ark-contract.md +43 -84
- package/templates/skills/ark-coverage.md +62 -83
- package/templates/skills/ark-fix.md +45 -90
- package/templates/skills/ark-loop.md +44 -66
package/bin/ark-layer-match.mjs
CHANGED
|
@@ -1,168 +1,174 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/layerMatch.ts
|
|
5
|
+
* Regenerate: node scripts/generate-layer-match.mjs
|
|
6
|
+
* Drift check: node scripts/generate-layer-match.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure layer-glob matching for ark.config.json (CLI load path).
|
|
9
|
+
* CLI-only layerForFile (Node path resolution) is appended below the pure core.
|
|
5
10
|
*/
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
|
|
8
|
-
const _regexpCache = new Map();
|
|
9
11
|
|
|
12
|
+
const regexpCache = new Map();
|
|
10
13
|
function escapeLiteral(ch) {
|
|
11
|
-
|
|
14
|
+
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Normalize path separators to `/` without destroying glob escape sequences.
|
|
18
|
+
* `src\domain\x` → `src/domain/x` (Windows paths); `src/\{legacy\}/**` keeps `\{` / `\}`.
|
|
19
|
+
* A plain `pattern.split('\\').join('/')` would eat those escapes.
|
|
20
|
+
*/
|
|
21
|
+
function normalizeGlobSeparators(pattern) {
|
|
22
|
+
let out = '';
|
|
23
|
+
for (let i = 0; i < pattern.length; i += 1) {
|
|
24
|
+
const c = pattern[i];
|
|
25
|
+
if (c === '\\' && i + 1 < pattern.length) {
|
|
26
|
+
const next = pattern[i + 1];
|
|
27
|
+
// Keep escapes for glob metacharacters (and escaped backslash).
|
|
28
|
+
if ('*?{}[],'.includes(next) || next === '\\') {
|
|
29
|
+
out += '\\' + next;
|
|
30
|
+
i += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Otherwise treat `\` as a path separator (Windows).
|
|
34
|
+
out += '/';
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
out += c;
|
|
22
38
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function bracesBalanced(glob) {
|
|
42
|
+
let depth = 0;
|
|
43
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
44
|
+
const c = glob[i];
|
|
45
|
+
if (c === '\\') {
|
|
46
|
+
i += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (c === '{')
|
|
50
|
+
depth += 1;
|
|
51
|
+
else if (c === '}') {
|
|
52
|
+
depth -= 1;
|
|
53
|
+
if (depth < 0)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
27
56
|
}
|
|
28
|
-
|
|
29
|
-
return depth === 0;
|
|
57
|
+
return depth === 0;
|
|
30
58
|
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Convert an ark.config.json layer glob pattern to an anchored RegExp (compiled once per
|
|
34
|
-
* pattern, then cached).
|
|
35
|
-
*
|
|
36
|
-
* IMPORTANT: the double-star is expanded in a SINGLE pass. A chained two-step replace
|
|
37
|
-
* (double-star to dot-star, then single-star to a no-slash class) corrupts the double-star,
|
|
38
|
-
* because the second step re-matches the star inside the substitution the first step just
|
|
39
|
-
* inserted. That made "src/kernel/**" stop matching nested paths, silently unclassifying
|
|
40
|
-
* every file in a subdirectory. Scanning one character at a time also lets us support
|
|
41
|
-
* brace alternation ("*.{ts,tsx}") and backslash escapes ("\\{" → literal brace).
|
|
42
|
-
*
|
|
43
|
-
* Brace alternation is only enabled when braces are balanced; an unbalanced brace (a config
|
|
44
|
-
* typo) is treated as a literal so the gate never crashes on `new RegExp`.
|
|
45
|
-
*/
|
|
46
59
|
export function globToRegExp(pattern) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const cached = regexpCache.get(pattern);
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached;
|
|
63
|
+
const glob = normalizeGlobSeparators(pattern);
|
|
64
|
+
const useBraces = bracesBalanced(glob);
|
|
65
|
+
let out = '';
|
|
66
|
+
let braceDepth = 0;
|
|
67
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
68
|
+
const c = glob[i];
|
|
69
|
+
if (c === '\\' && i + 1 < glob.length) {
|
|
70
|
+
out += escapeLiteral(glob[i + 1]);
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
else if (c === '*') {
|
|
74
|
+
if (glob[i + 1] === '*') {
|
|
75
|
+
if (glob[i + 2] === '/') {
|
|
76
|
+
out += '(?:.*/)?';
|
|
77
|
+
i += 2;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
out += '.*';
|
|
81
|
+
i += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
out += '[^/]*';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (c === '?') {
|
|
89
|
+
out += '[^/]';
|
|
90
|
+
}
|
|
91
|
+
else if (c === '{' && useBraces) {
|
|
92
|
+
out += '(?:';
|
|
93
|
+
braceDepth += 1;
|
|
94
|
+
}
|
|
95
|
+
else if (c === '}' && useBraces && braceDepth > 0) {
|
|
96
|
+
out += ')';
|
|
97
|
+
braceDepth -= 1;
|
|
98
|
+
}
|
|
99
|
+
else if (c === ',' && useBraces && braceDepth > 0) {
|
|
100
|
+
out += '|';
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
out += escapeLiteral(c);
|
|
67
104
|
}
|
|
68
|
-
} else {
|
|
69
|
-
out += '[^/]*'; // `*` matches within a single segment
|
|
70
|
-
}
|
|
71
|
-
} else if (c === '?') {
|
|
72
|
-
out += '[^/]';
|
|
73
|
-
} else if (c === '{' && useBraces) {
|
|
74
|
-
out += '(?:';
|
|
75
|
-
braceDepth += 1;
|
|
76
|
-
} else if (c === '}' && useBraces && braceDepth > 0) {
|
|
77
|
-
out += ')';
|
|
78
|
-
braceDepth -= 1;
|
|
79
|
-
} else if (c === ',' && useBraces && braceDepth > 0) {
|
|
80
|
-
out += '|';
|
|
81
|
-
} else {
|
|
82
|
-
out += escapeLiteral(c);
|
|
83
105
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return re;
|
|
106
|
+
const re = new RegExp(`^${out}$`);
|
|
107
|
+
regexpCache.set(pattern, re);
|
|
108
|
+
return re;
|
|
88
109
|
}
|
|
89
|
-
|
|
90
|
-
// Specificity score for a layer glob: more literal path segments before the first wildcard
|
|
91
|
-
// wins, then longer literal text. So `src/kernel/app/**` (3 literal segments) beats
|
|
92
|
-
// `src/kernel/**` (2), and an exact file like `src/kernel/events.ts` beats both. This is what
|
|
93
|
-
// makes a facade split (a KernelApi surface layer overlapping a KernelInternal catch-all)
|
|
94
|
-
// resolve to the surface REGARDLESS of layer declaration order — the intuitive result.
|
|
95
110
|
export function patternSpecificity(pattern) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
const glob = normalizeGlobSeparators(String(pattern));
|
|
112
|
+
const beforeWildcard = glob.split('*')[0];
|
|
113
|
+
const literalSegments = beforeWildcard.split('/').filter(Boolean).length;
|
|
114
|
+
const literalLength = glob.replace(/\*/g, '').length;
|
|
115
|
+
return literalSegments * 10000 + literalLength;
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const abs = path.isAbsolute(file) ? file : path.resolve(root, file);
|
|
120
|
-
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
121
|
-
let bestName;
|
|
122
|
-
let bestScore = -1;
|
|
123
|
-
for (const layer of layers ?? []) {
|
|
124
|
-
if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
for (const pattern of layer.patterns ?? []) {
|
|
128
|
-
if (globToRegExp(pattern).test(rel)) {
|
|
129
|
-
const score = patternSpecificity(pattern);
|
|
130
|
-
if (score > bestScore) {
|
|
131
|
-
bestScore = score;
|
|
132
|
-
bestName = layer.name;
|
|
117
|
+
export function layerForRelativePath(relPath, layers) {
|
|
118
|
+
// File paths (not globs): any OS separator → posix relative.
|
|
119
|
+
const rel = String(relPath).split(/[/\\]/).join('/');
|
|
120
|
+
let bestName;
|
|
121
|
+
let bestScore = -1;
|
|
122
|
+
for (const layer of layers ?? []) {
|
|
123
|
+
if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const pattern of layer.patterns ?? []) {
|
|
127
|
+
if (globToRegExp(pattern).test(rel)) {
|
|
128
|
+
const score = patternSpecificity(pattern);
|
|
129
|
+
if (score > bestScore) {
|
|
130
|
+
bestScore = score;
|
|
131
|
+
bestName = layer.name;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
133
134
|
}
|
|
134
|
-
}
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
return bestName;
|
|
137
|
+
}
|
|
138
|
+
export function isEdgeDenied(rules, from, to) {
|
|
139
|
+
if (from === to)
|
|
140
|
+
return false;
|
|
141
|
+
const hit = (rules ?? []).find((r) => r.from === from && r.to === to);
|
|
142
|
+
return hit?.allowed === false;
|
|
143
|
+
}
|
|
144
|
+
/** Codegen globs skipped by default scan (emitted into the CLI derived matcher). */
|
|
145
|
+
export const DEFAULT_GENERATED_FILE_GLOBS = [
|
|
146
|
+
'**/*.gen.ts',
|
|
147
|
+
'**/*.gen.tsx',
|
|
148
|
+
'**/*.generated.ts',
|
|
149
|
+
'**/*.generated.tsx',
|
|
150
|
+
];
|
|
151
|
+
export function scanExcludePatterns(config) {
|
|
152
|
+
const custom = Array.isArray(config?.exclude)
|
|
153
|
+
? config.exclude.filter((p) => typeof p === 'string')
|
|
154
|
+
: [];
|
|
155
|
+
const generated = config?.excludeGenerated === false ? [] : DEFAULT_GENERATED_FILE_GLOBS;
|
|
156
|
+
return [...generated, ...custom];
|
|
157
|
+
}
|
|
158
|
+
export function isScanExcludedRelative(relPath, config) {
|
|
159
|
+
const rel = String(relPath).split(/[/\\]/).join('/');
|
|
160
|
+
return scanExcludePatterns(config).some((pattern) => globToRegExp(pattern).test(rel));
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
|
|
141
|
-
|
|
142
|
-
export function layerForRelativePath(relPath, layers) {
|
|
143
|
-
const rel = String(relPath).split(path.sep).join('/');
|
|
144
|
-
let bestName;
|
|
145
|
-
let bestScore = -1;
|
|
146
|
-
for (const layer of layers ?? []) {
|
|
147
|
-
if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
for (const pattern of layer.patterns ?? []) {
|
|
151
|
-
if (globToRegExp(pattern).test(rel)) {
|
|
152
|
-
const score = patternSpecificity(pattern);
|
|
153
|
-
if (score > bestScore) {
|
|
154
|
-
bestScore = score;
|
|
155
|
-
bestName = layer.name;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return bestName;
|
|
161
|
-
}
|
|
164
|
+
import path from 'node:path';
|
|
162
165
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a file's architecture layer from ark.config.json layer glob patterns.
|
|
168
|
+
* Uses Node path resolution, then the pure layerForRelativePath classifier.
|
|
169
|
+
*/
|
|
170
|
+
export function layerForFile(root, file, layers) {
|
|
171
|
+
const abs = path.isAbsolute(file) ? file : path.resolve(root, file);
|
|
172
|
+
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
173
|
+
return layerForRelativePath(rel, layers);
|
|
168
174
|
}
|
package/bin/ark-mcp.mjs
CHANGED
|
@@ -41,6 +41,9 @@ import {
|
|
|
41
41
|
arkCommand,
|
|
42
42
|
layerForFile,
|
|
43
43
|
shouldShowNewHereNudge,
|
|
44
|
+
detectWorkspaces,
|
|
45
|
+
detectTsPackageRoots,
|
|
46
|
+
resolveIncludeRoots,
|
|
44
47
|
} from './ark-shared.mjs';
|
|
45
48
|
|
|
46
49
|
const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
|
|
@@ -576,9 +579,9 @@ async function main() {
|
|
|
576
579
|
{
|
|
577
580
|
name: 'ark_place',
|
|
578
581
|
description:
|
|
579
|
-
'
|
|
580
|
-
'
|
|
581
|
-
'
|
|
582
|
+
'Place a file in the architecture: pass filePath (preferred) and/or description. ' +
|
|
583
|
+
'Returns layer, mayImport / mustNotImport, forbiddenGlobals. Call BEFORE writing a new file. ' +
|
|
584
|
+
'If only description is given, returns a conventional path proposal under a governed layer.',
|
|
582
585
|
inputSchema: {
|
|
583
586
|
type: 'object',
|
|
584
587
|
properties: {
|
|
@@ -586,8 +589,12 @@ async function main() {
|
|
|
586
589
|
type: 'string',
|
|
587
590
|
description: 'Path (relative to project root or absolute) of the file to place.',
|
|
588
591
|
},
|
|
592
|
+
description: {
|
|
593
|
+
type: 'string',
|
|
594
|
+
description:
|
|
595
|
+
'What you are building (e.g. "Remotion caption overlay"). Used when filePath is omitted to propose a path.',
|
|
596
|
+
},
|
|
589
597
|
},
|
|
590
|
-
required: ['filePath'],
|
|
591
598
|
},
|
|
592
599
|
},
|
|
593
600
|
{
|
|
@@ -599,6 +606,14 @@ async function main() {
|
|
|
599
606
|
'Call BEFORE generating project structure on greenfield or early-adoption repos.',
|
|
600
607
|
inputSchema: { type: 'object', properties: {} },
|
|
601
608
|
},
|
|
609
|
+
{
|
|
610
|
+
name: 'ark_suggest_include',
|
|
611
|
+
description:
|
|
612
|
+
'Propose ark.config.json include roots from workspaces and nested TypeScript packages ' +
|
|
613
|
+
'(polyglot-safe). Same idea as ark-check --suggest-include. Use when coverage is empty ' +
|
|
614
|
+
'or the contract misses package roots.',
|
|
615
|
+
inputSchema: { type: 'object', properties: {} },
|
|
616
|
+
},
|
|
602
617
|
];
|
|
603
618
|
|
|
604
619
|
const RESOURCES = [
|
|
@@ -741,8 +756,52 @@ async function main() {
|
|
|
741
756
|
// `allowed:false` denies) — which layers it may and must not import.
|
|
742
757
|
function runPlace(params) {
|
|
743
758
|
const filePath = params?.arguments?.filePath;
|
|
759
|
+
const description = params?.arguments?.description;
|
|
760
|
+
if ((typeof filePath !== 'string' || !filePath) && typeof description === 'string' && description.trim()) {
|
|
761
|
+
// Description-only: propose a governed path under PresentationAdapters (UI default).
|
|
762
|
+
const slug = description
|
|
763
|
+
.trim()
|
|
764
|
+
.toLowerCase()
|
|
765
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
766
|
+
.replace(/^-|-$/g, '')
|
|
767
|
+
.slice(0, 48) || 'component';
|
|
768
|
+
const proposedPath = `src/components/${slug}.tsx`;
|
|
769
|
+
const layerName = inferLayer(proposedPath, config, args.root) || 'PresentationAdapters';
|
|
770
|
+
return {
|
|
771
|
+
content: [
|
|
772
|
+
{
|
|
773
|
+
type: 'text',
|
|
774
|
+
text: JSON.stringify(
|
|
775
|
+
{
|
|
776
|
+
filePath: proposedPath,
|
|
777
|
+
proposed: true,
|
|
778
|
+
description: description.trim(),
|
|
779
|
+
layer: layerName,
|
|
780
|
+
governed: Boolean(inferLayer(proposedPath, config, args.root)),
|
|
781
|
+
note:
|
|
782
|
+
'filePath was omitted — proposed a conventional path from description. ' +
|
|
783
|
+
'Pass filePath explicitly for authoritative placement. Then validate_code the snippet.',
|
|
784
|
+
},
|
|
785
|
+
null,
|
|
786
|
+
2
|
|
787
|
+
),
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
isError: false,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
744
793
|
if (typeof filePath !== 'string' || !filePath) {
|
|
745
|
-
return {
|
|
794
|
+
return {
|
|
795
|
+
content: [
|
|
796
|
+
{
|
|
797
|
+
type: 'text',
|
|
798
|
+
text:
|
|
799
|
+
'ark_place needs filePath and/or description. ' +
|
|
800
|
+
'Example: { "filePath": "src/components/Foo.tsx" } or { "description": "caption overlay UI component" }.',
|
|
801
|
+
},
|
|
802
|
+
],
|
|
803
|
+
isError: true,
|
|
804
|
+
};
|
|
746
805
|
}
|
|
747
806
|
const layerName = inferLayer(filePath, config, args.root);
|
|
748
807
|
if (!layerName) {
|
|
@@ -814,12 +873,50 @@ async function main() {
|
|
|
814
873
|
};
|
|
815
874
|
}
|
|
816
875
|
|
|
876
|
+
function runSuggestIncludeTool() {
|
|
877
|
+
try {
|
|
878
|
+
const workspaces = detectWorkspaces(args.root);
|
|
879
|
+
const tsPackages = detectTsPackageRoots(args.root);
|
|
880
|
+
const suggestedInclude = resolveIncludeRoots(args.root);
|
|
881
|
+
return {
|
|
882
|
+
content: [
|
|
883
|
+
{
|
|
884
|
+
type: 'text',
|
|
885
|
+
text: JSON.stringify(
|
|
886
|
+
{
|
|
887
|
+
ok: true,
|
|
888
|
+
workspaces,
|
|
889
|
+
tsPackages,
|
|
890
|
+
suggestedInclude:
|
|
891
|
+
suggestedInclude.length > 0
|
|
892
|
+
? suggestedInclude
|
|
893
|
+
: tsPackages.length > 0
|
|
894
|
+
? tsPackages
|
|
895
|
+
: ['src'],
|
|
896
|
+
next: 'npx ark-check --adopt-contract --write',
|
|
897
|
+
},
|
|
898
|
+
null,
|
|
899
|
+
2
|
|
900
|
+
),
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
isError: false,
|
|
904
|
+
};
|
|
905
|
+
} catch (error) {
|
|
906
|
+
return {
|
|
907
|
+
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
908
|
+
isError: true,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
817
913
|
const TOOL_HANDLERS = {
|
|
818
914
|
validate_code: runValidate,
|
|
819
915
|
ark_check: runCheckTool,
|
|
820
916
|
ark_coverage: runCoverageTool,
|
|
821
917
|
ark_place: runPlace,
|
|
822
918
|
ark_recommend: runRecommendTool,
|
|
919
|
+
ark_suggest_include: runSuggestIncludeTool,
|
|
823
920
|
};
|
|
824
921
|
|
|
825
922
|
const send = (msg) => process.stdout.write(`${JSON.stringify(msg)}\n`);
|