artifact-graph 0.9.2 → 0.9.3
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 +19 -0
- package/dist/cli.js +39 -8
- package/dist/index.cjs +41 -7
- package/dist/index.js +41 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.3
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Scenario titles and explicit relation fields inside Markdown fenced code blocks
|
|
8
|
+
(```` ``` ```` or `~~~`) are no longer scanned: examples such as
|
|
9
|
+
`**关联决策**: ADR-0001` shown inside a fence produce neither scenario nodes nor graph
|
|
10
|
+
edges. A fence closes only on the same marker with a length at least as long as the
|
|
11
|
+
opening fence.
|
|
12
|
+
- Explicit relation fields indented by four or more spaces or a tab are now treated as
|
|
13
|
+
indented code and no longer produce edges. Outside code blocks, a real relation field
|
|
14
|
+
may be indented by at most 0—3 plain spaces. Behavior tightening: adopters who relied
|
|
15
|
+
on deeply indented relation fields must move them to line start (0—3 spaces).
|
|
16
|
+
- The public `validateScenarioPrdLinks` function once again returns the complete
|
|
17
|
+
diagnostics (including `FORMAT_ERROR` for invalid feature references) when called
|
|
18
|
+
directly on scan results, restoring the public API behavior regressed in 0.9.2.
|
|
19
|
+
- `validateGraph` still uses scan diagnostics as the single source for scanned invalid
|
|
20
|
+
relations, so the same invalid feature reference is reported exactly once.
|
|
21
|
+
|
|
3
22
|
## 0.9.2
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/dist/cli.js
CHANGED
|
@@ -4936,7 +4936,7 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
4936
4936
|
}
|
|
4937
4937
|
issues.push(...validateE2eTests(graph));
|
|
4938
4938
|
issues.push(...validateE2eRegistry(graph));
|
|
4939
|
-
issues.push(...
|
|
4939
|
+
issues.push(...validateScenarioPrdLinksInternal(graph, schema, false));
|
|
4940
4940
|
issues.push(...validateCodeCommentTraceabilityFormat(graph));
|
|
4941
4941
|
issues.push(...validateCodeCommentScenarioFeatureConsistency(graph));
|
|
4942
4942
|
for (const edge2 of graph.edges) {
|
|
@@ -5038,7 +5038,7 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
5038
5038
|
issues.sort((left, right) => left.code.localeCompare(right.code) || left.path.localeCompare(right.path) || left.line - right.line);
|
|
5039
5039
|
return issues;
|
|
5040
5040
|
}
|
|
5041
|
-
function
|
|
5041
|
+
function validateScenarioPrdLinksInternal(graph, schema, includeScanDiagnosedInvalid) {
|
|
5042
5042
|
if (!schema.relationFields.scenario?.includes("\u5173\u8054\u529F\u80FD") || !schema.relationFields.feature?.includes("scenarios")) {
|
|
5043
5043
|
return [];
|
|
5044
5044
|
}
|
|
@@ -5072,7 +5072,7 @@ function validateScenarioPrdLinks(graph, schema = DEFAULT_SCHEMA) {
|
|
|
5072
5072
|
}
|
|
5073
5073
|
pushDuplicateIssues(issues, scenarioUid, validRefs, "feature");
|
|
5074
5074
|
for (const ref of refs) {
|
|
5075
|
-
if (ref.valid === false) continue;
|
|
5075
|
+
if (ref.valid === false && !includeScanDiagnosedInvalid) continue;
|
|
5076
5076
|
if (!featurePattern.test(ref.target)) {
|
|
5077
5077
|
issues.push(issue("FORMAT_ERROR", `scenario ${scenarioUid} has invalid feature reference ${ref.target}`, ref.path, ref.line, { node: scenarioUid }));
|
|
5078
5078
|
}
|
|
@@ -5576,8 +5576,12 @@ function parseFeature(path, raw) {
|
|
|
5576
5576
|
}
|
|
5577
5577
|
function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
5578
5578
|
const lines = raw.split(/\r?\n/);
|
|
5579
|
+
const codeLines = markdownCodeLineMask(lines);
|
|
5579
5580
|
const starts = [];
|
|
5580
5581
|
lines.forEach((line, index) => {
|
|
5582
|
+
if (codeLines[index]) {
|
|
5583
|
+
return;
|
|
5584
|
+
}
|
|
5581
5585
|
const match = /^#{2,3}\s+(S-\d+[a-z]?)\s*[::]\s*(.+?)\s*$/.exec(line);
|
|
5582
5586
|
if (match) {
|
|
5583
5587
|
starts.push({ code: match[1], title: match[2], line: index + 1, index });
|
|
@@ -5590,9 +5594,9 @@ function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
|
5590
5594
|
const start = starts[i];
|
|
5591
5595
|
const end = starts[i + 1]?.index ?? lines.length;
|
|
5592
5596
|
const block = lines.slice(start.index, end);
|
|
5593
|
-
const featureRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5594
|
-
const decisionRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5595
|
-
const entityRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5597
|
+
const featureRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5598
|
+
const decisionRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5599
|
+
const entityRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5596
5600
|
nodes.push({
|
|
5597
5601
|
type: "scenario",
|
|
5598
5602
|
code: start.code,
|
|
@@ -6829,12 +6833,39 @@ function decisionFrontmatterEdges(path, decisionCode, value, targetType, kind) {
|
|
|
6829
6833
|
}
|
|
6830
6834
|
function matchExplicitRelationFieldLine(line, label) {
|
|
6831
6835
|
const escaped = escapeRegExp(label);
|
|
6832
|
-
const match = new RegExp(
|
|
6836
|
+
const match = new RegExp(`^ {0,3}(?:\\*\\*${escaped}\\*\\*|${escaped})[:\uFF1A]\\s*(.*?)\\s*$`).exec(line);
|
|
6833
6837
|
return match?.[1] ?? null;
|
|
6834
6838
|
}
|
|
6835
|
-
function
|
|
6839
|
+
function markdownCodeLineMask(lines) {
|
|
6840
|
+
const result = new Array(lines.length).fill(false);
|
|
6841
|
+
let fence;
|
|
6842
|
+
lines.forEach((line, index) => {
|
|
6843
|
+
if (fence) {
|
|
6844
|
+
result[index] = true;
|
|
6845
|
+
const closing = /^ {0,3}(`+|~+)[ \t]*$/.exec(line);
|
|
6846
|
+
if (closing && closing[1][0] === fence.marker && closing[1].length >= fence.length) {
|
|
6847
|
+
fence = void 0;
|
|
6848
|
+
}
|
|
6849
|
+
return;
|
|
6850
|
+
}
|
|
6851
|
+
const opening = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
6852
|
+
if (opening) {
|
|
6853
|
+
result[index] = true;
|
|
6854
|
+
fence = { marker: opening[1][0], length: opening[1].length };
|
|
6855
|
+
return;
|
|
6856
|
+
}
|
|
6857
|
+
if (/^(?: {4,}| {0,3}\t)/.test(line)) {
|
|
6858
|
+
result[index] = true;
|
|
6859
|
+
}
|
|
6860
|
+
});
|
|
6861
|
+
return result;
|
|
6862
|
+
}
|
|
6863
|
+
function markdownRelationOccurrences(path, scenario, block, codeLines, label, targetType, schema = DEFAULT_SCHEMA) {
|
|
6836
6864
|
const result = [];
|
|
6837
6865
|
block.forEach((line, index) => {
|
|
6866
|
+
if (codeLines[scenario.index + index]) {
|
|
6867
|
+
return;
|
|
6868
|
+
}
|
|
6838
6869
|
const relationText = matchExplicitRelationFieldLine(line, label);
|
|
6839
6870
|
if (!relationText) {
|
|
6840
6871
|
return;
|
package/dist/index.cjs
CHANGED
|
@@ -4914,7 +4914,7 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
4914
4914
|
}
|
|
4915
4915
|
issues.push(...validateE2eTests(graph));
|
|
4916
4916
|
issues.push(...validateE2eRegistry(graph));
|
|
4917
|
-
issues.push(...
|
|
4917
|
+
issues.push(...validateScenarioPrdLinksInternal(graph, schema, false));
|
|
4918
4918
|
issues.push(...validateCodeCommentTraceabilityFormat(graph));
|
|
4919
4919
|
issues.push(...validateCodeCommentScenarioFeatureConsistency(graph));
|
|
4920
4920
|
for (const edge2 of graph.edges) {
|
|
@@ -5017,6 +5017,9 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
5017
5017
|
return issues;
|
|
5018
5018
|
}
|
|
5019
5019
|
function validateScenarioPrdLinks(graph, schema = DEFAULT_SCHEMA) {
|
|
5020
|
+
return validateScenarioPrdLinksInternal(graph, schema, true);
|
|
5021
|
+
}
|
|
5022
|
+
function validateScenarioPrdLinksInternal(graph, schema, includeScanDiagnosedInvalid) {
|
|
5020
5023
|
if (!schema.relationFields.scenario?.includes("\u5173\u8054\u529F\u80FD") || !schema.relationFields.feature?.includes("scenarios")) {
|
|
5021
5024
|
return [];
|
|
5022
5025
|
}
|
|
@@ -5050,7 +5053,7 @@ function validateScenarioPrdLinks(graph, schema = DEFAULT_SCHEMA) {
|
|
|
5050
5053
|
}
|
|
5051
5054
|
pushDuplicateIssues(issues, scenarioUid, validRefs, "feature");
|
|
5052
5055
|
for (const ref of refs) {
|
|
5053
|
-
if (ref.valid === false) continue;
|
|
5056
|
+
if (ref.valid === false && !includeScanDiagnosedInvalid) continue;
|
|
5054
5057
|
if (!featurePattern.test(ref.target)) {
|
|
5055
5058
|
issues.push(issue("FORMAT_ERROR", `scenario ${scenarioUid} has invalid feature reference ${ref.target}`, ref.path, ref.line, { node: scenarioUid }));
|
|
5056
5059
|
}
|
|
@@ -5554,8 +5557,12 @@ function parseFeature(path, raw) {
|
|
|
5554
5557
|
}
|
|
5555
5558
|
function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
5556
5559
|
const lines = raw.split(/\r?\n/);
|
|
5560
|
+
const codeLines = markdownCodeLineMask(lines);
|
|
5557
5561
|
const starts = [];
|
|
5558
5562
|
lines.forEach((line, index) => {
|
|
5563
|
+
if (codeLines[index]) {
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5559
5566
|
const match = /^#{2,3}\s+(S-\d+[a-z]?)\s*[::]\s*(.+?)\s*$/.exec(line);
|
|
5560
5567
|
if (match) {
|
|
5561
5568
|
starts.push({ code: match[1], title: match[2], line: index + 1, index });
|
|
@@ -5568,9 +5575,9 @@ function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
|
5568
5575
|
const start = starts[i];
|
|
5569
5576
|
const end = starts[i + 1]?.index ?? lines.length;
|
|
5570
5577
|
const block = lines.slice(start.index, end);
|
|
5571
|
-
const featureRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5572
|
-
const decisionRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5573
|
-
const entityRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5578
|
+
const featureRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5579
|
+
const decisionRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5580
|
+
const entityRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5574
5581
|
nodes.push({
|
|
5575
5582
|
type: "scenario",
|
|
5576
5583
|
code: start.code,
|
|
@@ -6807,12 +6814,39 @@ function decisionFrontmatterEdges(path, decisionCode, value, targetType, kind) {
|
|
|
6807
6814
|
}
|
|
6808
6815
|
function matchExplicitRelationFieldLine(line, label) {
|
|
6809
6816
|
const escaped = escapeRegExp(label);
|
|
6810
|
-
const match = new RegExp(
|
|
6817
|
+
const match = new RegExp(`^ {0,3}(?:\\*\\*${escaped}\\*\\*|${escaped})[:\uFF1A]\\s*(.*?)\\s*$`).exec(line);
|
|
6811
6818
|
return match?.[1] ?? null;
|
|
6812
6819
|
}
|
|
6813
|
-
function
|
|
6820
|
+
function markdownCodeLineMask(lines) {
|
|
6821
|
+
const result = new Array(lines.length).fill(false);
|
|
6822
|
+
let fence;
|
|
6823
|
+
lines.forEach((line, index) => {
|
|
6824
|
+
if (fence) {
|
|
6825
|
+
result[index] = true;
|
|
6826
|
+
const closing = /^ {0,3}(`+|~+)[ \t]*$/.exec(line);
|
|
6827
|
+
if (closing && closing[1][0] === fence.marker && closing[1].length >= fence.length) {
|
|
6828
|
+
fence = void 0;
|
|
6829
|
+
}
|
|
6830
|
+
return;
|
|
6831
|
+
}
|
|
6832
|
+
const opening = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
6833
|
+
if (opening) {
|
|
6834
|
+
result[index] = true;
|
|
6835
|
+
fence = { marker: opening[1][0], length: opening[1].length };
|
|
6836
|
+
return;
|
|
6837
|
+
}
|
|
6838
|
+
if (/^(?: {4,}| {0,3}\t)/.test(line)) {
|
|
6839
|
+
result[index] = true;
|
|
6840
|
+
}
|
|
6841
|
+
});
|
|
6842
|
+
return result;
|
|
6843
|
+
}
|
|
6844
|
+
function markdownRelationOccurrences(path, scenario, block, codeLines, label, targetType, schema = DEFAULT_SCHEMA) {
|
|
6814
6845
|
const result = [];
|
|
6815
6846
|
block.forEach((line, index) => {
|
|
6847
|
+
if (codeLines[scenario.index + index]) {
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6816
6850
|
const relationText = matchExplicitRelationFieldLine(line, label);
|
|
6817
6851
|
if (!relationText) {
|
|
6818
6852
|
return;
|
package/dist/index.js
CHANGED
|
@@ -4804,7 +4804,7 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
4804
4804
|
}
|
|
4805
4805
|
issues.push(...validateE2eTests(graph));
|
|
4806
4806
|
issues.push(...validateE2eRegistry(graph));
|
|
4807
|
-
issues.push(...
|
|
4807
|
+
issues.push(...validateScenarioPrdLinksInternal(graph, schema, false));
|
|
4808
4808
|
issues.push(...validateCodeCommentTraceabilityFormat(graph));
|
|
4809
4809
|
issues.push(...validateCodeCommentScenarioFeatureConsistency(graph));
|
|
4810
4810
|
for (const edge2 of graph.edges) {
|
|
@@ -4907,6 +4907,9 @@ function validateGraph(graph, schema = DEFAULT_SCHEMA) {
|
|
|
4907
4907
|
return issues;
|
|
4908
4908
|
}
|
|
4909
4909
|
function validateScenarioPrdLinks(graph, schema = DEFAULT_SCHEMA) {
|
|
4910
|
+
return validateScenarioPrdLinksInternal(graph, schema, true);
|
|
4911
|
+
}
|
|
4912
|
+
function validateScenarioPrdLinksInternal(graph, schema, includeScanDiagnosedInvalid) {
|
|
4910
4913
|
if (!schema.relationFields.scenario?.includes("\u5173\u8054\u529F\u80FD") || !schema.relationFields.feature?.includes("scenarios")) {
|
|
4911
4914
|
return [];
|
|
4912
4915
|
}
|
|
@@ -4940,7 +4943,7 @@ function validateScenarioPrdLinks(graph, schema = DEFAULT_SCHEMA) {
|
|
|
4940
4943
|
}
|
|
4941
4944
|
pushDuplicateIssues(issues, scenarioUid, validRefs, "feature");
|
|
4942
4945
|
for (const ref of refs) {
|
|
4943
|
-
if (ref.valid === false) continue;
|
|
4946
|
+
if (ref.valid === false && !includeScanDiagnosedInvalid) continue;
|
|
4944
4947
|
if (!featurePattern.test(ref.target)) {
|
|
4945
4948
|
issues.push(issue("FORMAT_ERROR", `scenario ${scenarioUid} has invalid feature reference ${ref.target}`, ref.path, ref.line, { node: scenarioUid }));
|
|
4946
4949
|
}
|
|
@@ -5444,8 +5447,12 @@ function parseFeature(path, raw) {
|
|
|
5444
5447
|
}
|
|
5445
5448
|
function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
5446
5449
|
const lines = raw.split(/\r?\n/);
|
|
5450
|
+
const codeLines = markdownCodeLineMask(lines);
|
|
5447
5451
|
const starts = [];
|
|
5448
5452
|
lines.forEach((line, index) => {
|
|
5453
|
+
if (codeLines[index]) {
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5449
5456
|
const match = /^#{2,3}\s+(S-\d+[a-z]?)\s*[::]\s*(.+?)\s*$/.exec(line);
|
|
5450
5457
|
if (match) {
|
|
5451
5458
|
starts.push({ code: match[1], title: match[2], line: index + 1, index });
|
|
@@ -5458,9 +5465,9 @@ function parseScenarios(path, raw, schema = DEFAULT_SCHEMA) {
|
|
|
5458
5465
|
const start = starts[i];
|
|
5459
5466
|
const end = starts[i + 1]?.index ?? lines.length;
|
|
5460
5467
|
const block = lines.slice(start.index, end);
|
|
5461
|
-
const featureRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5462
|
-
const decisionRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5463
|
-
const entityRefs = markdownRelationOccurrences(path, start, block, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5468
|
+
const featureRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u529F\u80FD", "feature", schema);
|
|
5469
|
+
const decisionRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u51B3\u7B56", "decision", schema);
|
|
5470
|
+
const entityRefs = markdownRelationOccurrences(path, start, block, codeLines, "\u5173\u8054\u5B9E\u4F53", "entity", schema);
|
|
5464
5471
|
nodes.push({
|
|
5465
5472
|
type: "scenario",
|
|
5466
5473
|
code: start.code,
|
|
@@ -6697,12 +6704,39 @@ function decisionFrontmatterEdges(path, decisionCode, value, targetType, kind) {
|
|
|
6697
6704
|
}
|
|
6698
6705
|
function matchExplicitRelationFieldLine(line, label) {
|
|
6699
6706
|
const escaped = escapeRegExp(label);
|
|
6700
|
-
const match = new RegExp(
|
|
6707
|
+
const match = new RegExp(`^ {0,3}(?:\\*\\*${escaped}\\*\\*|${escaped})[:\uFF1A]\\s*(.*?)\\s*$`).exec(line);
|
|
6701
6708
|
return match?.[1] ?? null;
|
|
6702
6709
|
}
|
|
6703
|
-
function
|
|
6710
|
+
function markdownCodeLineMask(lines) {
|
|
6711
|
+
const result = new Array(lines.length).fill(false);
|
|
6712
|
+
let fence;
|
|
6713
|
+
lines.forEach((line, index) => {
|
|
6714
|
+
if (fence) {
|
|
6715
|
+
result[index] = true;
|
|
6716
|
+
const closing = /^ {0,3}(`+|~+)[ \t]*$/.exec(line);
|
|
6717
|
+
if (closing && closing[1][0] === fence.marker && closing[1].length >= fence.length) {
|
|
6718
|
+
fence = void 0;
|
|
6719
|
+
}
|
|
6720
|
+
return;
|
|
6721
|
+
}
|
|
6722
|
+
const opening = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
6723
|
+
if (opening) {
|
|
6724
|
+
result[index] = true;
|
|
6725
|
+
fence = { marker: opening[1][0], length: opening[1].length };
|
|
6726
|
+
return;
|
|
6727
|
+
}
|
|
6728
|
+
if (/^(?: {4,}| {0,3}\t)/.test(line)) {
|
|
6729
|
+
result[index] = true;
|
|
6730
|
+
}
|
|
6731
|
+
});
|
|
6732
|
+
return result;
|
|
6733
|
+
}
|
|
6734
|
+
function markdownRelationOccurrences(path, scenario, block, codeLines, label, targetType, schema = DEFAULT_SCHEMA) {
|
|
6704
6735
|
const result = [];
|
|
6705
6736
|
block.forEach((line, index) => {
|
|
6737
|
+
if (codeLines[scenario.index + index]) {
|
|
6738
|
+
return;
|
|
6739
|
+
}
|
|
6706
6740
|
const relationText = matchExplicitRelationFieldLine(line, label);
|
|
6707
6741
|
if (!relationText) {
|
|
6708
6742
|
return;
|