artifact-graph 0.8.3 → 0.8.5
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 +30 -0
- package/INSTALL.md +21 -0
- package/README.md +22 -0
- package/README.zh-CN.md +20 -0
- package/dist/cli.js +300 -116
- package/dist/index.cjs +330 -151
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +277 -101
- package/package.json +1 -1
- package/templates/git-hooks/pre-commit.sh +5 -2
package/dist/index.js
CHANGED
|
@@ -335,6 +335,41 @@ var init_glob_matcher = __esm({
|
|
|
335
335
|
}
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
+
// src/file-walker.ts
|
|
339
|
+
import { readdir } from "fs/promises";
|
|
340
|
+
import { join, relative } from "path";
|
|
341
|
+
async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
|
|
342
|
+
let entries;
|
|
343
|
+
try {
|
|
344
|
+
entries = await readDirectory(current, { withFileTypes: true });
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (current !== root && error.code === "ENOENT") {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
const files = [];
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const fullPath = join(current, entry.name);
|
|
357
|
+
if (entry.isDirectory()) {
|
|
358
|
+
files.push(...await walkFiles(root, fullPath, readDirectory));
|
|
359
|
+
} else {
|
|
360
|
+
files.push(relative(root, fullPath).split("\\").join("/"));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return files;
|
|
364
|
+
}
|
|
365
|
+
var defaultReadDirectory;
|
|
366
|
+
var init_file_walker = __esm({
|
|
367
|
+
"src/file-walker.ts"() {
|
|
368
|
+
"use strict";
|
|
369
|
+
defaultReadDirectory = readdir;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
338
373
|
// src/target-selector.ts
|
|
339
374
|
function parseTargetSelector(value) {
|
|
340
375
|
const separator = value.indexOf(":");
|
|
@@ -849,7 +884,7 @@ var init_packet_assembler = __esm({
|
|
|
849
884
|
|
|
850
885
|
// src/packet-audit.ts
|
|
851
886
|
import { mkdir, writeFile } from "fs/promises";
|
|
852
|
-
import { join } from "path";
|
|
887
|
+
import { join as join2 } from "path";
|
|
853
888
|
function parseTargetsFile(content, schema) {
|
|
854
889
|
const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
|
|
855
890
|
const validTypesLabel = [...validTypes].join(", ");
|
|
@@ -940,7 +975,7 @@ async function auditSingleTarget(target, graph, options) {
|
|
|
940
975
|
const fmt = options.format ?? "markdown";
|
|
941
976
|
const ext = fmt === "json" ? "json" : "md";
|
|
942
977
|
const filename = `${target.type}-${target.id}.packet.${ext}`;
|
|
943
|
-
const outPath =
|
|
978
|
+
const outPath = join2(options.outDir, filename);
|
|
944
979
|
let content;
|
|
945
980
|
if (fmt === "json") {
|
|
946
981
|
content = JSON.stringify(packet, null, 2) + "\n";
|
|
@@ -1020,7 +1055,7 @@ async function auditPackets(root, targets, options, graph) {
|
|
|
1020
1055
|
...isCompact ? { summaryDetail: "compact", countsByType } : {}
|
|
1021
1056
|
};
|
|
1022
1057
|
if (options.outDir) {
|
|
1023
|
-
const summaryPath =
|
|
1058
|
+
const summaryPath = join2(options.outDir, "summary.json");
|
|
1024
1059
|
await writeFile(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
1025
1060
|
}
|
|
1026
1061
|
return summary;
|
|
@@ -1392,7 +1427,7 @@ var init_packet_prompt_validator = __esm({
|
|
|
1392
1427
|
import { createHash } from "crypto";
|
|
1393
1428
|
import { existsSync } from "fs";
|
|
1394
1429
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
1395
|
-
import { dirname, join as
|
|
1430
|
+
import { dirname, join as join3, relative as relative2 } from "path";
|
|
1396
1431
|
async function buildVersionIndex(root, graph) {
|
|
1397
1432
|
const scannedGraph = graph ?? await scanArtifacts(root);
|
|
1398
1433
|
const hashCache = /* @__PURE__ */ new Map();
|
|
@@ -1528,7 +1563,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1528
1563
|
for (const entry of lock.locks) {
|
|
1529
1564
|
if (entry.kind !== "verifies") continue;
|
|
1530
1565
|
const sourcePath = entry.source.path;
|
|
1531
|
-
const fullSourcePath =
|
|
1566
|
+
const fullSourcePath = join3(root, sourcePath);
|
|
1532
1567
|
if (!existsSync(fullSourcePath)) continue;
|
|
1533
1568
|
let liveness = livenessCache.get(sourcePath);
|
|
1534
1569
|
if (liveness === void 0) {
|
|
@@ -1540,6 +1575,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1540
1575
|
status: "orphan_lock",
|
|
1541
1576
|
edgeId: entry.edgeId,
|
|
1542
1577
|
message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
|
|
1578
|
+
severity: "warning",
|
|
1543
1579
|
artifact: entry.artifact,
|
|
1544
1580
|
source: entry.source
|
|
1545
1581
|
});
|
|
@@ -1622,14 +1658,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1622
1658
|
currentArtifactHash: targetNode.contentHash
|
|
1623
1659
|
});
|
|
1624
1660
|
}
|
|
1625
|
-
if (!existsSync(
|
|
1661
|
+
if (!existsSync(join3(root, sourceNode.path))) {
|
|
1626
1662
|
entryIssues.push({
|
|
1627
1663
|
status: "orphan_lock",
|
|
1628
1664
|
edgeId: relEdgeId,
|
|
1629
1665
|
message: `Artifact relation source file ${sourceNode.path} no longer exists`
|
|
1630
1666
|
});
|
|
1631
1667
|
}
|
|
1632
|
-
if (!existsSync(
|
|
1668
|
+
if (!existsSync(join3(root, targetNode.path))) {
|
|
1633
1669
|
entryIssues.push({
|
|
1634
1670
|
status: "orphan_lock",
|
|
1635
1671
|
edgeId: relEdgeId,
|
|
@@ -1669,7 +1705,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1669
1705
|
fresh,
|
|
1670
1706
|
totalArtifactRelationLocks: artifactRelationLocks.length,
|
|
1671
1707
|
artifactRelationFresh,
|
|
1672
|
-
issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
|
|
1708
|
+
issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
function isLivenessIssue(issue2) {
|
|
1712
|
+
return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
|
|
1713
|
+
}
|
|
1714
|
+
function versionLockIssueSeverity(issue2) {
|
|
1715
|
+
if (issue2.severity) {
|
|
1716
|
+
return issue2.severity;
|
|
1717
|
+
}
|
|
1718
|
+
if (issue2.status === "missing_lock") {
|
|
1719
|
+
return "warning";
|
|
1720
|
+
}
|
|
1721
|
+
if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
|
|
1722
|
+
return "warning";
|
|
1723
|
+
}
|
|
1724
|
+
return "error";
|
|
1725
|
+
}
|
|
1726
|
+
function isVersionLockIssueBlocking(issue2, strictMissingLock) {
|
|
1727
|
+
if (issue2.status === "missing_lock") {
|
|
1728
|
+
return strictMissingLock;
|
|
1729
|
+
}
|
|
1730
|
+
return versionLockIssueSeverity(issue2) === "error";
|
|
1731
|
+
}
|
|
1732
|
+
function versionLockIssueRemediation(issue2) {
|
|
1733
|
+
switch (issue2.status) {
|
|
1734
|
+
case "artifact_changed":
|
|
1735
|
+
return [
|
|
1736
|
+
"\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
|
|
1737
|
+
"\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
|
|
1738
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
|
|
1739
|
+
];
|
|
1740
|
+
case "source_changed":
|
|
1741
|
+
return [
|
|
1742
|
+
"\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
|
|
1743
|
+
"\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
|
|
1744
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
|
|
1745
|
+
];
|
|
1746
|
+
case "verified_by_changed":
|
|
1747
|
+
return [
|
|
1748
|
+
"\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
|
|
1749
|
+
"\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
|
|
1750
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
|
|
1751
|
+
];
|
|
1752
|
+
case "missing_lock":
|
|
1753
|
+
return [
|
|
1754
|
+
"\u65B0\u589E\u8FFD\u6EAF\u8FB9\u8FD8\u6CA1\u6709\u5BF9\u5E94\u7248\u672C\u9501\uFF1B\u9ED8\u8BA4\u4E0D\u963B\u65AD\uFF0C`--strict-missing-lock` \u4E0B\u4F1A\u963B\u65AD\u3002",
|
|
1755
|
+
"\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown` \u4E3A\u53D7\u5F71\u54CD\u8FB9\u8865\u9501\uFF1B\u9996\u6B21\u5EFA\u7ACB\u57FA\u7EBF\u6216\u914D\u7F6E\u53D8\u66F4\u65F6\u7528 `artifact-graph version-lock refresh --all --format markdown`\u3002",
|
|
1756
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
|
|
1757
|
+
];
|
|
1758
|
+
case "orphan_lock":
|
|
1759
|
+
if (isLivenessIssue(issue2)) {
|
|
1760
|
+
return [
|
|
1761
|
+
"\u8FD9\u662F liveness \u8B66\u544A\uFF0C\u4E0D\u662F\u6821\u9A8C\u5931\u8D25\uFF1A\u9501\u4ECD\u7136\u4FDD\u7559\uFF0C\u9ED8\u8BA4\u4E0D\u963B\u65AD\u3002",
|
|
1762
|
+
"\u5982\u679C\u6D4B\u8BD5\u5DF2\u5E9F\u5F03\uFF1A\u5220\u9664\u8BE5\u6587\u4EF6\u6216\u79FB\u9664\u5176\u4E2D\u7684\u8FFD\u6EAF\u6CE8\u91CA\uFF0C\u7136\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\u5BF9\u5E94\u7684\u9501\u3002",
|
|
1763
|
+
"\u5982\u679C\u6D4B\u8BD5\u4ECD\u7136\u6709\u6548\uFF1A\u628A\u5B83\u52A0\u5165 artifact-graph.config.yaml \u4E2D\u67D0\u4E2A e2e runner \u7684 include\uFF0C\u6216\u68C0\u67E5 exclude/testIgnore \u662F\u5426\u8BEF\u4F24\u3002"
|
|
1764
|
+
];
|
|
1765
|
+
}
|
|
1766
|
+
return [
|
|
1767
|
+
"\u9501\u5F15\u7528\u7684\u5236\u54C1\u3001\u6E90\u7801\u6216\u8FFD\u6EAF\u8FB9\u5728\u5F53\u524D\u56FE\u4E2D\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5E38\u89C1\u4E8E\u5236\u54C1\u5220\u9664\u6216\u62C6\u5206\u4E4B\u540E\u3002",
|
|
1768
|
+
"\u786E\u8BA4\u5220\u9664\u662F\u6709\u610F\u7684\u540E\uFF0C\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\u5B64\u7ACB\u9501\u3002",
|
|
1769
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u88AB\u79FB\u9664\u7684\u9501\u6761\u76EE\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002",
|
|
1770
|
+
"\u5982\u679C\u5220\u9664\u662F\u8BEF\u64CD\u4F5C\uFF0C\u5148\u6062\u590D\u5BF9\u5E94\u5236\u54C1\u6216\u6E90\u7801\u6587\u4EF6\uFF0C\u518D\u8FD0\u884C\u4E0D\u5E26 `--remove-orphans` \u7684 refresh \u5237\u65B0\u54C8\u5E0C\u3002"
|
|
1771
|
+
];
|
|
1772
|
+
case "target_not_found":
|
|
1773
|
+
return [
|
|
1774
|
+
"\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
|
|
1775
|
+
"\u5982\u679C\u8BE5\u5236\u54C1\u5DF2\u88AB\u5220\u9664\u6216\u6539\u540D\uFF0C\u6539\u7528\u5F53\u524D\u5B58\u5728\u7684\u5236\u54C1\u6807\u8BC6\u91CD\u8BD5\u3002"
|
|
1776
|
+
];
|
|
1777
|
+
default:
|
|
1778
|
+
return [];
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function enrichVersionLockIssue(issue2) {
|
|
1782
|
+
return {
|
|
1783
|
+
...issue2,
|
|
1784
|
+
severity: versionLockIssueSeverity(issue2),
|
|
1785
|
+
remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
|
|
1673
1786
|
};
|
|
1674
1787
|
}
|
|
1675
1788
|
async function updateVersionLock(root, options) {
|
|
@@ -1967,59 +2080,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
|
|
|
1967
2080
|
currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
|
|
1968
2081
|
locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
|
|
1969
2082
|
artifactRelations: targetArtifactRelations,
|
|
1970
|
-
issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
|
|
2083
|
+
issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
|
|
1971
2084
|
};
|
|
1972
2085
|
}
|
|
1973
|
-
function
|
|
2086
|
+
function renderIssueBlockingTag(issue2, strictMissingLock) {
|
|
2087
|
+
return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
|
|
2088
|
+
}
|
|
2089
|
+
function renderIssueBlockingExplanation(issue2, strictMissingLock) {
|
|
2090
|
+
if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
|
|
2091
|
+
return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
|
|
2092
|
+
}
|
|
2093
|
+
if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
|
|
2094
|
+
return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
|
|
2095
|
+
}
|
|
2096
|
+
return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
|
|
2097
|
+
}
|
|
2098
|
+
function appendIssueDetails(lines, issues, strictMissingLock) {
|
|
2099
|
+
issues.forEach((issue2, index) => {
|
|
2100
|
+
const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
|
|
2101
|
+
lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
|
|
2102
|
+
lines.push("");
|
|
2103
|
+
lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
|
|
2104
|
+
lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
|
|
2105
|
+
lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
|
|
2106
|
+
const remediation = issue2.remediation ?? [];
|
|
2107
|
+
if (remediation.length > 0) {
|
|
2108
|
+
lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
|
|
2109
|
+
remediation.forEach((step, stepIndex) => {
|
|
2110
|
+
lines.push(` ${stepIndex + 1}. ${step}`);
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
lines.push("");
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
function renderVersionLockAuditMarkdown(result, options = {}) {
|
|
2117
|
+
const strictMissingLock = options.strictMissingLock === true;
|
|
2118
|
+
const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
|
|
2119
|
+
const nonBlockingCount = result.issues.length - blockingCount;
|
|
1974
2120
|
const lines = [
|
|
1975
|
-
"#
|
|
2121
|
+
"# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
|
|
1976
2122
|
"",
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2123
|
+
`- \u6839\u76EE\u5F55: \`${result.root}\``,
|
|
2124
|
+
`- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
|
|
2125
|
+
`- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
|
|
2126
|
+
`- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
|
|
2127
|
+
`- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
|
|
2128
|
+
`- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
|
|
1981
2129
|
""
|
|
1982
2130
|
];
|
|
1983
2131
|
if (result.issues.length === 0) {
|
|
1984
|
-
lines.push("
|
|
2132
|
+
lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
|
|
1985
2133
|
return `${lines.join("\n")}
|
|
1986
2134
|
`;
|
|
1987
2135
|
}
|
|
1988
|
-
|
|
1989
|
-
lines.push(
|
|
2136
|
+
if (blockingCount > 0) {
|
|
2137
|
+
lines.push(`\u5F53\u524D\u7B56\u7565\u4E0B\u5B58\u5728 ${blockingCount} \u4E2A\u963B\u65AD\u95EE\u9898\uFF0C\u547D\u4EE4\u5C06\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF1B\u8BF7\u6309\u4E0B\u65B9\u6B65\u9AA4\u9010\u9879\u5904\u7406\u3002`);
|
|
2138
|
+
} else {
|
|
2139
|
+
lines.push("\u5F53\u524D\u7B56\u7565\u4E0B\u6CA1\u6709\u963B\u65AD\u95EE\u9898\uFF0C\u4EE5\u4E0B\u4EC5\u4E3A\u63D0\u9192\uFF0C\u547D\u4EE4\u4EE5\u96F6\u9000\u51FA\u7801\u7ED3\u675F\u3002");
|
|
1990
2140
|
}
|
|
2141
|
+
lines.push("");
|
|
2142
|
+
lines.push("## \u95EE\u9898\u6E05\u5355");
|
|
2143
|
+
lines.push("");
|
|
2144
|
+
appendIssueDetails(lines, result.issues, strictMissingLock);
|
|
1991
2145
|
return `${lines.join("\n")}
|
|
1992
2146
|
`;
|
|
1993
2147
|
}
|
|
1994
2148
|
function renderVersionLockRefreshMarkdown(result) {
|
|
2149
|
+
const strictMissingLock = true;
|
|
2150
|
+
const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
|
|
2151
|
+
const nonBlockingCount = result.postAudit.issues.length - blockingCount;
|
|
1995
2152
|
const lines = [
|
|
1996
|
-
"#
|
|
2153
|
+
"# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
|
|
2154
|
+
"",
|
|
2155
|
+
`- \u6839\u76EE\u5F55: \`${result.root}\``,
|
|
2156
|
+
`- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
|
|
2157
|
+
`- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
|
|
2158
|
+
`- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: \u65B0\u589E ${result.addedLocks.length} | \u66F4\u65B0 ${result.updatedLocks.length} | \u4FDD\u7559\u5B64\u7ACB ${result.retainedOrphans.length} | \u5220\u9664\u5B64\u7ACB ${result.removedOrphans.length}`,
|
|
2159
|
+
`- \u5236\u54C1\u5173\u7CFB\u9501: \u65B0\u589E ${result.addedArtifactRelationLocks.length} | \u66F4\u65B0 ${result.updatedArtifactRelationLocks.length} | \u4FDD\u7559\u5B64\u7ACB ${result.retainedArtifactRelationOrphans.length} | \u5220\u9664 ${result.removedArtifactRelationLocks.length}`,
|
|
2160
|
+
`- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
|
|
2161
|
+
`- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
|
|
1997
2162
|
"",
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
`
|
|
2001
|
-
`
|
|
2002
|
-
`
|
|
2003
|
-
`Added: ${result.addedLocks.length} | Updated: ${result.updatedLocks.length} | Retained orphans: ${result.retainedOrphans.length} | Removed orphans: ${result.removedOrphans.length}`,
|
|
2004
|
-
`Artifact Relations \u2014 Added: ${result.addedArtifactRelationLocks.length} | Updated: ${result.updatedArtifactRelationLocks.length} | Retained orphans: ${result.retainedArtifactRelationOrphans.length} | Removed: ${result.removedArtifactRelationLocks.length}`,
|
|
2005
|
-
`Post-audit issues: ${result.postAudit.issues.length}`,
|
|
2163
|
+
"\u9501\u6587\u4EF6\u5DF2\u5199\u5165\u3002\u82E5\u9501\u6587\u4EF6\u76F8\u5BF9\u6682\u5B58\u533A\u6709\u53D8\u5316\uFF08\u4F8B\u5982 pre-commit \u573A\u666F\uFF09\uFF0C\u8BF7\u5148\u5BA1\u67E5\u5E76\u6682\u5B58\uFF1A",
|
|
2164
|
+
"",
|
|
2165
|
+
"1. `git diff artifacts/traceability-version-lock.json`",
|
|
2166
|
+
"2. `git add artifacts/traceability-version-lock.json`",
|
|
2167
|
+
"3. \u91CD\u65B0\u6267\u884C `git commit`",
|
|
2006
2168
|
""
|
|
2007
2169
|
];
|
|
2008
|
-
appendList(lines, "
|
|
2009
|
-
appendList(lines, "
|
|
2010
|
-
appendList(lines, "
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
appendList(lines, "
|
|
2016
|
-
appendList(lines, "
|
|
2170
|
+
appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
|
|
2171
|
+
appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
|
|
2172
|
+
appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
|
|
2173
|
+
if (result.retainedOrphans.length > 0) {
|
|
2174
|
+
lines.push("> \u4EE5\u4E0A\u662F\u5DF2\u5220\u9664/\u62C6\u5206\u5236\u54C1\u6216\u5931\u6548\u8FFD\u6EAF\u8FB9\u9057\u7559\u7684\u9501\u3002\u786E\u8BA4\u4E0D\u518D\u9700\u8981\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\uFF0C\u5BA1\u67E5 `git diff artifacts/traceability-version-lock.json` \u540E\u518D\u6682\u5B58\u3002");
|
|
2175
|
+
lines.push("");
|
|
2176
|
+
}
|
|
2177
|
+
appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
|
|
2178
|
+
appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
|
|
2179
|
+
appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
|
|
2180
|
+
appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
|
|
2181
|
+
if (result.retainedArtifactRelationOrphans.length > 0) {
|
|
2182
|
+
lines.push("> \u4EE5\u4E0A\u662F\u5931\u6548\u5236\u54C1\u5173\u7CFB\u9057\u7559\u7684\u9501\u3002\u786E\u8BA4\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\uFF0C\u5BA1\u67E5\u9501 diff \u540E\u518D\u6682\u5B58\u3002");
|
|
2183
|
+
lines.push("");
|
|
2184
|
+
}
|
|
2185
|
+
appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
|
|
2186
|
+
appendList(lines, "\u63D0\u9192", result.warnings);
|
|
2017
2187
|
if (result.postAudit.issues.length > 0) {
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2188
|
+
if (blockingCount > 0) {
|
|
2189
|
+
lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
|
|
2190
|
+
} else {
|
|
2191
|
+
lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
|
|
2021
2192
|
}
|
|
2022
2193
|
lines.push("");
|
|
2194
|
+
appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
|
|
2023
2195
|
}
|
|
2024
2196
|
return `${lines.join("\n")}
|
|
2025
2197
|
`;
|
|
@@ -2066,7 +2238,7 @@ function renderTraceVersionMarkdown(result) {
|
|
|
2066
2238
|
async function readVersionLock(root, lockPath) {
|
|
2067
2239
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2068
2240
|
try {
|
|
2069
|
-
const raw = await readFile(
|
|
2241
|
+
const raw = await readFile(join3(root, safeLockPath), "utf-8");
|
|
2070
2242
|
let parsed;
|
|
2071
2243
|
try {
|
|
2072
2244
|
parsed = JSON.parse(raw);
|
|
@@ -2225,7 +2397,7 @@ function requireSafeRelativePath(value, path) {
|
|
|
2225
2397
|
}
|
|
2226
2398
|
async function writeVersionLock(root, lockPath, lock) {
|
|
2227
2399
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2228
|
-
const fullPath =
|
|
2400
|
+
const fullPath = join3(root, safeLockPath);
|
|
2229
2401
|
await mkdir2(dirname(fullPath), { recursive: true });
|
|
2230
2402
|
await writeFile2(fullPath, `${JSON.stringify(lock, null, 2)}
|
|
2231
2403
|
`);
|
|
@@ -2408,14 +2580,14 @@ async function hashRelativePath(root, path, cache) {
|
|
|
2408
2580
|
const normalized = normalizeRelativePath(root, path);
|
|
2409
2581
|
const cached = cache.get(normalized);
|
|
2410
2582
|
if (cached) return cached;
|
|
2411
|
-
const content = await readFile(
|
|
2583
|
+
const content = await readFile(join3(root, normalized));
|
|
2412
2584
|
const hash = `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
2413
2585
|
cache.set(normalized, hash);
|
|
2414
2586
|
return hash;
|
|
2415
2587
|
}
|
|
2416
2588
|
function normalizeRelativePath(root, path) {
|
|
2417
2589
|
const normalized = path.replace(/\\/g, "/");
|
|
2418
|
-
const relativePath = normalized.startsWith("/") ?
|
|
2590
|
+
const relativePath = normalized.startsWith("/") ? relative2(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
|
|
2419
2591
|
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
2420
2592
|
throw new Error(`Path is outside root: ${path}`);
|
|
2421
2593
|
}
|
|
@@ -2431,7 +2603,7 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
|
|
|
2431
2603
|
if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
|
|
2432
2604
|
return "active";
|
|
2433
2605
|
}
|
|
2434
|
-
const fullSourcePath =
|
|
2606
|
+
const fullSourcePath = join3(root, filePath);
|
|
2435
2607
|
if (!existsSync(fullSourcePath)) return "inactive";
|
|
2436
2608
|
try {
|
|
2437
2609
|
const content = await readFile(fullSourcePath, "utf-8");
|
|
@@ -2474,7 +2646,7 @@ async function isFileActiveInRunner(root, filePath, runner) {
|
|
|
2474
2646
|
function sortUnique(items) {
|
|
2475
2647
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2476
2648
|
}
|
|
2477
|
-
var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
|
|
2649
|
+
var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
|
|
2478
2650
|
var init_versioned_traceability = __esm({
|
|
2479
2651
|
"src/versioned-traceability.ts"() {
|
|
2480
2652
|
"use strict";
|
|
@@ -2483,6 +2655,16 @@ var init_versioned_traceability = __esm({
|
|
|
2483
2655
|
VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
2484
2656
|
VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
2485
2657
|
VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
2658
|
+
LIVENESS_MESSAGE_PREFIX = "Liveness:";
|
|
2659
|
+
VERSION_LOCK_STATUS_LABELS = {
|
|
2660
|
+
fresh: "\u65B0\u9C9C",
|
|
2661
|
+
target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
|
|
2662
|
+
artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
|
|
2663
|
+
source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
|
|
2664
|
+
verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
|
|
2665
|
+
missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
|
|
2666
|
+
orphan_lock: "\u5B64\u7ACB\u9501"
|
|
2667
|
+
};
|
|
2486
2668
|
}
|
|
2487
2669
|
});
|
|
2488
2670
|
|
|
@@ -2490,7 +2672,7 @@ var init_versioned_traceability = __esm({
|
|
|
2490
2672
|
import { execFile } from "child_process";
|
|
2491
2673
|
import { constants } from "fs";
|
|
2492
2674
|
import { access } from "fs/promises";
|
|
2493
|
-
import { isAbsolute, join as
|
|
2675
|
+
import { isAbsolute, join as join4, resolve } from "path";
|
|
2494
2676
|
import { promisify } from "util";
|
|
2495
2677
|
async function resolveArtifactGraphCli(root, options = {}) {
|
|
2496
2678
|
const pathCli = await findCommandOnPath("artifact-graph");
|
|
@@ -2498,7 +2680,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2498
2680
|
const candidates = [
|
|
2499
2681
|
{
|
|
2500
2682
|
source: "node_modules",
|
|
2501
|
-
path:
|
|
2683
|
+
path: join4(root, "node_modules/.bin/artifact-graph"),
|
|
2502
2684
|
exists: false
|
|
2503
2685
|
},
|
|
2504
2686
|
{
|
|
@@ -2531,8 +2713,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2531
2713
|
}
|
|
2532
2714
|
async function doctorArtifactChain(root, options = {}) {
|
|
2533
2715
|
const cli = await resolveArtifactGraphCli(root, options);
|
|
2534
|
-
const configPath =
|
|
2535
|
-
const lockPath =
|
|
2716
|
+
const configPath = join4(root, "artifact-graph.config.yaml");
|
|
2717
|
+
const lockPath = join4(root, VERSION_LOCK_PATH);
|
|
2536
2718
|
const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
|
|
2537
2719
|
const nodeCompatible = isNodeCompatible(process.versions.node);
|
|
2538
2720
|
const warnings = [
|
|
@@ -2757,7 +2939,7 @@ var init_git_hook_path = __esm({
|
|
|
2757
2939
|
import { constants as constants2 } from "fs";
|
|
2758
2940
|
import { randomUUID } from "crypto";
|
|
2759
2941
|
import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
|
|
2760
|
-
import { basename, dirname as dirname2, join as
|
|
2942
|
+
import { basename, dirname as dirname2, join as join5 } from "path";
|
|
2761
2943
|
function detectHookInterpreter(content) {
|
|
2762
2944
|
if (content.trim().length === 0) {
|
|
2763
2945
|
return "empty";
|
|
@@ -3069,7 +3251,7 @@ async function removeHookAtomically(hookPath, snapshot) {
|
|
|
3069
3251
|
}
|
|
3070
3252
|
async function writeHookAtomically(hookPath, content, snapshot, mode) {
|
|
3071
3253
|
await mkdir3(dirname2(hookPath), { recursive: true });
|
|
3072
|
-
const temporaryPath =
|
|
3254
|
+
const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
|
|
3073
3255
|
let temporaryExists = false;
|
|
3074
3256
|
try {
|
|
3075
3257
|
const temporary = await open(temporaryPath, "wx", mode);
|
|
@@ -3507,8 +3689,8 @@ var init_review_result_validator = __esm({
|
|
|
3507
3689
|
|
|
3508
3690
|
// src/contract-kernel.ts
|
|
3509
3691
|
import { createHash as createHash2 } from "crypto";
|
|
3510
|
-
import { readFile as readFile2, readdir } from "fs/promises";
|
|
3511
|
-
import { join as
|
|
3692
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
3693
|
+
import { join as join6 } from "path";
|
|
3512
3694
|
import _AjvModule from "ajv";
|
|
3513
3695
|
function isOfficialNamespace(namespace) {
|
|
3514
3696
|
return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
|
|
@@ -3837,10 +4019,10 @@ async function loadContract(contractPath, options) {
|
|
|
3837
4019
|
}
|
|
3838
4020
|
async function loadContractsFromDirectory(contractsDir, options) {
|
|
3839
4021
|
const contracts = [];
|
|
3840
|
-
const entries = await
|
|
4022
|
+
const entries = await readdir2(contractsDir, { withFileTypes: true });
|
|
3841
4023
|
for (const entry of entries) {
|
|
3842
4024
|
if (entry.isDirectory()) {
|
|
3843
|
-
const schemaPath =
|
|
4025
|
+
const schemaPath = join6(contractsDir, entry.name, "schema.json");
|
|
3844
4026
|
const contract = await loadContract(schemaPath, options);
|
|
3845
4027
|
contracts.push(contract);
|
|
3846
4028
|
}
|
|
@@ -4285,8 +4467,8 @@ import Database from "better-sqlite3";
|
|
|
4285
4467
|
import matter from "gray-matter";
|
|
4286
4468
|
import yaml from "js-yaml";
|
|
4287
4469
|
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
4288
|
-
import { mkdir as mkdir4, readFile as readFile3, readdir as
|
|
4289
|
-
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as
|
|
4470
|
+
import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
|
|
4471
|
+
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
|
|
4290
4472
|
function isTargetArtifactType(type) {
|
|
4291
4473
|
return isPacketTargetType(type);
|
|
4292
4474
|
}
|
|
@@ -4328,7 +4510,7 @@ function resolveArtifactTypeName(schema, token) {
|
|
|
4328
4510
|
return void 0;
|
|
4329
4511
|
}
|
|
4330
4512
|
async function loadConfig(root) {
|
|
4331
|
-
const configPath =
|
|
4513
|
+
const configPath = join7(root, "artifact-graph.config.yaml");
|
|
4332
4514
|
let parsed = {};
|
|
4333
4515
|
try {
|
|
4334
4516
|
const raw = await readFile3(configPath, "utf-8");
|
|
@@ -4505,7 +4687,7 @@ async function scanArtifacts(root, schema) {
|
|
|
4505
4687
|
continue;
|
|
4506
4688
|
}
|
|
4507
4689
|
scannedFiles.set(file, type);
|
|
4508
|
-
const raw = await readFile3(
|
|
4690
|
+
const raw = await readFile3(join7(root, file), "utf-8");
|
|
4509
4691
|
const parsed = parseFile(type, file, raw, config);
|
|
4510
4692
|
nodes.push(...parsed.nodes);
|
|
4511
4693
|
edges.push(...parsed.edges);
|
|
@@ -4817,7 +4999,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
|
|
|
4817
4999
|
const indexPath = "artifacts/prd/feature-index.md";
|
|
4818
5000
|
let raw = "";
|
|
4819
5001
|
try {
|
|
4820
|
-
raw = await readFile3(
|
|
5002
|
+
raw = await readFile3(join7(root, indexPath), "utf-8");
|
|
4821
5003
|
} catch (error) {
|
|
4822
5004
|
if (error.code === "ENOENT") {
|
|
4823
5005
|
return [];
|
|
@@ -5007,11 +5189,11 @@ function nextId(graph, schema, type, rangeName) {
|
|
|
5007
5189
|
throw new Error(`ID range ${type}.${rangeName} is exhausted`);
|
|
5008
5190
|
}
|
|
5009
5191
|
async function writeGraphCache(root, graph) {
|
|
5010
|
-
const cacheDir =
|
|
5192
|
+
const cacheDir = join7(root, ".artifact-graph");
|
|
5011
5193
|
await mkdir4(cacheDir, { recursive: true });
|
|
5012
|
-
await writeFile3(
|
|
5194
|
+
await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
|
|
5013
5195
|
`);
|
|
5014
|
-
const db = new Database(
|
|
5196
|
+
const db = new Database(join7(cacheDir, "graph.sqlite"));
|
|
5015
5197
|
try {
|
|
5016
5198
|
db.exec(`
|
|
5017
5199
|
DROP TABLE IF EXISTS nodes;
|
|
@@ -6694,10 +6876,10 @@ function validateE2eRegistry(graph) {
|
|
|
6694
6876
|
async function validateExecutableTraceability(root, config) {
|
|
6695
6877
|
const issues = [];
|
|
6696
6878
|
const schema = config ?? await loadConfig(root);
|
|
6697
|
-
const e2eDir =
|
|
6879
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
6698
6880
|
let e2eFiles;
|
|
6699
6881
|
try {
|
|
6700
|
-
e2eFiles = (await
|
|
6882
|
+
e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
|
|
6701
6883
|
} catch {
|
|
6702
6884
|
return [];
|
|
6703
6885
|
}
|
|
@@ -6707,7 +6889,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6707
6889
|
const mdBatches = /* @__PURE__ */ new Set();
|
|
6708
6890
|
for (const filePath of e2eFiles) {
|
|
6709
6891
|
const raw = await readFile3(filePath, "utf-8");
|
|
6710
|
-
const relPath =
|
|
6892
|
+
const relPath = relative3(root, filePath).split("\\").join("/");
|
|
6711
6893
|
const parsed = matter(raw);
|
|
6712
6894
|
const data = parsed.data;
|
|
6713
6895
|
const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
@@ -6735,7 +6917,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6735
6917
|
}
|
|
6736
6918
|
}
|
|
6737
6919
|
}
|
|
6738
|
-
const allFiles = await
|
|
6920
|
+
const allFiles = await walkFiles(root);
|
|
6739
6921
|
const specFiles = /* @__PURE__ */ new Set();
|
|
6740
6922
|
const configuredRunners = schema.e2e?.runners ?? [];
|
|
6741
6923
|
if (configuredRunners.length > 0) {
|
|
@@ -6755,7 +6937,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6755
6937
|
const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
|
|
6756
6938
|
const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
|
|
6757
6939
|
for (const specFile of specFiles) {
|
|
6758
|
-
const fullSpecPath =
|
|
6940
|
+
const fullSpecPath = join7(root, specFile);
|
|
6759
6941
|
let content;
|
|
6760
6942
|
try {
|
|
6761
6943
|
content = await readFile3(fullSpecPath, "utf-8");
|
|
@@ -6764,14 +6946,16 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6764
6946
|
}
|
|
6765
6947
|
const level = detectTestLevel(specFile, content);
|
|
6766
6948
|
const specLines = content.split(/\r?\n/);
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6949
|
+
const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
|
|
6950
|
+
for (const comment of lineComments) {
|
|
6951
|
+
const lineIndex = comment.lineNumber - 1;
|
|
6952
|
+
const commentText = comment.text.replace(/^!/, "");
|
|
6953
|
+
let match = tcAnnotationRegex.exec(`//${commentText}`);
|
|
6770
6954
|
let annotatedLevel = "";
|
|
6771
6955
|
if (match) {
|
|
6772
6956
|
annotatedLevel = match[2];
|
|
6773
6957
|
} else {
|
|
6774
|
-
match = tcAnnotationNoLevelRegex.exec(
|
|
6958
|
+
match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
|
|
6775
6959
|
}
|
|
6776
6960
|
if (!match) {
|
|
6777
6961
|
continue;
|
|
@@ -6798,7 +6982,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6798
6982
|
level: effectiveLevel,
|
|
6799
6983
|
file: specFile,
|
|
6800
6984
|
testName,
|
|
6801
|
-
line:
|
|
6985
|
+
line: comment.lineNumber
|
|
6802
6986
|
};
|
|
6803
6987
|
const key = `${batch}:${tcId}`;
|
|
6804
6988
|
const existing = refToSource.get(key) ?? [];
|
|
@@ -6835,7 +7019,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6835
7019
|
if (entry.testId) {
|
|
6836
7020
|
let content;
|
|
6837
7021
|
try {
|
|
6838
|
-
content = await readFile3(
|
|
7022
|
+
content = await readFile3(join7(root, normalizedRefFile), "utf-8");
|
|
6839
7023
|
} catch {
|
|
6840
7024
|
continue;
|
|
6841
7025
|
}
|
|
@@ -7012,11 +7196,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7012
7196
|
let withExecutableRef = 0;
|
|
7013
7197
|
const statusBreakdown = {};
|
|
7014
7198
|
const chainTypeBreakdown = {};
|
|
7015
|
-
const e2eDir =
|
|
7199
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
7016
7200
|
const tcFieldsMap = /* @__PURE__ */ new Map();
|
|
7017
7201
|
let e2eFiles;
|
|
7018
7202
|
try {
|
|
7019
|
-
e2eFiles = (await
|
|
7203
|
+
e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
|
|
7020
7204
|
} catch {
|
|
7021
7205
|
e2eFiles = [];
|
|
7022
7206
|
}
|
|
@@ -7083,7 +7267,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7083
7267
|
}
|
|
7084
7268
|
}
|
|
7085
7269
|
const runners = (await loadConfig(root)).e2e?.runners ?? [];
|
|
7086
|
-
const allProjectFiles = await
|
|
7270
|
+
const allProjectFiles = await walkFiles(root);
|
|
7087
7271
|
for (const node of e2eNodes) {
|
|
7088
7272
|
const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
|
|
7089
7273
|
const status = String(fields["status"] ?? "").trim().toLowerCase();
|
|
@@ -7094,7 +7278,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7094
7278
|
let hasActiveE2eRef = false;
|
|
7095
7279
|
for (const entry of parseExecutableRefLines(execRef)) {
|
|
7096
7280
|
const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
|
|
7097
|
-
if (!normalized || !existsSync2(
|
|
7281
|
+
if (!normalized || !existsSync2(join7(root, normalized))) continue;
|
|
7098
7282
|
const accepting = await getAcceptingRunners(root, normalized, runners);
|
|
7099
7283
|
if (accepting.some((runner) => runner.kind === "e2e")) {
|
|
7100
7284
|
hasActiveE2eRef = true;
|
|
@@ -7153,7 +7337,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7153
7337
|
const acCoverageRateByFeature = {};
|
|
7154
7338
|
const featureAcMap = /* @__PURE__ */ new Map();
|
|
7155
7339
|
for (const node of featureNodes) {
|
|
7156
|
-
const acs = parseAcceptanceCriteria(await readFile3(
|
|
7340
|
+
const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
|
|
7157
7341
|
featureAcMap.set(node.code, new Set(acs));
|
|
7158
7342
|
}
|
|
7159
7343
|
const coveredAcByFeature = /* @__PURE__ */ new Map();
|
|
@@ -7195,10 +7379,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7195
7379
|
};
|
|
7196
7380
|
}
|
|
7197
7381
|
async function generateE2eRegistry(root, opts) {
|
|
7198
|
-
const e2eDir =
|
|
7382
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
7199
7383
|
let files;
|
|
7200
7384
|
try {
|
|
7201
|
-
files = (await
|
|
7385
|
+
files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
|
|
7202
7386
|
} catch {
|
|
7203
7387
|
return {
|
|
7204
7388
|
registry_version: "1.0",
|
|
@@ -7211,7 +7395,7 @@ async function generateE2eRegistry(root, opts) {
|
|
|
7211
7395
|
const batches = [];
|
|
7212
7396
|
let totalTestCases = 0;
|
|
7213
7397
|
for (const file of files) {
|
|
7214
|
-
const filePath =
|
|
7398
|
+
const filePath = join7(e2eDir, file);
|
|
7215
7399
|
const raw = await readFile3(filePath, "utf-8");
|
|
7216
7400
|
const parsed = matter(raw);
|
|
7217
7401
|
const data = parsed.data;
|
|
@@ -7339,7 +7523,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
|
|
|
7339
7523
|
if (!normalizedPath) {
|
|
7340
7524
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
7341
7525
|
}
|
|
7342
|
-
const fullPath =
|
|
7526
|
+
const fullPath = join7(root, normalizedPath);
|
|
7343
7527
|
let content;
|
|
7344
7528
|
try {
|
|
7345
7529
|
content = await readFile3(fullPath, "utf-8");
|
|
@@ -7451,7 +7635,7 @@ function escapeRegExp(value) {
|
|
|
7451
7635
|
}
|
|
7452
7636
|
async function hasMarkdownTc(tcKey, e2eDir) {
|
|
7453
7637
|
const [batch, tcId] = tcKey.split(":");
|
|
7454
|
-
const filePath =
|
|
7638
|
+
const filePath = join7(e2eDir, `${batch}.md`);
|
|
7455
7639
|
try {
|
|
7456
7640
|
const raw = await readFile3(filePath, "utf-8");
|
|
7457
7641
|
const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
|
|
@@ -7461,7 +7645,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
|
|
|
7461
7645
|
}
|
|
7462
7646
|
}
|
|
7463
7647
|
async function findFiles(root, patterns) {
|
|
7464
|
-
const all = await
|
|
7648
|
+
const all = await walkFiles(root);
|
|
7465
7649
|
const matched = /* @__PURE__ */ new Set();
|
|
7466
7650
|
for (const pattern of patterns) {
|
|
7467
7651
|
for (const file of all) {
|
|
@@ -7472,21 +7656,9 @@ async function findFiles(root, patterns) {
|
|
|
7472
7656
|
}
|
|
7473
7657
|
return [...matched].sort();
|
|
7474
7658
|
}
|
|
7475
|
-
|
|
7476
|
-
const
|
|
7477
|
-
|
|
7478
|
-
for (const entry of entries) {
|
|
7479
|
-
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
7480
|
-
continue;
|
|
7481
|
-
}
|
|
7482
|
-
const fullPath = join6(current, entry.name);
|
|
7483
|
-
if (entry.isDirectory()) {
|
|
7484
|
-
files.push(...await walk(root, fullPath));
|
|
7485
|
-
} else {
|
|
7486
|
-
files.push(relative2(root, fullPath).split("\\").join("/"));
|
|
7487
|
-
}
|
|
7488
|
-
}
|
|
7489
|
-
return files;
|
|
7659
|
+
function matchesConfiguredArtifactPath(path, schema) {
|
|
7660
|
+
const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7661
|
+
return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
|
|
7490
7662
|
}
|
|
7491
7663
|
function matchesPattern(file, pattern) {
|
|
7492
7664
|
if (!pattern.includes("*")) {
|
|
@@ -7963,7 +8135,7 @@ function resolveArtifactContext(graph, opts) {
|
|
|
7963
8135
|
}
|
|
7964
8136
|
if (root) {
|
|
7965
8137
|
for (const ap of ALWAYS_PRESENT_ITEMS) {
|
|
7966
|
-
const fullPath =
|
|
8138
|
+
const fullPath = join7(root, ap.path);
|
|
7967
8139
|
let stat;
|
|
7968
8140
|
try {
|
|
7969
8141
|
stat = statSync(fullPath);
|
|
@@ -8199,6 +8371,7 @@ var init_index = __esm({
|
|
|
8199
8371
|
init_packet_constants();
|
|
8200
8372
|
init_packet_validator();
|
|
8201
8373
|
init_glob_matcher();
|
|
8374
|
+
init_file_walker();
|
|
8202
8375
|
init_packet_constants();
|
|
8203
8376
|
init_target_selector();
|
|
8204
8377
|
init_packet_assembler();
|
|
@@ -8343,10 +8516,12 @@ export {
|
|
|
8343
8516
|
isPacketTargetType,
|
|
8344
8517
|
isPacketTargetTypeDynamic,
|
|
8345
8518
|
isTargetArtifactType,
|
|
8519
|
+
isVersionLockIssueBlocking,
|
|
8346
8520
|
loadConfig,
|
|
8347
8521
|
loadContract,
|
|
8348
8522
|
loadContractCatalog,
|
|
8349
8523
|
loadContractsFromDirectory,
|
|
8524
|
+
matchesConfiguredArtifactPath,
|
|
8350
8525
|
nextId,
|
|
8351
8526
|
normalizeE2eLegacyArtifact,
|
|
8352
8527
|
normalizeToCanonical,
|
|
@@ -8383,5 +8558,6 @@ export {
|
|
|
8383
8558
|
validateScenarioPrdLinkIndex,
|
|
8384
8559
|
validateScenarioPrdLinks,
|
|
8385
8560
|
verifyDigest,
|
|
8561
|
+
versionLockIssueSeverity,
|
|
8386
8562
|
writeGraphCache
|
|
8387
8563
|
};
|