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.cjs
CHANGED
|
@@ -362,6 +362,41 @@ var init_glob_matcher = __esm({
|
|
|
362
362
|
}
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
// src/file-walker.ts
|
|
366
|
+
async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
|
|
367
|
+
let entries;
|
|
368
|
+
try {
|
|
369
|
+
entries = await readDirectory(current, { withFileTypes: true });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
if (current !== root && error.code === "ENOENT") {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
const files = [];
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const fullPath = (0, import_node_path.join)(current, entry.name);
|
|
382
|
+
if (entry.isDirectory()) {
|
|
383
|
+
files.push(...await walkFiles(root, fullPath, readDirectory));
|
|
384
|
+
} else {
|
|
385
|
+
files.push((0, import_node_path.relative)(root, fullPath).split("\\").join("/"));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return files;
|
|
389
|
+
}
|
|
390
|
+
var import_promises, import_node_path, defaultReadDirectory;
|
|
391
|
+
var init_file_walker = __esm({
|
|
392
|
+
"src/file-walker.ts"() {
|
|
393
|
+
"use strict";
|
|
394
|
+
import_promises = require("fs/promises");
|
|
395
|
+
import_node_path = require("path");
|
|
396
|
+
defaultReadDirectory = import_promises.readdir;
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
365
400
|
// src/target-selector.ts
|
|
366
401
|
function parseTargetSelector(value) {
|
|
367
402
|
const separator = value.indexOf(":");
|
|
@@ -965,14 +1000,14 @@ async function auditSingleTarget(target, graph, options) {
|
|
|
965
1000
|
const fmt = options.format ?? "markdown";
|
|
966
1001
|
const ext = fmt === "json" ? "json" : "md";
|
|
967
1002
|
const filename = `${target.type}-${target.id}.packet.${ext}`;
|
|
968
|
-
const outPath = (0,
|
|
1003
|
+
const outPath = (0, import_node_path2.join)(options.outDir, filename);
|
|
969
1004
|
let content;
|
|
970
1005
|
if (fmt === "json") {
|
|
971
1006
|
content = JSON.stringify(packet, null, 2) + "\n";
|
|
972
1007
|
} else {
|
|
973
1008
|
content = renderPacketMarkdown(packet);
|
|
974
1009
|
}
|
|
975
|
-
await (0,
|
|
1010
|
+
await (0, import_promises2.writeFile)(outPath, content, "utf-8");
|
|
976
1011
|
entry.outputPath = outPath;
|
|
977
1012
|
}
|
|
978
1013
|
} catch (error) {
|
|
@@ -993,7 +1028,7 @@ async function auditPackets(root, targets, options, graph) {
|
|
|
993
1028
|
}
|
|
994
1029
|
}
|
|
995
1030
|
if (options.outDir) {
|
|
996
|
-
await (0,
|
|
1031
|
+
await (0, import_promises2.mkdir)(options.outDir, { recursive: true });
|
|
997
1032
|
}
|
|
998
1033
|
const sampleSet = options.sampleTargets ? new Set(options.sampleTargets) : null;
|
|
999
1034
|
const sampleOutputPaths = [];
|
|
@@ -1045,8 +1080,8 @@ async function auditPackets(root, targets, options, graph) {
|
|
|
1045
1080
|
...isCompact ? { summaryDetail: "compact", countsByType } : {}
|
|
1046
1081
|
};
|
|
1047
1082
|
if (options.outDir) {
|
|
1048
|
-
const summaryPath = (0,
|
|
1049
|
-
await (0,
|
|
1083
|
+
const summaryPath = (0, import_node_path2.join)(options.outDir, "summary.json");
|
|
1084
|
+
await (0, import_promises2.writeFile)(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
1050
1085
|
}
|
|
1051
1086
|
return summary;
|
|
1052
1087
|
}
|
|
@@ -1072,14 +1107,14 @@ async function discoverAndAuditPackets(root, options) {
|
|
|
1072
1107
|
universalBaseline: effectiveBaseline
|
|
1073
1108
|
}, graph);
|
|
1074
1109
|
}
|
|
1075
|
-
var
|
|
1110
|
+
var import_promises2, import_node_path2, VALID_TYPES, VALID_TYPES_LABEL;
|
|
1076
1111
|
var init_packet_audit = __esm({
|
|
1077
1112
|
"src/packet-audit.ts"() {
|
|
1078
1113
|
"use strict";
|
|
1079
1114
|
init_index();
|
|
1080
1115
|
init_packet_validator();
|
|
1081
|
-
|
|
1082
|
-
|
|
1116
|
+
import_promises2 = require("fs/promises");
|
|
1117
|
+
import_node_path2 = require("path");
|
|
1083
1118
|
VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
|
|
1084
1119
|
VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
|
|
1085
1120
|
}
|
|
@@ -1551,7 +1586,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1551
1586
|
for (const entry of lock.locks) {
|
|
1552
1587
|
if (entry.kind !== "verifies") continue;
|
|
1553
1588
|
const sourcePath = entry.source.path;
|
|
1554
|
-
const fullSourcePath = (0,
|
|
1589
|
+
const fullSourcePath = (0, import_node_path3.join)(root, sourcePath);
|
|
1555
1590
|
if (!(0, import_node_fs.existsSync)(fullSourcePath)) continue;
|
|
1556
1591
|
let liveness = livenessCache.get(sourcePath);
|
|
1557
1592
|
if (liveness === void 0) {
|
|
@@ -1563,6 +1598,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1563
1598
|
status: "orphan_lock",
|
|
1564
1599
|
edgeId: entry.edgeId,
|
|
1565
1600
|
message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
|
|
1601
|
+
severity: "warning",
|
|
1566
1602
|
artifact: entry.artifact,
|
|
1567
1603
|
source: entry.source
|
|
1568
1604
|
});
|
|
@@ -1645,14 +1681,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1645
1681
|
currentArtifactHash: targetNode.contentHash
|
|
1646
1682
|
});
|
|
1647
1683
|
}
|
|
1648
|
-
if (!(0, import_node_fs.existsSync)((0,
|
|
1684
|
+
if (!(0, import_node_fs.existsSync)((0, import_node_path3.join)(root, sourceNode.path))) {
|
|
1649
1685
|
entryIssues.push({
|
|
1650
1686
|
status: "orphan_lock",
|
|
1651
1687
|
edgeId: relEdgeId,
|
|
1652
1688
|
message: `Artifact relation source file ${sourceNode.path} no longer exists`
|
|
1653
1689
|
});
|
|
1654
1690
|
}
|
|
1655
|
-
if (!(0, import_node_fs.existsSync)((0,
|
|
1691
|
+
if (!(0, import_node_fs.existsSync)((0, import_node_path3.join)(root, targetNode.path))) {
|
|
1656
1692
|
entryIssues.push({
|
|
1657
1693
|
status: "orphan_lock",
|
|
1658
1694
|
edgeId: relEdgeId,
|
|
@@ -1692,7 +1728,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1692
1728
|
fresh,
|
|
1693
1729
|
totalArtifactRelationLocks: artifactRelationLocks.length,
|
|
1694
1730
|
artifactRelationFresh,
|
|
1695
|
-
issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
|
|
1731
|
+
issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
function isLivenessIssue(issue2) {
|
|
1735
|
+
return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
|
|
1736
|
+
}
|
|
1737
|
+
function versionLockIssueSeverity(issue2) {
|
|
1738
|
+
if (issue2.severity) {
|
|
1739
|
+
return issue2.severity;
|
|
1740
|
+
}
|
|
1741
|
+
if (issue2.status === "missing_lock") {
|
|
1742
|
+
return "warning";
|
|
1743
|
+
}
|
|
1744
|
+
if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
|
|
1745
|
+
return "warning";
|
|
1746
|
+
}
|
|
1747
|
+
return "error";
|
|
1748
|
+
}
|
|
1749
|
+
function isVersionLockIssueBlocking(issue2, strictMissingLock) {
|
|
1750
|
+
if (issue2.status === "missing_lock") {
|
|
1751
|
+
return strictMissingLock;
|
|
1752
|
+
}
|
|
1753
|
+
return versionLockIssueSeverity(issue2) === "error";
|
|
1754
|
+
}
|
|
1755
|
+
function versionLockIssueRemediation(issue2) {
|
|
1756
|
+
switch (issue2.status) {
|
|
1757
|
+
case "artifact_changed":
|
|
1758
|
+
return [
|
|
1759
|
+
"\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
|
|
1760
|
+
"\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",
|
|
1761
|
+
"\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"
|
|
1762
|
+
];
|
|
1763
|
+
case "source_changed":
|
|
1764
|
+
return [
|
|
1765
|
+
"\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
|
|
1766
|
+
"\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",
|
|
1767
|
+
"\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"
|
|
1768
|
+
];
|
|
1769
|
+
case "verified_by_changed":
|
|
1770
|
+
return [
|
|
1771
|
+
"\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
|
|
1772
|
+
"\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",
|
|
1773
|
+
"\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"
|
|
1774
|
+
];
|
|
1775
|
+
case "missing_lock":
|
|
1776
|
+
return [
|
|
1777
|
+
"\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",
|
|
1778
|
+
"\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",
|
|
1779
|
+
"\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
|
|
1780
|
+
];
|
|
1781
|
+
case "orphan_lock":
|
|
1782
|
+
if (isLivenessIssue(issue2)) {
|
|
1783
|
+
return [
|
|
1784
|
+
"\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",
|
|
1785
|
+
"\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",
|
|
1786
|
+
"\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"
|
|
1787
|
+
];
|
|
1788
|
+
}
|
|
1789
|
+
return [
|
|
1790
|
+
"\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",
|
|
1791
|
+
"\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",
|
|
1792
|
+
"\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",
|
|
1793
|
+
"\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"
|
|
1794
|
+
];
|
|
1795
|
+
case "target_not_found":
|
|
1796
|
+
return [
|
|
1797
|
+
"\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
|
|
1798
|
+
"\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"
|
|
1799
|
+
];
|
|
1800
|
+
default:
|
|
1801
|
+
return [];
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function enrichVersionLockIssue(issue2) {
|
|
1805
|
+
return {
|
|
1806
|
+
...issue2,
|
|
1807
|
+
severity: versionLockIssueSeverity(issue2),
|
|
1808
|
+
remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
|
|
1696
1809
|
};
|
|
1697
1810
|
}
|
|
1698
1811
|
async function updateVersionLock(root, options) {
|
|
@@ -1990,59 +2103,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
|
|
|
1990
2103
|
currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
|
|
1991
2104
|
locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
|
|
1992
2105
|
artifactRelations: targetArtifactRelations,
|
|
1993
|
-
issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
|
|
2106
|
+
issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
|
|
1994
2107
|
};
|
|
1995
2108
|
}
|
|
1996
|
-
function
|
|
2109
|
+
function renderIssueBlockingTag(issue2, strictMissingLock) {
|
|
2110
|
+
return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
|
|
2111
|
+
}
|
|
2112
|
+
function renderIssueBlockingExplanation(issue2, strictMissingLock) {
|
|
2113
|
+
if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
|
|
2114
|
+
return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
|
|
2115
|
+
}
|
|
2116
|
+
if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
|
|
2117
|
+
return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
|
|
2118
|
+
}
|
|
2119
|
+
return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
|
|
2120
|
+
}
|
|
2121
|
+
function appendIssueDetails(lines, issues, strictMissingLock) {
|
|
2122
|
+
issues.forEach((issue2, index) => {
|
|
2123
|
+
const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
|
|
2124
|
+
lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
|
|
2125
|
+
lines.push("");
|
|
2126
|
+
lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
|
|
2127
|
+
lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
|
|
2128
|
+
lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
|
|
2129
|
+
const remediation = issue2.remediation ?? [];
|
|
2130
|
+
if (remediation.length > 0) {
|
|
2131
|
+
lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
|
|
2132
|
+
remediation.forEach((step, stepIndex) => {
|
|
2133
|
+
lines.push(` ${stepIndex + 1}. ${step}`);
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
lines.push("");
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
function renderVersionLockAuditMarkdown(result, options = {}) {
|
|
2140
|
+
const strictMissingLock = options.strictMissingLock === true;
|
|
2141
|
+
const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
|
|
2142
|
+
const nonBlockingCount = result.issues.length - blockingCount;
|
|
1997
2143
|
const lines = [
|
|
1998
|
-
"#
|
|
2144
|
+
"# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
|
|
1999
2145
|
"",
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2146
|
+
`- \u6839\u76EE\u5F55: \`${result.root}\``,
|
|
2147
|
+
`- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
|
|
2148
|
+
`- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
|
|
2149
|
+
`- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
|
|
2150
|
+
`- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
|
|
2151
|
+
`- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
|
|
2004
2152
|
""
|
|
2005
2153
|
];
|
|
2006
2154
|
if (result.issues.length === 0) {
|
|
2007
|
-
lines.push("
|
|
2155
|
+
lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
|
|
2008
2156
|
return `${lines.join("\n")}
|
|
2009
2157
|
`;
|
|
2010
2158
|
}
|
|
2011
|
-
|
|
2012
|
-
lines.push(
|
|
2159
|
+
if (blockingCount > 0) {
|
|
2160
|
+
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`);
|
|
2161
|
+
} else {
|
|
2162
|
+
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");
|
|
2013
2163
|
}
|
|
2164
|
+
lines.push("");
|
|
2165
|
+
lines.push("## \u95EE\u9898\u6E05\u5355");
|
|
2166
|
+
lines.push("");
|
|
2167
|
+
appendIssueDetails(lines, result.issues, strictMissingLock);
|
|
2014
2168
|
return `${lines.join("\n")}
|
|
2015
2169
|
`;
|
|
2016
2170
|
}
|
|
2017
2171
|
function renderVersionLockRefreshMarkdown(result) {
|
|
2172
|
+
const strictMissingLock = true;
|
|
2173
|
+
const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
|
|
2174
|
+
const nonBlockingCount = result.postAudit.issues.length - blockingCount;
|
|
2018
2175
|
const lines = [
|
|
2019
|
-
"#
|
|
2176
|
+
"# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
|
|
2177
|
+
"",
|
|
2178
|
+
`- \u6839\u76EE\u5F55: \`${result.root}\``,
|
|
2179
|
+
`- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
|
|
2180
|
+
`- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
|
|
2181
|
+
`- \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}`,
|
|
2182
|
+
`- \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}`,
|
|
2183
|
+
`- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
|
|
2184
|
+
`- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
|
|
2185
|
+
"",
|
|
2186
|
+
"\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",
|
|
2020
2187
|
"",
|
|
2021
|
-
`
|
|
2022
|
-
`
|
|
2023
|
-
`
|
|
2024
|
-
`Changed paths: ${result.changedPaths.length}`,
|
|
2025
|
-
`Affected edges: ${result.affectedEdges.length}`,
|
|
2026
|
-
`Added: ${result.addedLocks.length} | Updated: ${result.updatedLocks.length} | Retained orphans: ${result.retainedOrphans.length} | Removed orphans: ${result.removedOrphans.length}`,
|
|
2027
|
-
`Artifact Relations \u2014 Added: ${result.addedArtifactRelationLocks.length} | Updated: ${result.updatedArtifactRelationLocks.length} | Retained orphans: ${result.retainedArtifactRelationOrphans.length} | Removed: ${result.removedArtifactRelationLocks.length}`,
|
|
2028
|
-
`Post-audit issues: ${result.postAudit.issues.length}`,
|
|
2188
|
+
"1. `git diff artifacts/traceability-version-lock.json`",
|
|
2189
|
+
"2. `git add artifacts/traceability-version-lock.json`",
|
|
2190
|
+
"3. \u91CD\u65B0\u6267\u884C `git commit`",
|
|
2029
2191
|
""
|
|
2030
2192
|
];
|
|
2031
|
-
appendList(lines, "
|
|
2032
|
-
appendList(lines, "
|
|
2033
|
-
appendList(lines, "
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
appendList(lines, "
|
|
2039
|
-
appendList(lines, "
|
|
2193
|
+
appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
|
|
2194
|
+
appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
|
|
2195
|
+
appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
|
|
2196
|
+
if (result.retainedOrphans.length > 0) {
|
|
2197
|
+
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");
|
|
2198
|
+
lines.push("");
|
|
2199
|
+
}
|
|
2200
|
+
appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
|
|
2201
|
+
appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
|
|
2202
|
+
appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
|
|
2203
|
+
appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
|
|
2204
|
+
if (result.retainedArtifactRelationOrphans.length > 0) {
|
|
2205
|
+
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");
|
|
2206
|
+
lines.push("");
|
|
2207
|
+
}
|
|
2208
|
+
appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
|
|
2209
|
+
appendList(lines, "\u63D0\u9192", result.warnings);
|
|
2040
2210
|
if (result.postAudit.issues.length > 0) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2211
|
+
if (blockingCount > 0) {
|
|
2212
|
+
lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
|
|
2213
|
+
} else {
|
|
2214
|
+
lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
|
|
2044
2215
|
}
|
|
2045
2216
|
lines.push("");
|
|
2217
|
+
appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
|
|
2046
2218
|
}
|
|
2047
2219
|
return `${lines.join("\n")}
|
|
2048
2220
|
`;
|
|
@@ -2089,7 +2261,7 @@ function renderTraceVersionMarkdown(result) {
|
|
|
2089
2261
|
async function readVersionLock(root, lockPath) {
|
|
2090
2262
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2091
2263
|
try {
|
|
2092
|
-
const raw = await (0,
|
|
2264
|
+
const raw = await (0, import_promises3.readFile)((0, import_node_path3.join)(root, safeLockPath), "utf-8");
|
|
2093
2265
|
let parsed;
|
|
2094
2266
|
try {
|
|
2095
2267
|
parsed = JSON.parse(raw);
|
|
@@ -2248,9 +2420,9 @@ function requireSafeRelativePath(value, path) {
|
|
|
2248
2420
|
}
|
|
2249
2421
|
async function writeVersionLock(root, lockPath, lock) {
|
|
2250
2422
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2251
|
-
const fullPath = (0,
|
|
2252
|
-
await (0,
|
|
2253
|
-
await (0,
|
|
2423
|
+
const fullPath = (0, import_node_path3.join)(root, safeLockPath);
|
|
2424
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(fullPath), { recursive: true });
|
|
2425
|
+
await (0, import_promises3.writeFile)(fullPath, `${JSON.stringify(lock, null, 2)}
|
|
2254
2426
|
`);
|
|
2255
2427
|
}
|
|
2256
2428
|
function implementationEdges(index) {
|
|
@@ -2431,14 +2603,14 @@ async function hashRelativePath(root, path, cache) {
|
|
|
2431
2603
|
const normalized = normalizeRelativePath(root, path);
|
|
2432
2604
|
const cached = cache.get(normalized);
|
|
2433
2605
|
if (cached) return cached;
|
|
2434
|
-
const content = await (0,
|
|
2606
|
+
const content = await (0, import_promises3.readFile)((0, import_node_path3.join)(root, normalized));
|
|
2435
2607
|
const hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(content).digest("hex")}`;
|
|
2436
2608
|
cache.set(normalized, hash);
|
|
2437
2609
|
return hash;
|
|
2438
2610
|
}
|
|
2439
2611
|
function normalizeRelativePath(root, path) {
|
|
2440
2612
|
const normalized = path.replace(/\\/g, "/");
|
|
2441
|
-
const relativePath = normalized.startsWith("/") ? (0,
|
|
2613
|
+
const relativePath = normalized.startsWith("/") ? (0, import_node_path3.relative)(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
|
|
2442
2614
|
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
2443
2615
|
throw new Error(`Path is outside root: ${path}`);
|
|
2444
2616
|
}
|
|
@@ -2454,10 +2626,10 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
|
|
|
2454
2626
|
if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
|
|
2455
2627
|
return "active";
|
|
2456
2628
|
}
|
|
2457
|
-
const fullSourcePath = (0,
|
|
2629
|
+
const fullSourcePath = (0, import_node_path3.join)(root, filePath);
|
|
2458
2630
|
if (!(0, import_node_fs.existsSync)(fullSourcePath)) return "inactive";
|
|
2459
2631
|
try {
|
|
2460
|
-
const content = await (0,
|
|
2632
|
+
const content = await (0, import_promises3.readFile)(fullSourcePath, "utf-8");
|
|
2461
2633
|
return /\/\/!?\s*@(?:e2e_test|tc)\s+/.test(content) ? "active" : "inactive";
|
|
2462
2634
|
} catch {
|
|
2463
2635
|
return "inactive";
|
|
@@ -2497,19 +2669,29 @@ async function isFileActiveInRunner(root, filePath, runner) {
|
|
|
2497
2669
|
function sortUnique(items) {
|
|
2498
2670
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2499
2671
|
}
|
|
2500
|
-
var import_node_crypto, import_node_fs,
|
|
2672
|
+
var import_node_crypto, import_node_fs, import_promises3, import_node_path3, VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
|
|
2501
2673
|
var init_versioned_traceability = __esm({
|
|
2502
2674
|
"src/versioned-traceability.ts"() {
|
|
2503
2675
|
"use strict";
|
|
2504
2676
|
import_node_crypto = require("crypto");
|
|
2505
2677
|
import_node_fs = require("fs");
|
|
2506
|
-
|
|
2507
|
-
|
|
2678
|
+
import_promises3 = require("fs/promises");
|
|
2679
|
+
import_node_path3 = require("path");
|
|
2508
2680
|
init_index();
|
|
2509
2681
|
init_glob_matcher();
|
|
2510
2682
|
VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
2511
2683
|
VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
2512
2684
|
VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
2685
|
+
LIVENESS_MESSAGE_PREFIX = "Liveness:";
|
|
2686
|
+
VERSION_LOCK_STATUS_LABELS = {
|
|
2687
|
+
fresh: "\u65B0\u9C9C",
|
|
2688
|
+
target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
|
|
2689
|
+
artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
|
|
2690
|
+
source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
|
|
2691
|
+
verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
|
|
2692
|
+
missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
|
|
2693
|
+
orphan_lock: "\u5B64\u7ACB\u9501"
|
|
2694
|
+
};
|
|
2513
2695
|
}
|
|
2514
2696
|
});
|
|
2515
2697
|
|
|
@@ -2520,7 +2702,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2520
2702
|
const candidates = [
|
|
2521
2703
|
{
|
|
2522
2704
|
source: "node_modules",
|
|
2523
|
-
path: (0,
|
|
2705
|
+
path: (0, import_node_path4.join)(root, "node_modules/.bin/artifact-graph"),
|
|
2524
2706
|
exists: false
|
|
2525
2707
|
},
|
|
2526
2708
|
{
|
|
@@ -2553,8 +2735,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2553
2735
|
}
|
|
2554
2736
|
async function doctorArtifactChain(root, options = {}) {
|
|
2555
2737
|
const cli = await resolveArtifactGraphCli(root, options);
|
|
2556
|
-
const configPath = (0,
|
|
2557
|
-
const lockPath = (0,
|
|
2738
|
+
const configPath = (0, import_node_path4.join)(root, "artifact-graph.config.yaml");
|
|
2739
|
+
const lockPath = (0, import_node_path4.join)(root, VERSION_LOCK_PATH);
|
|
2558
2740
|
const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
|
|
2559
2741
|
const nodeCompatible = isNodeCompatible(process.versions.node);
|
|
2560
2742
|
const warnings = [
|
|
@@ -2627,7 +2809,7 @@ ${maybe.stderr ?? ""}`;
|
|
|
2627
2809
|
}
|
|
2628
2810
|
async function pathExists(path) {
|
|
2629
2811
|
try {
|
|
2630
|
-
await (0,
|
|
2812
|
+
await (0, import_promises4.access)(path, import_node_fs2.constants.R_OK);
|
|
2631
2813
|
return true;
|
|
2632
2814
|
} catch {
|
|
2633
2815
|
return false;
|
|
@@ -2643,20 +2825,20 @@ async function findCommandOnPath(command) {
|
|
|
2643
2825
|
}
|
|
2644
2826
|
}
|
|
2645
2827
|
function resolveCandidatePath(root, candidatePath) {
|
|
2646
|
-
return (0,
|
|
2828
|
+
return (0, import_node_path4.isAbsolute)(candidatePath) ? candidatePath : (0, import_node_path4.resolve)(root, candidatePath);
|
|
2647
2829
|
}
|
|
2648
2830
|
function isNodeCompatible(version) {
|
|
2649
2831
|
const major = Number(version.split(".")[0]);
|
|
2650
2832
|
return Number.isFinite(major) && major >= 22;
|
|
2651
2833
|
}
|
|
2652
|
-
var import_node_child_process, import_node_fs2,
|
|
2834
|
+
var import_node_child_process, import_node_fs2, import_promises4, import_node_path4, import_node_util, execFileAsync, KNOWN_COMMANDS;
|
|
2653
2835
|
var init_cli_resolver = __esm({
|
|
2654
2836
|
"src/cli-resolver.ts"() {
|
|
2655
2837
|
"use strict";
|
|
2656
2838
|
import_node_child_process = require("child_process");
|
|
2657
2839
|
import_node_fs2 = require("fs");
|
|
2658
|
-
|
|
2659
|
-
|
|
2840
|
+
import_promises4 = require("fs/promises");
|
|
2841
|
+
import_node_path4 = require("path");
|
|
2660
2842
|
import_node_util = require("util");
|
|
2661
2843
|
init_versioned_traceability();
|
|
2662
2844
|
execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
@@ -2767,14 +2949,14 @@ async function resolveGitHookPath(root, hookName) {
|
|
|
2767
2949
|
]);
|
|
2768
2950
|
const value = stdout.trim();
|
|
2769
2951
|
if (!value) throw new Error(`Git returned an empty hook path for ${hookName}`);
|
|
2770
|
-
return (0,
|
|
2952
|
+
return (0, import_node_path5.isAbsolute)(value) ? (0, import_node_path5.resolve)(value) : (0, import_node_path5.resolve)(root, value);
|
|
2771
2953
|
}
|
|
2772
|
-
var import_node_child_process3,
|
|
2954
|
+
var import_node_child_process3, import_node_path5, import_node_util3, execFileAsync3;
|
|
2773
2955
|
var init_git_hook_path = __esm({
|
|
2774
2956
|
"src/git-hook-path.ts"() {
|
|
2775
2957
|
"use strict";
|
|
2776
2958
|
import_node_child_process3 = require("child_process");
|
|
2777
|
-
|
|
2959
|
+
import_node_path5 = require("path");
|
|
2778
2960
|
import_node_util3 = require("util");
|
|
2779
2961
|
execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
2780
2962
|
}
|
|
@@ -2997,7 +3179,7 @@ async function readHookSnapshot(hookPath) {
|
|
|
2997
3179
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2998
3180
|
let metadata;
|
|
2999
3181
|
try {
|
|
3000
|
-
metadata = await (0,
|
|
3182
|
+
metadata = await (0, import_promises5.lstat)(hookPath, { bigint: true });
|
|
3001
3183
|
} catch (error) {
|
|
3002
3184
|
if (error.code === "ENOENT") {
|
|
3003
3185
|
return { kind: "missing", bytes: Buffer.alloc(0) };
|
|
@@ -3013,7 +3195,7 @@ async function readHookSnapshot(hookPath) {
|
|
|
3013
3195
|
size: metadata.size,
|
|
3014
3196
|
mtimeNs: metadata.mtimeNs,
|
|
3015
3197
|
mode: metadata.mode,
|
|
3016
|
-
linkTarget: await (0,
|
|
3198
|
+
linkTarget: await (0, import_promises5.readlink)(hookPath)
|
|
3017
3199
|
};
|
|
3018
3200
|
}
|
|
3019
3201
|
if (!metadata.isFile()) {
|
|
@@ -3029,7 +3211,7 @@ async function readHookSnapshot(hookPath) {
|
|
|
3029
3211
|
}
|
|
3030
3212
|
let handle;
|
|
3031
3213
|
try {
|
|
3032
|
-
handle = await (0,
|
|
3214
|
+
handle = await (0, import_promises5.open)(hookPath, import_node_fs3.constants.O_RDONLY | (import_node_fs3.constants.O_NOFOLLOW ?? 0));
|
|
3033
3215
|
const opened = await handle.stat({ bigint: true });
|
|
3034
3216
|
if (opened.dev !== metadata.dev || opened.ino !== metadata.ino) {
|
|
3035
3217
|
await handle.close();
|
|
@@ -3087,15 +3269,15 @@ async function removeHookAtomically(hookPath, snapshot) {
|
|
|
3087
3269
|
throw new ConcurrentHookModificationError(hookPath);
|
|
3088
3270
|
}
|
|
3089
3271
|
if (current.kind !== "missing") {
|
|
3090
|
-
await (0,
|
|
3272
|
+
await (0, import_promises5.unlink)(hookPath);
|
|
3091
3273
|
}
|
|
3092
3274
|
}
|
|
3093
3275
|
async function writeHookAtomically(hookPath, content, snapshot, mode) {
|
|
3094
|
-
await (0,
|
|
3095
|
-
const temporaryPath = (0,
|
|
3276
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(hookPath), { recursive: true });
|
|
3277
|
+
const temporaryPath = (0, import_node_path6.join)((0, import_node_path6.dirname)(hookPath), `.${(0, import_node_path6.basename)(hookPath)}.${(0, import_node_crypto2.randomUUID)()}.tmp`);
|
|
3096
3278
|
let temporaryExists = false;
|
|
3097
3279
|
try {
|
|
3098
|
-
const temporary = await (0,
|
|
3280
|
+
const temporary = await (0, import_promises5.open)(temporaryPath, "wx", mode);
|
|
3099
3281
|
temporaryExists = true;
|
|
3100
3282
|
try {
|
|
3101
3283
|
await temporary.writeFile(content);
|
|
@@ -3108,11 +3290,11 @@ async function writeHookAtomically(hookPath, content, snapshot, mode) {
|
|
|
3108
3290
|
if (!sameHookSnapshot(snapshot, current)) {
|
|
3109
3291
|
throw new ConcurrentHookModificationError(hookPath);
|
|
3110
3292
|
}
|
|
3111
|
-
await (0,
|
|
3293
|
+
await (0, import_promises5.rename)(temporaryPath, hookPath);
|
|
3112
3294
|
temporaryExists = false;
|
|
3113
3295
|
} finally {
|
|
3114
3296
|
if (temporaryExists) {
|
|
3115
|
-
await (0,
|
|
3297
|
+
await (0, import_promises5.unlink)(temporaryPath).catch((error) => {
|
|
3116
3298
|
if (error.code !== "ENOENT") {
|
|
3117
3299
|
throw error;
|
|
3118
3300
|
}
|
|
@@ -3138,14 +3320,14 @@ function findManagedRange(content, begin, end) {
|
|
|
3138
3320
|
end: endLine < 0 ? content.length : endLine + 1
|
|
3139
3321
|
};
|
|
3140
3322
|
}
|
|
3141
|
-
var import_node_fs3, import_node_crypto2,
|
|
3323
|
+
var import_node_fs3, import_node_crypto2, import_promises5, import_node_path6, DEFAULT_MARKER_ID, MANAGED_STATE_PREFIX, UnsupportedHookInterpreterError, SymlinkHookUnsupportedError, UnsupportedHookTypeError, InvalidManagedHookStateError, ConcurrentHookModificationError, HookTransactionRollbackError;
|
|
3142
3324
|
var init_hook_installer = __esm({
|
|
3143
3325
|
"src/hook-installer.ts"() {
|
|
3144
3326
|
"use strict";
|
|
3145
3327
|
import_node_fs3 = require("fs");
|
|
3146
3328
|
import_node_crypto2 = require("crypto");
|
|
3147
|
-
|
|
3148
|
-
|
|
3329
|
+
import_promises5 = require("fs/promises");
|
|
3330
|
+
import_node_path6 = require("path");
|
|
3149
3331
|
DEFAULT_MARKER_ID = "artifact-chain-assistant";
|
|
3150
3332
|
MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
|
|
3151
3333
|
UnsupportedHookInterpreterError = class extends Error {
|
|
@@ -3812,7 +3994,7 @@ function validatePolicyCompatibility(policy, baseContract) {
|
|
|
3812
3994
|
};
|
|
3813
3995
|
}
|
|
3814
3996
|
async function loadContract(contractPath, options) {
|
|
3815
|
-
const rawContent = await (0,
|
|
3997
|
+
const rawContent = await (0, import_promises6.readFile)(contractPath, "utf-8");
|
|
3816
3998
|
let schema;
|
|
3817
3999
|
try {
|
|
3818
4000
|
schema = JSON.parse(rawContent);
|
|
@@ -3860,10 +4042,10 @@ async function loadContract(contractPath, options) {
|
|
|
3860
4042
|
}
|
|
3861
4043
|
async function loadContractsFromDirectory(contractsDir, options) {
|
|
3862
4044
|
const contracts = [];
|
|
3863
|
-
const entries = await (0,
|
|
4045
|
+
const entries = await (0, import_promises6.readdir)(contractsDir, { withFileTypes: true });
|
|
3864
4046
|
for (const entry of entries) {
|
|
3865
4047
|
if (entry.isDirectory()) {
|
|
3866
|
-
const schemaPath = (0,
|
|
4048
|
+
const schemaPath = (0, import_node_path7.join)(contractsDir, entry.name, "schema.json");
|
|
3867
4049
|
const contract = await loadContract(schemaPath, options);
|
|
3868
4050
|
contracts.push(contract);
|
|
3869
4051
|
}
|
|
@@ -3978,13 +4160,13 @@ async function loadContractCatalog(contractsDir, options) {
|
|
|
3978
4160
|
}
|
|
3979
4161
|
return catalog;
|
|
3980
4162
|
}
|
|
3981
|
-
var import_node_crypto3,
|
|
4163
|
+
var import_node_crypto3, import_promises6, import_node_path7, import_ajv, CONTRACT_ERROR_CODES, ContractError, OFFICIAL_AUTHORITY, OFFICIAL_NAMESPACE_PATTERN, ContractRegistry, _validatorCache, E2E_NORMALIZER_CONFIG, ContractCatalog;
|
|
3982
4164
|
var init_contract_kernel = __esm({
|
|
3983
4165
|
"src/contract-kernel.ts"() {
|
|
3984
4166
|
"use strict";
|
|
3985
4167
|
import_node_crypto3 = require("crypto");
|
|
3986
|
-
|
|
3987
|
-
|
|
4168
|
+
import_promises6 = require("fs/promises");
|
|
4169
|
+
import_node_path7 = require("path");
|
|
3988
4170
|
import_ajv = __toESM(require("ajv"), 1);
|
|
3989
4171
|
CONTRACT_ERROR_CODES = {
|
|
3990
4172
|
/** Contract identity not found */
|
|
@@ -4337,11 +4519,11 @@ __export(index_exports, {
|
|
|
4337
4519
|
collectChangedPaths: () => collectChangedPaths,
|
|
4338
4520
|
computeE2eCoverageStats: () => computeE2eCoverageStats,
|
|
4339
4521
|
computeRevisionDigest: () => computeRevisionDigest,
|
|
4340
|
-
dirname: () =>
|
|
4522
|
+
dirname: () => import_node_path8.dirname,
|
|
4341
4523
|
discoverAndAuditPackets: () => discoverAndAuditPackets,
|
|
4342
4524
|
discoverTargets: () => discoverTargets,
|
|
4343
4525
|
doctorArtifactChain: () => doctorArtifactChain,
|
|
4344
|
-
extname: () =>
|
|
4526
|
+
extname: () => import_node_path8.extname,
|
|
4345
4527
|
formatContextMarkdown: () => formatContextMarkdown,
|
|
4346
4528
|
generateE2eRegistry: () => generateE2eRegistry,
|
|
4347
4529
|
getArtifactTypeMetadata: () => getArtifactTypeMetadata,
|
|
@@ -4351,10 +4533,12 @@ __export(index_exports, {
|
|
|
4351
4533
|
isPacketTargetType: () => isPacketTargetType,
|
|
4352
4534
|
isPacketTargetTypeDynamic: () => isPacketTargetTypeDynamic,
|
|
4353
4535
|
isTargetArtifactType: () => isTargetArtifactType,
|
|
4536
|
+
isVersionLockIssueBlocking: () => isVersionLockIssueBlocking,
|
|
4354
4537
|
loadConfig: () => loadConfig,
|
|
4355
4538
|
loadContract: () => loadContract,
|
|
4356
4539
|
loadContractCatalog: () => loadContractCatalog,
|
|
4357
4540
|
loadContractsFromDirectory: () => loadContractsFromDirectory,
|
|
4541
|
+
matchesConfiguredArtifactPath: () => matchesConfiguredArtifactPath,
|
|
4358
4542
|
nextId: () => nextId,
|
|
4359
4543
|
normalizeE2eLegacyArtifact: () => normalizeE2eLegacyArtifact,
|
|
4360
4544
|
normalizeToCanonical: () => normalizeToCanonical,
|
|
@@ -4391,6 +4575,7 @@ __export(index_exports, {
|
|
|
4391
4575
|
validateScenarioPrdLinkIndex: () => validateScenarioPrdLinkIndex,
|
|
4392
4576
|
validateScenarioPrdLinks: () => validateScenarioPrdLinks,
|
|
4393
4577
|
verifyDigest: () => verifyDigest,
|
|
4578
|
+
versionLockIssueSeverity: () => versionLockIssueSeverity,
|
|
4394
4579
|
writeGraphCache: () => writeGraphCache
|
|
4395
4580
|
});
|
|
4396
4581
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -4435,10 +4620,10 @@ function resolveArtifactTypeName(schema, token) {
|
|
|
4435
4620
|
return void 0;
|
|
4436
4621
|
}
|
|
4437
4622
|
async function loadConfig(root) {
|
|
4438
|
-
const configPath = (0,
|
|
4623
|
+
const configPath = (0, import_node_path8.join)(root, "artifact-graph.config.yaml");
|
|
4439
4624
|
let parsed = {};
|
|
4440
4625
|
try {
|
|
4441
|
-
const raw = await (0,
|
|
4626
|
+
const raw = await (0, import_promises7.readFile)(configPath, "utf-8");
|
|
4442
4627
|
parsed = import_js_yaml.default.load(raw) ?? {};
|
|
4443
4628
|
} catch (error) {
|
|
4444
4629
|
if (error.code !== "ENOENT") {
|
|
@@ -4528,7 +4713,7 @@ function validateE2eConfig(e2e) {
|
|
|
4528
4713
|
if (typeof runner.root !== "string" || !runner.root.trim()) {
|
|
4529
4714
|
throw new Error(`Invalid e2e.runners[${runner.name}].root: must be a non-empty string.`);
|
|
4530
4715
|
}
|
|
4531
|
-
if ((0,
|
|
4716
|
+
if ((0, import_node_path8.isAbsolute)(runner.root)) {
|
|
4532
4717
|
throw new Error(`Invalid e2e.runners[${runner.name}].root: "${runner.root}" must not be an absolute path.`);
|
|
4533
4718
|
}
|
|
4534
4719
|
if (runner.root.replace(/\\/g, "/").split("/").includes("..")) {
|
|
@@ -4612,14 +4797,14 @@ async function scanArtifacts(root, schema) {
|
|
|
4612
4797
|
continue;
|
|
4613
4798
|
}
|
|
4614
4799
|
scannedFiles.set(file, type);
|
|
4615
|
-
const raw = await (0,
|
|
4800
|
+
const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, file), "utf-8");
|
|
4616
4801
|
const parsed = parseFile(type, file, raw, config);
|
|
4617
4802
|
nodes.push(...parsed.nodes);
|
|
4618
4803
|
edges.push(...parsed.edges);
|
|
4619
4804
|
scanDiagnostics.push(...parsed.diagnostics);
|
|
4620
4805
|
}
|
|
4621
4806
|
}
|
|
4622
|
-
const absoluteRoot = (0,
|
|
4807
|
+
const absoluteRoot = (0, import_node_path8.isAbsolute)(root) ? root : (0, import_node_path8.resolve)(root);
|
|
4623
4808
|
const graph = buildGraph(nodes, edges, scanDiagnostics, absoluteRoot);
|
|
4624
4809
|
return resolveMatrixEdges(graph);
|
|
4625
4810
|
}
|
|
@@ -4924,7 +5109,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
|
|
|
4924
5109
|
const indexPath = "artifacts/prd/feature-index.md";
|
|
4925
5110
|
let raw = "";
|
|
4926
5111
|
try {
|
|
4927
|
-
raw = await (0,
|
|
5112
|
+
raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, indexPath), "utf-8");
|
|
4928
5113
|
} catch (error) {
|
|
4929
5114
|
if (error.code === "ENOENT") {
|
|
4930
5115
|
return [];
|
|
@@ -5114,11 +5299,11 @@ function nextId(graph, schema, type, rangeName) {
|
|
|
5114
5299
|
throw new Error(`ID range ${type}.${rangeName} is exhausted`);
|
|
5115
5300
|
}
|
|
5116
5301
|
async function writeGraphCache(root, graph) {
|
|
5117
|
-
const cacheDir = (0,
|
|
5118
|
-
await (0,
|
|
5119
|
-
await (0,
|
|
5302
|
+
const cacheDir = (0, import_node_path8.join)(root, ".artifact-graph");
|
|
5303
|
+
await (0, import_promises7.mkdir)(cacheDir, { recursive: true });
|
|
5304
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
|
|
5120
5305
|
`);
|
|
5121
|
-
const db = new import_better_sqlite3.default((0,
|
|
5306
|
+
const db = new import_better_sqlite3.default((0, import_node_path8.join)(cacheDir, "graph.sqlite"));
|
|
5122
5307
|
try {
|
|
5123
5308
|
db.exec(`
|
|
5124
5309
|
DROP TABLE IF EXISTS nodes;
|
|
@@ -5229,7 +5414,7 @@ function parseFile(type, path, raw, schema = DEFAULT_SCHEMA) {
|
|
|
5229
5414
|
}
|
|
5230
5415
|
function parseGenericMarkdown(type, path, raw, schema) {
|
|
5231
5416
|
const diagnostics = [];
|
|
5232
|
-
const ext = (0,
|
|
5417
|
+
const ext = (0, import_node_path8.extname)(path).toLowerCase();
|
|
5233
5418
|
if (ext !== ".md" && ext !== ".markdown") {
|
|
5234
5419
|
diagnostics.push(issue(
|
|
5235
5420
|
"UNSUPPORTED_FORMAT",
|
|
@@ -5456,7 +5641,7 @@ function parseDecisions(path, raw) {
|
|
|
5456
5641
|
}
|
|
5457
5642
|
function isTestFile(filePath) {
|
|
5458
5643
|
const normalized = filePath.replace(/\\/g, "/");
|
|
5459
|
-
const name = (0,
|
|
5644
|
+
const name = (0, import_node_path8.basename)(normalized);
|
|
5460
5645
|
if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
|
|
5461
5646
|
if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
|
|
5462
5647
|
if (/\w+Tests?\.java$/.test(name)) return true;
|
|
@@ -5747,7 +5932,7 @@ function parseE2eTest(path, raw) {
|
|
|
5747
5932
|
});
|
|
5748
5933
|
const nodes = [];
|
|
5749
5934
|
const edges = [];
|
|
5750
|
-
const batch = String(data.test_batch ?? (0,
|
|
5935
|
+
const batch = String(data.test_batch ?? (0, import_node_path8.basename)(path, (0, import_node_path8.extname)(path))).trim();
|
|
5751
5936
|
const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
|
|
5752
5937
|
const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
|
|
5753
5938
|
const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
|
|
@@ -5820,7 +6005,7 @@ function parseE2eRegistry(path, raw) {
|
|
|
5820
6005
|
data = { parseError: error.message };
|
|
5821
6006
|
}
|
|
5822
6007
|
return {
|
|
5823
|
-
nodes: [{ type: "e2e_registry", code: (0,
|
|
6008
|
+
nodes: [{ type: "e2e_registry", code: (0, import_node_path8.basename)(path, (0, import_node_path8.extname)(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
|
|
5824
6009
|
edges: []
|
|
5825
6010
|
};
|
|
5826
6011
|
}
|
|
@@ -6801,10 +6986,10 @@ function validateE2eRegistry(graph) {
|
|
|
6801
6986
|
async function validateExecutableTraceability(root, config) {
|
|
6802
6987
|
const issues = [];
|
|
6803
6988
|
const schema = config ?? await loadConfig(root);
|
|
6804
|
-
const e2eDir = (0,
|
|
6989
|
+
const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
|
|
6805
6990
|
let e2eFiles;
|
|
6806
6991
|
try {
|
|
6807
|
-
e2eFiles = (await (0,
|
|
6992
|
+
e2eFiles = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path8.join)(e2eDir, name));
|
|
6808
6993
|
} catch {
|
|
6809
6994
|
return [];
|
|
6810
6995
|
}
|
|
@@ -6813,11 +6998,11 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6813
6998
|
const tcKeyToFields = /* @__PURE__ */ new Map();
|
|
6814
6999
|
const mdBatches = /* @__PURE__ */ new Set();
|
|
6815
7000
|
for (const filePath of e2eFiles) {
|
|
6816
|
-
const raw = await (0,
|
|
6817
|
-
const relPath = (0,
|
|
7001
|
+
const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
7002
|
+
const relPath = (0, import_node_path8.relative)(root, filePath).split("\\").join("/");
|
|
6818
7003
|
const parsed = (0, import_gray_matter.default)(raw);
|
|
6819
7004
|
const data = parsed.data;
|
|
6820
|
-
const batch = String(data.test_batch ?? (0,
|
|
7005
|
+
const batch = String(data.test_batch ?? (0, import_node_path8.basename)(filePath, (0, import_node_path8.extname)(filePath))).trim();
|
|
6821
7006
|
const lines = raw.split(/\r?\n/);
|
|
6822
7007
|
const tcStarts = [];
|
|
6823
7008
|
lines.forEach((line, index) => {
|
|
@@ -6842,7 +7027,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6842
7027
|
}
|
|
6843
7028
|
}
|
|
6844
7029
|
}
|
|
6845
|
-
const allFiles = await
|
|
7030
|
+
const allFiles = await walkFiles(root);
|
|
6846
7031
|
const specFiles = /* @__PURE__ */ new Set();
|
|
6847
7032
|
const configuredRunners = schema.e2e?.runners ?? [];
|
|
6848
7033
|
if (configuredRunners.length > 0) {
|
|
@@ -6862,23 +7047,25 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6862
7047
|
const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
|
|
6863
7048
|
const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
|
|
6864
7049
|
for (const specFile of specFiles) {
|
|
6865
|
-
const fullSpecPath = (0,
|
|
7050
|
+
const fullSpecPath = (0, import_node_path8.join)(root, specFile);
|
|
6866
7051
|
let content;
|
|
6867
7052
|
try {
|
|
6868
|
-
content = await (0,
|
|
7053
|
+
content = await (0, import_promises7.readFile)(fullSpecPath, "utf-8");
|
|
6869
7054
|
} catch {
|
|
6870
7055
|
continue;
|
|
6871
7056
|
}
|
|
6872
7057
|
const level = detectTestLevel(specFile, content);
|
|
6873
7058
|
const specLines = content.split(/\r?\n/);
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
7059
|
+
const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
|
|
7060
|
+
for (const comment of lineComments) {
|
|
7061
|
+
const lineIndex = comment.lineNumber - 1;
|
|
7062
|
+
const commentText = comment.text.replace(/^!/, "");
|
|
7063
|
+
let match = tcAnnotationRegex.exec(`//${commentText}`);
|
|
6877
7064
|
let annotatedLevel = "";
|
|
6878
7065
|
if (match) {
|
|
6879
7066
|
annotatedLevel = match[2];
|
|
6880
7067
|
} else {
|
|
6881
|
-
match = tcAnnotationNoLevelRegex.exec(
|
|
7068
|
+
match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
|
|
6882
7069
|
}
|
|
6883
7070
|
if (!match) {
|
|
6884
7071
|
continue;
|
|
@@ -6905,7 +7092,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6905
7092
|
level: effectiveLevel,
|
|
6906
7093
|
file: specFile,
|
|
6907
7094
|
testName,
|
|
6908
|
-
line:
|
|
7095
|
+
line: comment.lineNumber
|
|
6909
7096
|
};
|
|
6910
7097
|
const key = `${batch}:${tcId}`;
|
|
6911
7098
|
const existing = refToSource.get(key) ?? [];
|
|
@@ -6942,7 +7129,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6942
7129
|
if (entry.testId) {
|
|
6943
7130
|
let content;
|
|
6944
7131
|
try {
|
|
6945
|
-
content = await (0,
|
|
7132
|
+
content = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, normalizedRefFile), "utf-8");
|
|
6946
7133
|
} catch {
|
|
6947
7134
|
continue;
|
|
6948
7135
|
}
|
|
@@ -7119,16 +7306,16 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7119
7306
|
let withExecutableRef = 0;
|
|
7120
7307
|
const statusBreakdown = {};
|
|
7121
7308
|
const chainTypeBreakdown = {};
|
|
7122
|
-
const e2eDir = (0,
|
|
7309
|
+
const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
|
|
7123
7310
|
const tcFieldsMap = /* @__PURE__ */ new Map();
|
|
7124
7311
|
let e2eFiles;
|
|
7125
7312
|
try {
|
|
7126
|
-
e2eFiles = (await (0,
|
|
7313
|
+
e2eFiles = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path8.join)(e2eDir, name));
|
|
7127
7314
|
} catch {
|
|
7128
7315
|
e2eFiles = [];
|
|
7129
7316
|
}
|
|
7130
7317
|
for (const filePath of e2eFiles) {
|
|
7131
|
-
const raw = await (0,
|
|
7318
|
+
const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
7132
7319
|
const lines = raw.split(/\r?\n/);
|
|
7133
7320
|
const tcStarts = [];
|
|
7134
7321
|
lines.forEach((line, index) => {
|
|
@@ -7138,7 +7325,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7138
7325
|
}
|
|
7139
7326
|
});
|
|
7140
7327
|
const parsed = (0, import_gray_matter.default)(raw);
|
|
7141
|
-
const batch = String(parsed.data.test_batch ?? (0,
|
|
7328
|
+
const batch = String(parsed.data.test_batch ?? (0, import_node_path8.basename)(filePath, (0, import_node_path8.extname)(filePath))).trim();
|
|
7142
7329
|
for (let i = 0; i < tcStarts.length; i++) {
|
|
7143
7330
|
const start = tcStarts[i];
|
|
7144
7331
|
const end = tcStarts[i + 1]?.index ?? lines.length;
|
|
@@ -7190,7 +7377,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7190
7377
|
}
|
|
7191
7378
|
}
|
|
7192
7379
|
const runners = (await loadConfig(root)).e2e?.runners ?? [];
|
|
7193
|
-
const allProjectFiles = await
|
|
7380
|
+
const allProjectFiles = await walkFiles(root);
|
|
7194
7381
|
for (const node of e2eNodes) {
|
|
7195
7382
|
const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
|
|
7196
7383
|
const status = String(fields["status"] ?? "").trim().toLowerCase();
|
|
@@ -7201,7 +7388,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7201
7388
|
let hasActiveE2eRef = false;
|
|
7202
7389
|
for (const entry of parseExecutableRefLines(execRef)) {
|
|
7203
7390
|
const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
|
|
7204
|
-
if (!normalized || !(0, import_node_fs4.existsSync)((0,
|
|
7391
|
+
if (!normalized || !(0, import_node_fs4.existsSync)((0, import_node_path8.join)(root, normalized))) continue;
|
|
7205
7392
|
const accepting = await getAcceptingRunners(root, normalized, runners);
|
|
7206
7393
|
if (accepting.some((runner) => runner.kind === "e2e")) {
|
|
7207
7394
|
hasActiveE2eRef = true;
|
|
@@ -7260,7 +7447,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7260
7447
|
const acCoverageRateByFeature = {};
|
|
7261
7448
|
const featureAcMap = /* @__PURE__ */ new Map();
|
|
7262
7449
|
for (const node of featureNodes) {
|
|
7263
|
-
const acs = parseAcceptanceCriteria(await (0,
|
|
7450
|
+
const acs = parseAcceptanceCriteria(await (0, import_promises7.readFile)((0, import_node_path8.join)(root, node.path), "utf-8"));
|
|
7264
7451
|
featureAcMap.set(node.code, new Set(acs));
|
|
7265
7452
|
}
|
|
7266
7453
|
const coveredAcByFeature = /* @__PURE__ */ new Map();
|
|
@@ -7302,10 +7489,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7302
7489
|
};
|
|
7303
7490
|
}
|
|
7304
7491
|
async function generateE2eRegistry(root, opts) {
|
|
7305
|
-
const e2eDir = (0,
|
|
7492
|
+
const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
|
|
7306
7493
|
let files;
|
|
7307
7494
|
try {
|
|
7308
|
-
files = (await (0,
|
|
7495
|
+
files = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
|
|
7309
7496
|
} catch {
|
|
7310
7497
|
return {
|
|
7311
7498
|
registry_version: "1.0",
|
|
@@ -7318,11 +7505,11 @@ async function generateE2eRegistry(root, opts) {
|
|
|
7318
7505
|
const batches = [];
|
|
7319
7506
|
let totalTestCases = 0;
|
|
7320
7507
|
for (const file of files) {
|
|
7321
|
-
const filePath = (0,
|
|
7322
|
-
const raw = await (0,
|
|
7508
|
+
const filePath = (0, import_node_path8.join)(e2eDir, file);
|
|
7509
|
+
const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
7323
7510
|
const parsed = (0, import_gray_matter.default)(raw);
|
|
7324
7511
|
const data = parsed.data;
|
|
7325
|
-
const batch = String(data.test_batch ?? (0,
|
|
7512
|
+
const batch = String(data.test_batch ?? (0, import_node_path8.basename)(file, (0, import_node_path8.extname)(file))).trim();
|
|
7326
7513
|
const relPath = `artifacts/tests/e2e/${file}`;
|
|
7327
7514
|
const scope = String(data.scope ?? "").trim();
|
|
7328
7515
|
const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
|
|
@@ -7446,10 +7633,10 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
|
|
|
7446
7633
|
if (!normalizedPath) {
|
|
7447
7634
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
7448
7635
|
}
|
|
7449
|
-
const fullPath = (0,
|
|
7636
|
+
const fullPath = (0, import_node_path8.join)(root, normalizedPath);
|
|
7450
7637
|
let content;
|
|
7451
7638
|
try {
|
|
7452
|
-
content = await (0,
|
|
7639
|
+
content = await (0, import_promises7.readFile)(fullPath, "utf-8");
|
|
7453
7640
|
} catch {
|
|
7454
7641
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
7455
7642
|
}
|
|
@@ -7483,7 +7670,7 @@ function detectTestLevel(specFile, content) {
|
|
|
7483
7670
|
}
|
|
7484
7671
|
function resolveExecutableRefFile(refFile, allFiles) {
|
|
7485
7672
|
const normalized = refFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7486
|
-
if (!normalized || (0,
|
|
7673
|
+
if (!normalized || (0, import_node_path8.isAbsolute)(refFile) || normalized.split("/").includes("..")) {
|
|
7487
7674
|
return void 0;
|
|
7488
7675
|
}
|
|
7489
7676
|
if (allFiles.includes(normalized)) {
|
|
@@ -7558,9 +7745,9 @@ function escapeRegExp(value) {
|
|
|
7558
7745
|
}
|
|
7559
7746
|
async function hasMarkdownTc(tcKey, e2eDir) {
|
|
7560
7747
|
const [batch, tcId] = tcKey.split(":");
|
|
7561
|
-
const filePath = (0,
|
|
7748
|
+
const filePath = (0, import_node_path8.join)(e2eDir, `${batch}.md`);
|
|
7562
7749
|
try {
|
|
7563
|
-
const raw = await (0,
|
|
7750
|
+
const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
7564
7751
|
const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
|
|
7565
7752
|
return tcRegex.test(raw);
|
|
7566
7753
|
} catch {
|
|
@@ -7568,7 +7755,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
|
|
|
7568
7755
|
}
|
|
7569
7756
|
}
|
|
7570
7757
|
async function findFiles(root, patterns) {
|
|
7571
|
-
const all = await
|
|
7758
|
+
const all = await walkFiles(root);
|
|
7572
7759
|
const matched = /* @__PURE__ */ new Set();
|
|
7573
7760
|
for (const pattern of patterns) {
|
|
7574
7761
|
for (const file of all) {
|
|
@@ -7579,21 +7766,9 @@ async function findFiles(root, patterns) {
|
|
|
7579
7766
|
}
|
|
7580
7767
|
return [...matched].sort();
|
|
7581
7768
|
}
|
|
7582
|
-
|
|
7583
|
-
const
|
|
7584
|
-
|
|
7585
|
-
for (const entry of entries) {
|
|
7586
|
-
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
7587
|
-
continue;
|
|
7588
|
-
}
|
|
7589
|
-
const fullPath = (0, import_node_path7.join)(current, entry.name);
|
|
7590
|
-
if (entry.isDirectory()) {
|
|
7591
|
-
files.push(...await walk(root, fullPath));
|
|
7592
|
-
} else {
|
|
7593
|
-
files.push((0, import_node_path7.relative)(root, fullPath).split("\\").join("/"));
|
|
7594
|
-
}
|
|
7595
|
-
}
|
|
7596
|
-
return files;
|
|
7769
|
+
function matchesConfiguredArtifactPath(path, schema) {
|
|
7770
|
+
const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7771
|
+
return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
|
|
7597
7772
|
}
|
|
7598
7773
|
function matchesPattern(file, pattern) {
|
|
7599
7774
|
if (!pattern.includes("*")) {
|
|
@@ -7805,7 +7980,7 @@ function normalizeDesignCode(value) {
|
|
|
7805
7980
|
if (!raw) {
|
|
7806
7981
|
return "";
|
|
7807
7982
|
}
|
|
7808
|
-
return (0,
|
|
7983
|
+
return (0, import_node_path8.basename)(raw, (0, import_node_path8.extname)(raw));
|
|
7809
7984
|
}
|
|
7810
7985
|
function toUid(type, code) {
|
|
7811
7986
|
return `${type}:${code}`;
|
|
@@ -8070,7 +8245,7 @@ function resolveArtifactContext(graph, opts) {
|
|
|
8070
8245
|
}
|
|
8071
8246
|
if (root) {
|
|
8072
8247
|
for (const ap of ALWAYS_PRESENT_ITEMS) {
|
|
8073
|
-
const fullPath = (0,
|
|
8248
|
+
const fullPath = (0, import_node_path8.join)(root, ap.path);
|
|
8074
8249
|
let stat;
|
|
8075
8250
|
try {
|
|
8076
8251
|
stat = (0, import_node_fs4.statSync)(fullPath);
|
|
@@ -8300,18 +8475,19 @@ function formatContextMarkdown(manifest) {
|
|
|
8300
8475
|
}
|
|
8301
8476
|
return lines.join("\n");
|
|
8302
8477
|
}
|
|
8303
|
-
var import_better_sqlite3, import_gray_matter, import_js_yaml, import_node_fs4,
|
|
8478
|
+
var import_better_sqlite3, import_gray_matter, import_js_yaml, import_node_fs4, import_promises7, import_node_path8, TARGET_ARTIFACT_TYPES, NON_TARGET_ROLES, DEFAULT_SCHEMA, VALID_TC_STATUSES, VALID_CHAIN_TYPES, DEPRECATED_CHAIN_TYPE_ALIASES, CONTEXT_CATEGORIES, TIER_ORDER;
|
|
8304
8479
|
var init_index = __esm({
|
|
8305
8480
|
"src/index.ts"() {
|
|
8306
8481
|
import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
8307
8482
|
import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
8308
8483
|
import_js_yaml = __toESM(require("js-yaml"), 1);
|
|
8309
8484
|
import_node_fs4 = require("fs");
|
|
8310
|
-
|
|
8311
|
-
|
|
8485
|
+
import_promises7 = require("fs/promises");
|
|
8486
|
+
import_node_path8 = require("path");
|
|
8312
8487
|
init_packet_constants();
|
|
8313
8488
|
init_packet_validator();
|
|
8314
8489
|
init_glob_matcher();
|
|
8490
|
+
init_file_walker();
|
|
8315
8491
|
init_packet_constants();
|
|
8316
8492
|
init_target_selector();
|
|
8317
8493
|
init_packet_assembler();
|
|
@@ -8457,10 +8633,12 @@ init_index();
|
|
|
8457
8633
|
isPacketTargetType,
|
|
8458
8634
|
isPacketTargetTypeDynamic,
|
|
8459
8635
|
isTargetArtifactType,
|
|
8636
|
+
isVersionLockIssueBlocking,
|
|
8460
8637
|
loadConfig,
|
|
8461
8638
|
loadContract,
|
|
8462
8639
|
loadContractCatalog,
|
|
8463
8640
|
loadContractsFromDirectory,
|
|
8641
|
+
matchesConfiguredArtifactPath,
|
|
8464
8642
|
nextId,
|
|
8465
8643
|
normalizeE2eLegacyArtifact,
|
|
8466
8644
|
normalizeToCanonical,
|
|
@@ -8497,5 +8675,6 @@ init_index();
|
|
|
8497
8675
|
validateScenarioPrdLinkIndex,
|
|
8498
8676
|
validateScenarioPrdLinks,
|
|
8499
8677
|
verifyDigest,
|
|
8678
|
+
versionLockIssueSeverity,
|
|
8500
8679
|
writeGraphCache
|
|
8501
8680
|
});
|