android2harmony 0.1.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 +407 -0
- package/agents/build-fixer.md +385 -0
- package/agents/code-reviewer.md +478 -0
- package/agents/logic-coder.md +212 -0
- package/agents/logic-context-builder.md +199 -0
- package/agents/scripts/hmos-precheck/arkts.mjs +534 -0
- package/agents/scripts/hmos-precheck/codelinter.mjs +362 -0
- package/agents/scripts/hmos-precheck/index.mjs +926 -0
- package/agents/scripts/platform_query.mjs +535 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +7 -0
- package/package.json +29 -0
- package/skills/hmos-batch-ui-align/SKILL.md +141 -0
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +217 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
- package/skills/hmos-batch-ui-align/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
- package/skills/hmos-batch-ui-align/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
- package/skills/hmos-batch-ui-align/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
- package/skills/hmos-batch-ui-align/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
- package/skills/hmos-batch-ui-align/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
- package/skills/hmos-batch-ui-align/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
- package/skills/hmos-batch-ui-align/scripts/android_parse_fast.ts +1750 -0
- package/skills/hmos-convert-pipeline/SKILL.md +446 -0
- package/skills/hmos-fix-build-errors/SKILL.md +281 -0
- package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +219 -0
- package/skills/hmos-fix-build-errors/references/known-patterns.md +157 -0
- package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +131 -0
- package/skills/hmos-resources-convert/SKILL.md +654 -0
- package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
- package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
- package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
- package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
- package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
- package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
- package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
- package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
- package/skills/hmos-spec-generate/SKILL.md +413 -0
- package/skills/hmos-spec-generate/references/android-platform-tokens.md +105 -0
- package/skills/hmos-spec-generate/references/spec-sample-1.md +78 -0
- package/skills/hmos-spec-generate/references/spec-sample-2.md +58 -0
- package/skills/hmos-spec-generate/references/spec-sample-3.md +116 -0
- package/skills/hmos-spec-generate/references/step4-report-template.md +33 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
4
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
async function exists(pathname) {
|
|
7
|
+
try {
|
|
8
|
+
await access(pathname, fsConstants.R_OK);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function runCommand(command, args, cwd, timeoutSeconds) {
|
|
15
|
+
return await new Promise((resolve) => {
|
|
16
|
+
const proc = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
17
|
+
let stdout = "";
|
|
18
|
+
let stderr = "";
|
|
19
|
+
let timedOut = false;
|
|
20
|
+
let settled = false;
|
|
21
|
+
const timer = timeoutSeconds && timeoutSeconds > 0 ? setTimeout(() => {
|
|
22
|
+
timedOut = true;
|
|
23
|
+
proc.kill("SIGKILL");
|
|
24
|
+
}, timeoutSeconds * 1e3) : null;
|
|
25
|
+
proc.stdout.on("data", (chunk) => {
|
|
26
|
+
stdout += String(chunk);
|
|
27
|
+
});
|
|
28
|
+
proc.stderr.on("data", (chunk) => {
|
|
29
|
+
stderr += String(chunk);
|
|
30
|
+
});
|
|
31
|
+
proc.on("close", (code) => {
|
|
32
|
+
if (settled) return;
|
|
33
|
+
settled = true;
|
|
34
|
+
if (timer) clearTimeout(timer);
|
|
35
|
+
resolve({ code: code ?? 1, stdout, stderr, timedOut });
|
|
36
|
+
});
|
|
37
|
+
proc.on("error", (error) => {
|
|
38
|
+
if (settled) return;
|
|
39
|
+
settled = true;
|
|
40
|
+
if (timer) clearTimeout(timer);
|
|
41
|
+
stderr += error instanceof Error ? error.message : String(error);
|
|
42
|
+
resolve({ code: 1, stdout, stderr, timedOut });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function clearArtifacts(...paths) {
|
|
47
|
+
await Promise.all(paths.map(async (pathname) => {
|
|
48
|
+
await rm(pathname, { force: true });
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
function candidateRoots(commandlineTools) {
|
|
52
|
+
const resolved = path.resolve(commandlineTools);
|
|
53
|
+
return [.../* @__PURE__ */ new Set([resolved, path.dirname(resolved)])];
|
|
54
|
+
}
|
|
55
|
+
async function findCodelinterTool(commandlineTools) {
|
|
56
|
+
if (!commandlineTools) return null;
|
|
57
|
+
const suffixes = process.platform.startsWith("win") ? ["codelinter.bat", "codelinter.cmd"] : ["codelinter"];
|
|
58
|
+
const candidates = [];
|
|
59
|
+
for (const root of candidateRoots(commandlineTools)) {
|
|
60
|
+
for (const suffix of suffixes) {
|
|
61
|
+
candidates.push(
|
|
62
|
+
path.join(root, "bin", suffix),
|
|
63
|
+
path.join(root, "codelinter", "bin", suffix)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const candidate of candidates) {
|
|
68
|
+
if (await exists(candidate)) return path.resolve(candidate);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
async function runGit(projectRoot, args) {
|
|
73
|
+
return await runCommand("git", args, projectRoot);
|
|
74
|
+
}
|
|
75
|
+
async function changedEtsFiles(projectRoot, baseRef, headRef) {
|
|
76
|
+
const args = ["diff", "--name-only", baseRef];
|
|
77
|
+
if (headRef) {
|
|
78
|
+
args.push(headRef);
|
|
79
|
+
}
|
|
80
|
+
args.push("--", "*.ets");
|
|
81
|
+
const result = await runGit(projectRoot, args);
|
|
82
|
+
if (result.code !== 0) {
|
|
83
|
+
throw new Error(`git diff failed: ${(result.stderr || result.stdout || `exit_code=${result.code}`).trim()}`);
|
|
84
|
+
}
|
|
85
|
+
const files = [];
|
|
86
|
+
const seen = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
88
|
+
if (!line || !line.endsWith(".ets")) continue;
|
|
89
|
+
const absolutePath = path.resolve(projectRoot, line);
|
|
90
|
+
if (!await exists(absolutePath) || seen.has(absolutePath)) continue;
|
|
91
|
+
seen.add(absolutePath);
|
|
92
|
+
files.push(absolutePath);
|
|
93
|
+
}
|
|
94
|
+
if (headRef === null) {
|
|
95
|
+
const untracked = await runGit(projectRoot, ["ls-files", "--others", "--exclude-standard", "--", "*.ets"]);
|
|
96
|
+
if (untracked.code !== 0) {
|
|
97
|
+
throw new Error(`git ls-files failed: ${(untracked.stderr || untracked.stdout || `exit_code=${untracked.code}`).trim()}`);
|
|
98
|
+
}
|
|
99
|
+
for (const line of untracked.stdout.split(/\r?\n/)) {
|
|
100
|
+
if (!line || !line.endsWith(".ets")) continue;
|
|
101
|
+
const absolutePath = path.resolve(projectRoot, line);
|
|
102
|
+
if (!await exists(absolutePath) || seen.has(absolutePath)) continue;
|
|
103
|
+
seen.add(absolutePath);
|
|
104
|
+
files.push(absolutePath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return files;
|
|
108
|
+
}
|
|
109
|
+
function parseUnifiedZeroHunks(diffText) {
|
|
110
|
+
const changedRanges = {};
|
|
111
|
+
let currentFile = null;
|
|
112
|
+
for (const line of diffText.split(/\r?\n/)) {
|
|
113
|
+
if (line.startsWith("+++ b/")) {
|
|
114
|
+
currentFile = line.replace("+++ b/", "");
|
|
115
|
+
if (!changedRanges[currentFile]) changedRanges[currentFile] = [];
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!currentFile || !line.startsWith("@@")) continue;
|
|
119
|
+
const match = /@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
120
|
+
if (!match) continue;
|
|
121
|
+
const start = Number.parseInt(match[1], 10);
|
|
122
|
+
const count = match[2] !== undefined ? Number.parseInt(match[2], 10) : 1;
|
|
123
|
+
if (count > 0) changedRanges[currentFile].push([start, start + count - 1]);
|
|
124
|
+
}
|
|
125
|
+
return changedRanges;
|
|
126
|
+
}
|
|
127
|
+
async function changedEtsLineRanges(projectRoot, baseRef, headRef) {
|
|
128
|
+
const args = ["diff", "--unified=0", baseRef];
|
|
129
|
+
if (headRef) args.push(headRef);
|
|
130
|
+
args.push("--", "*.ets");
|
|
131
|
+
const result = await runGit(projectRoot, args);
|
|
132
|
+
if (result.code !== 0) {
|
|
133
|
+
throw new Error(`git diff failed: ${(result.stderr || result.stdout || `exit_code=${result.code}`).trim()}`);
|
|
134
|
+
}
|
|
135
|
+
const parsed = parseUnifiedZeroHunks(result.stdout);
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
function lineInRanges(line, ranges) {
|
|
139
|
+
if (line === null) return false;
|
|
140
|
+
return ranges.some(([start, end]) => start <= line && line <= end);
|
|
141
|
+
}
|
|
142
|
+
function normalizeSeverity(value) {
|
|
143
|
+
const severity = String(value ?? "").toLowerCase();
|
|
144
|
+
return severity === "warn" ? "warning" : severity;
|
|
145
|
+
}
|
|
146
|
+
function relativeFile(projectRoot, rawPath) {
|
|
147
|
+
const absolute = path.isAbsolute(rawPath) ? rawPath : path.resolve(projectRoot, rawPath);
|
|
148
|
+
if (!absolute.startsWith(path.resolve(projectRoot) + path.sep)) {
|
|
149
|
+
return rawPath.replace(/\\/g, "/");
|
|
150
|
+
}
|
|
151
|
+
return path.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
152
|
+
}
|
|
153
|
+
async function parseReport(reportPath) {
|
|
154
|
+
if (!await exists(reportPath)) return [];
|
|
155
|
+
try {
|
|
156
|
+
const payload = JSON.parse(await readFile(reportPath, "utf8"));
|
|
157
|
+
if (Array.isArray(payload)) return payload.filter((item) => item && typeof item === "object");
|
|
158
|
+
if (payload && typeof payload === "object") return [payload];
|
|
159
|
+
return [];
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function writeSummary(payload, pathname, maxFindings = 30) {
|
|
165
|
+
const diagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
|
|
166
|
+
const errors = diagnostics.filter((item) => item.severity === "error");
|
|
167
|
+
const warnings = diagnostics.filter((item) => item.severity === "warning");
|
|
168
|
+
const suggestions = diagnostics.filter((item) => item.severity === "suggestion");
|
|
169
|
+
const lines = [
|
|
170
|
+
"# CodeLinter Diff Summary",
|
|
171
|
+
"",
|
|
172
|
+
`- status: ${String(payload.tool_status ?? "")}`,
|
|
173
|
+
`- checked_files: ${Array.isArray(payload.checked_files) ? payload.checked_files.length : 0}`,
|
|
174
|
+
`- errors: ${errors.length}`,
|
|
175
|
+
`- warnings_hidden_from_fixer: ${warnings.length}`,
|
|
176
|
+
`- suggestions_hidden_from_fixer: ${suggestions.length}`,
|
|
177
|
+
"",
|
|
178
|
+
"## Blocking Errors"
|
|
179
|
+
];
|
|
180
|
+
if (!errors.length) lines.push("- none");
|
|
181
|
+
for (const item of errors.slice(0, maxFindings)) {
|
|
182
|
+
lines.push(`- ${String(item.file ?? "<unknown>")}:${String(item.line ?? "?")}:${String(item.column ?? "?")} [${String(item.rule || "no-rule")}] ${String(item.message ?? "")}`);
|
|
183
|
+
}
|
|
184
|
+
await writeFile(pathname, `${lines.join("\n")}
|
|
185
|
+
`, "utf8");
|
|
186
|
+
}
|
|
187
|
+
function unavailable(reason, checkedFiles, changedLineRanges, elapsedMs, failureRule) {
|
|
188
|
+
const payload = {
|
|
189
|
+
tool: "codelinter",
|
|
190
|
+
tool_status: "unavailable",
|
|
191
|
+
evidence_status: "partial",
|
|
192
|
+
success: true,
|
|
193
|
+
checked_files: checkedFiles,
|
|
194
|
+
diagnostics: [],
|
|
195
|
+
summary: { errorCount: 0, warnCount: 0, suggestionCount: 0 },
|
|
196
|
+
reason,
|
|
197
|
+
scope: "diff",
|
|
198
|
+
changed_files_only: true,
|
|
199
|
+
changed_line_ranges: Object.fromEntries(Object.entries(changedLineRanges).map(([file, ranges]) => [file, ranges.map(([start, end]) => [start, end])]))
|
|
200
|
+
};
|
|
201
|
+
if (failureRule) payload.failure_rule = failureRule;
|
|
202
|
+
if (elapsedMs !== void 0) payload.elapsed_ms = elapsedMs;
|
|
203
|
+
return payload;
|
|
204
|
+
}
|
|
205
|
+
function buildArtifactPaths(derivedPath, rawPath, summaryPath, stdoutPath, stderrPath) {
|
|
206
|
+
return {
|
|
207
|
+
json: derivedPath,
|
|
208
|
+
raw_json: rawPath,
|
|
209
|
+
summary: summaryPath,
|
|
210
|
+
stdout: stdoutPath,
|
|
211
|
+
stderr: stderrPath
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function diagnosticsFromReport(parsedReport, projectRoot, checkedFileList, changedLineRanges) {
|
|
215
|
+
const diagnostics = [];
|
|
216
|
+
const checkedFiles = new Set(checkedFileList);
|
|
217
|
+
let malformed = false;
|
|
218
|
+
for (const entry of parsedReport) {
|
|
219
|
+
const relative = relativeFile(projectRoot, String(entry.filePath ?? entry.file ?? ""));
|
|
220
|
+
if (!checkedFiles.has(relative)) continue;
|
|
221
|
+
const ranges = changedLineRanges[relative];
|
|
222
|
+
if (entry.messages !== void 0 && !Array.isArray(entry.messages)) {
|
|
223
|
+
malformed = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const rawMessage of Array.isArray(entry.messages) ? entry.messages : []) {
|
|
227
|
+
if (!rawMessage || typeof rawMessage !== "object" || Array.isArray(rawMessage)) {
|
|
228
|
+
malformed = true;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const message = rawMessage;
|
|
232
|
+
const line = message.line == null ? null : Number(message.line);
|
|
233
|
+
if (Array.isArray(ranges) && ranges.length === 0) continue;
|
|
234
|
+
if (Array.isArray(ranges) && line !== null && !lineInRanges(line, ranges)) continue;
|
|
235
|
+
diagnostics.push({
|
|
236
|
+
file: relative,
|
|
237
|
+
line,
|
|
238
|
+
column: message.column == null ? null : Number(message.column),
|
|
239
|
+
severity: normalizeSeverity(message.severity),
|
|
240
|
+
message: String(message.message ?? ""),
|
|
241
|
+
rule: String(message.rule ?? "")
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { diagnostics, checkedFiles: [...checkedFiles].sort(), malformed };
|
|
246
|
+
}
|
|
247
|
+
async function runCodelinterDiffScan(options) {
|
|
248
|
+
const started = Date.now();
|
|
249
|
+
const rawReportPath = path.join(options.validationOutputDir, "codelinter-raw-report.json");
|
|
250
|
+
const derivedReportPath = path.join(options.validationOutputDir, "codelinter-derived.json");
|
|
251
|
+
const stdoutPath = path.join(options.validationOutputDir, "codelinter-stdout.txt");
|
|
252
|
+
const stderrPath = path.join(options.validationOutputDir, "codelinter-stderr.txt");
|
|
253
|
+
const summaryPath = path.join(options.validationOutputDir, "codelinter-fix-summary.md");
|
|
254
|
+
await mkdir(options.validationOutputDir, { recursive: true });
|
|
255
|
+
await clearArtifacts(rawReportPath, derivedReportPath, stdoutPath, stderrPath, summaryPath);
|
|
256
|
+
let payload;
|
|
257
|
+
try {
|
|
258
|
+
const files = await changedEtsFiles(options.projectRoot, options.baseRef ?? "HEAD", options.headRef ?? null);
|
|
259
|
+
const changedLineRanges = await changedEtsLineRanges(options.projectRoot, options.baseRef ?? "HEAD", options.headRef ?? null);
|
|
260
|
+
if (!files.length) {
|
|
261
|
+
payload = {
|
|
262
|
+
tool: "codelinter",
|
|
263
|
+
tool_status: "ok",
|
|
264
|
+
success: true,
|
|
265
|
+
checked_files: [],
|
|
266
|
+
diagnostics: [],
|
|
267
|
+
summary: { errorCount: 0, warnCount: 0, suggestionCount: 0 },
|
|
268
|
+
reason: "no changed ETS files",
|
|
269
|
+
scope: "diff",
|
|
270
|
+
changed_files_only: true,
|
|
271
|
+
changed_line_ranges: {}
|
|
272
|
+
};
|
|
273
|
+
} else {
|
|
274
|
+
const codelinter = await findCodelinterTool(options.commandlineTools);
|
|
275
|
+
const checkedFileList = files.map((file) => path.relative(options.projectRoot, file).replace(/\\/g, "/"));
|
|
276
|
+
if (!codelinter) {
|
|
277
|
+
payload = unavailable("codelinter not found", checkedFileList, changedLineRanges, void 0, "codelinter-missing");
|
|
278
|
+
} else {
|
|
279
|
+
const command = [
|
|
280
|
+
"--format",
|
|
281
|
+
"json",
|
|
282
|
+
"--output",
|
|
283
|
+
rawReportPath,
|
|
284
|
+
"--exit-on",
|
|
285
|
+
"error"
|
|
286
|
+
];
|
|
287
|
+
if (options.incremental) command.push("--incremental");
|
|
288
|
+
command.push(...checkedFileList);
|
|
289
|
+
const result = await runCommand(codelinter, command, options.projectRoot, options.timeoutSeconds ?? 30);
|
|
290
|
+
await writeFile(stdoutPath, result.stdout || "", "utf8");
|
|
291
|
+
await writeFile(stderrPath, result.stderr || "", "utf8");
|
|
292
|
+
if (result.timedOut) {
|
|
293
|
+
payload = unavailable(`codelinter timed out after ${options.timeoutSeconds ?? 30}s`, checkedFileList, changedLineRanges, Date.now() - started, "codelinter-timeout");
|
|
294
|
+
payload.incremental = Boolean(options.incremental);
|
|
295
|
+
payload.timeout_seconds = options.timeoutSeconds ?? 30;
|
|
296
|
+
} else {
|
|
297
|
+
const parsedReport = await parseReport(rawReportPath);
|
|
298
|
+
if (parsedReport === null) {
|
|
299
|
+
payload = unavailable("codelinter produced an unreadable JSON report", checkedFileList, changedLineRanges, Date.now() - started, "codelinter-report-invalid");
|
|
300
|
+
payload.exit_code = result.code;
|
|
301
|
+
payload.incremental = Boolean(options.incremental);
|
|
302
|
+
payload.timeout_seconds = options.timeoutSeconds ?? 30;
|
|
303
|
+
} else {
|
|
304
|
+
const { diagnostics, checkedFiles, malformed } = diagnosticsFromReport(parsedReport, options.projectRoot, checkedFileList, changedLineRanges);
|
|
305
|
+
if (malformed) {
|
|
306
|
+
payload = unavailable("codelinter produced a malformed JSON report", checkedFileList, changedLineRanges, Date.now() - started, "codelinter-report-invalid");
|
|
307
|
+
payload.exit_code = result.code;
|
|
308
|
+
payload.incremental = Boolean(options.incremental);
|
|
309
|
+
payload.timeout_seconds = options.timeoutSeconds ?? 30;
|
|
310
|
+
} else {
|
|
311
|
+
const toolStatus = await exists(rawReportPath) ? "ok" : "internal_error";
|
|
312
|
+
payload = {
|
|
313
|
+
tool: "codelinter",
|
|
314
|
+
tool_status: toolStatus,
|
|
315
|
+
evidence_status: toolStatus === "ok" ? "raw_structured" : "partial",
|
|
316
|
+
success: toolStatus === "ok" && !diagnostics.some((item) => item.severity === "error"),
|
|
317
|
+
checked_files: checkedFiles,
|
|
318
|
+
diagnostics,
|
|
319
|
+
summary: {
|
|
320
|
+
errorCount: diagnostics.filter((item) => item.severity === "error").length,
|
|
321
|
+
warnCount: diagnostics.filter((item) => item.severity === "warning").length,
|
|
322
|
+
suggestionCount: diagnostics.filter((item) => item.severity === "suggestion").length
|
|
323
|
+
},
|
|
324
|
+
reason: toolStatus === "ok" ? null : `codelinter exited without report (exit_code=${result.code})`,
|
|
325
|
+
failure_rule: toolStatus === "ok" ? null : "codelinter-report-missing",
|
|
326
|
+
scope: "diff",
|
|
327
|
+
changed_files_only: true,
|
|
328
|
+
changed_line_ranges: Object.fromEntries(Object.entries(changedLineRanges).map(([file, ranges]) => [file, ranges.map(([start, end]) => [start, end])])),
|
|
329
|
+
exit_code: result.code,
|
|
330
|
+
elapsed_ms: Date.now() - started,
|
|
331
|
+
incremental: Boolean(options.incremental),
|
|
332
|
+
timeout_seconds: options.timeoutSeconds ?? 30
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
payload = unavailable(
|
|
341
|
+
error instanceof Error ? error.message : String(error),
|
|
342
|
+
[],
|
|
343
|
+
{},
|
|
344
|
+
Date.now() - started,
|
|
345
|
+
"git-diff-failed"
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
payload.artifact_paths = buildArtifactPaths(derivedReportPath, rawReportPath, summaryPath, stdoutPath, stderrPath);
|
|
349
|
+
await writeFile(derivedReportPath, `${JSON.stringify(payload, null, 2)}
|
|
350
|
+
`, "utf8");
|
|
351
|
+
await writeSummary(payload, summaryPath);
|
|
352
|
+
return payload;
|
|
353
|
+
}
|
|
354
|
+
const __test__ = {
|
|
355
|
+
parseUnifiedZeroHunks,
|
|
356
|
+
relativeFile
|
|
357
|
+
};
|
|
358
|
+
export {
|
|
359
|
+
__test__,
|
|
360
|
+
findCodelinterTool,
|
|
361
|
+
runCodelinterDiffScan
|
|
362
|
+
};
|