@jrpool/kilotest 42.0.5 → 43.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/diagnoses/index.js +3 -3
- package/index.js +2 -3
- package/issues/index.js +4 -4
- package/llms-full.txt +1 -1
- package/package.json +2 -1
- package/reannotate/index.js +2 -2
- package/reannotateForm/index.js +11 -12
- package/reportIssue/index.js +3 -3
- package/reportIssues/index.js +2 -2
- package/rules/index.html +1 -3
- package/rules/index.js +53 -32
- package/util.js +21 -78
package/diagnoses/index.js
CHANGED
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
isHidden,
|
|
16
16
|
tools
|
|
17
17
|
} = require('../util');
|
|
18
|
-
const {issues} = require('
|
|
18
|
+
const {issues: issueSpecs} = require('testaro-issues');
|
|
19
19
|
const fs = require('fs/promises');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
|
|
@@ -62,7 +62,7 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
62
62
|
query.target = what;
|
|
63
63
|
query.urlLink = urlLink;
|
|
64
64
|
query.testInfo = testInfo;
|
|
65
|
-
query.issue =
|
|
65
|
+
query.issue = issueSpecs[issueID]?.summary;
|
|
66
66
|
// If adding the issue summary failed:
|
|
67
67
|
if (!query.issue) {
|
|
68
68
|
// Populate the query with the reason.
|
|
@@ -71,7 +71,7 @@ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, qu
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
// Otherwise, i.e. if it succeeded, get the issue details.
|
|
74
|
-
const issue =
|
|
74
|
+
const issue = issueSpecs[issueID];
|
|
75
75
|
const {wcag, weight, why} = issue;
|
|
76
76
|
query.why = why;
|
|
77
77
|
query.priority = getWeightName(weight);
|
package/index.js
CHANGED
|
@@ -26,8 +26,7 @@ const {
|
|
|
26
26
|
jobsPath,
|
|
27
27
|
makeReportsExtract,
|
|
28
28
|
recsLock,
|
|
29
|
-
reportsPath
|
|
30
|
-
ruleIDs
|
|
29
|
+
reportsPath
|
|
31
30
|
} = require('./util');
|
|
32
31
|
const {handleMCP, mcpPath} = require('./mcp');
|
|
33
32
|
const fs = require('fs/promises');
|
|
@@ -750,7 +749,7 @@ const requestHandler = async (request, response) => {
|
|
|
750
749
|
// Update the data on the available reports.
|
|
751
750
|
await makeReportsExtract();
|
|
752
751
|
// Annotate the report.
|
|
753
|
-
await annotateReport(
|
|
752
|
+
await annotateReport(timeStamp, jobID);
|
|
754
753
|
console.log(`Testaro report ${id} was annotated, saved, and indexed`);
|
|
755
754
|
// Check the monetary balances and send alerts if nearing exhaustion.
|
|
756
755
|
await checkBalancesForAlerts(report);
|
package/issues/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
getWeightName,
|
|
15
15
|
objectSort,
|
|
16
16
|
} = require('../util');
|
|
17
|
-
const {issues} = require('
|
|
17
|
+
const {issues: issueSpecs} = require('testaro-issues');
|
|
18
18
|
const fs = require('fs/promises');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
|
|
@@ -69,13 +69,13 @@ const getIssuesSummary = async () => {
|
|
|
69
69
|
Object.entries(issuesData).forEach(([issueID, data]) => {
|
|
70
70
|
const {count, reporters} = data;
|
|
71
71
|
// If the issue is still classified:
|
|
72
|
-
if (
|
|
72
|
+
if (issueSpecs[issueID]) {
|
|
73
73
|
// Increment the report violation count by the issue violation count.
|
|
74
74
|
summary.totalCount += count;
|
|
75
75
|
// Add the issue data and an initilized percentage to the summary.
|
|
76
76
|
summary.issues.push({
|
|
77
77
|
issueID,
|
|
78
|
-
weight:
|
|
78
|
+
weight: issueSpecs[issueID].weight,
|
|
79
79
|
count,
|
|
80
80
|
percentage: 0,
|
|
81
81
|
reporters: getToolNamesString(reporters)
|
|
@@ -129,7 +129,7 @@ const populateQuery = async query => {
|
|
|
129
129
|
if (reportedIssue.weight === weight && percentage >= 2) {
|
|
130
130
|
existsIssue = true;
|
|
131
131
|
// Get the data on it from the issue classification.
|
|
132
|
-
const issue =
|
|
132
|
+
const issue = issueSpecs[issueID];
|
|
133
133
|
const {summary, wcag, why} = issue;
|
|
134
134
|
const wcagLink = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
|
|
135
135
|
// Add a description of it to the lines.
|
package/llms-full.txt
CHANGED
|
@@ -174,6 +174,6 @@ More information about Kilotest features and internals:
|
|
|
174
174
|
- [Tutorial on authorizing language models to use Kilotest](https://kilotest.com/qai/)
|
|
175
175
|
- [README file](https://github.com/jrpool/kilotest/blob/main/README.md)
|
|
176
176
|
- [Testaro rule-engine orchestrator](https://github.com/jrpool/testaro)
|
|
177
|
-
- [
|
|
177
|
+
- [Testaro rule classifier](https://github.com/jrpool/testaro-issues)
|
|
178
178
|
- [Research report on Testaro](https://arxiv.org/abs/2309.10167)
|
|
179
179
|
- [Blog post on the Kilotest use case](https://medium.com/cvs-health-tech-blog/how-to-run-a-thousand-accessibility-tests-63692ad120c3)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrpool/kilotest",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "43.0.0",
|
|
4
4
|
"description": "Ensemble testing for front-end web quality",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "*",
|
|
37
37
|
"dotenv": "*",
|
|
38
|
+
"testaro-issues": "*",
|
|
38
39
|
"zod": "*"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
package/reannotate/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {annotateReport, getReportExtracts
|
|
8
|
+
const {annotateReport, getReportExtracts} = require('../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -23,7 +23,7 @@ exports.answer = async authCode => {
|
|
|
23
23
|
for (const reportExtract of reportExtracts) {
|
|
24
24
|
const {timeStamp, jobID} = reportExtract;
|
|
25
25
|
// Reannotate it.
|
|
26
|
-
const annotationError = await annotateReport(
|
|
26
|
+
const annotationError = await annotateReport(timeStamp, jobID);
|
|
27
27
|
// If this failed:
|
|
28
28
|
if (annotationError) {
|
|
29
29
|
// Return an error page.
|
package/reannotateForm/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {getIssue, getReport, getReportExtracts
|
|
8
|
+
const {getIssue, getReport, getReportExtracts} = require('../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -40,7 +40,7 @@ const populateQuery = async query => {
|
|
|
40
40
|
result.standardResult.instances.forEach(instance => {
|
|
41
41
|
const {ruleID} = instance;
|
|
42
42
|
// Get the issue ID of the rule, or null if none.
|
|
43
|
-
const issueID = getIssue(
|
|
43
|
+
const issueID = getIssue(which, ruleID);
|
|
44
44
|
// If the issue ID of the instance differs from that of the rule:
|
|
45
45
|
if ((instance.issueID || null) !== issueID) {
|
|
46
46
|
// Add the rule and the report to the rules with changed issue IDs.
|
|
@@ -88,21 +88,20 @@ const populateQuery = async query => {
|
|
|
88
88
|
});
|
|
89
89
|
// Add the lines to the query.
|
|
90
90
|
query.reClassified = reClassifiedLines.join('\n');
|
|
91
|
+
const formLines = [];
|
|
91
92
|
if (reClassifiedLines.length) {
|
|
92
93
|
query.how = 'Each <q>reclassified</q> rule indicates that report annotations are out of date. To update them, submit your authorization code.';
|
|
93
|
-
const formLines = [];
|
|
94
|
-
formLines.push(`${margin}<form action="/reannotate.html" method="post">`);
|
|
95
|
-
formLines.push(
|
|
96
|
-
`${margin} <p><label>Authorization code: <input size="3" minLength="3" maxlength="3" name="authCode" required></label></p>`
|
|
97
|
-
);
|
|
98
|
-
formLines.push(`${margin} <p><button type="submit">Reannotate</button></p>`);
|
|
99
|
-
formLines.push(`${margin}</form>`);
|
|
100
|
-
query.reannotateForm = formLines.join('\n');
|
|
101
94
|
}
|
|
102
95
|
else {
|
|
103
|
-
query.how = 'No violated rules have been classified or reclassified after being reported, so reannotation of the reports is not necessary.';
|
|
104
|
-
query.reannotateForm = '';
|
|
96
|
+
query.how = 'No violated rules have been classified or reclassified after being reported, so reannotation of the reports is normally not necessary. If there is another reason for reannotation, submit your authorization code.';
|
|
105
97
|
}
|
|
98
|
+
formLines.push(`${margin}<form action="/reannotate.html" method="post">`);
|
|
99
|
+
formLines.push(
|
|
100
|
+
`${margin} <p><label>Authorization code: <input size="3" minLength="3" maxlength="3" name="authCode" required></label></p>`
|
|
101
|
+
);
|
|
102
|
+
formLines.push(`${margin} <p><button type="submit">Reannotate</button></p>`);
|
|
103
|
+
formLines.push(`${margin}</form>`);
|
|
104
|
+
query.reannotateForm = formLines.join('\n');
|
|
106
105
|
};
|
|
107
106
|
// Returns a page disclosing newly classified rules and a form to reannotate reports.
|
|
108
107
|
exports.answer = async () => {
|
package/reportIssue/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
isHidden,
|
|
18
18
|
makeBreakable,
|
|
19
19
|
} = require('../util');
|
|
20
|
-
const {issues} = require('
|
|
20
|
+
const {issues: issueSpecs} = require('testaro-issues');
|
|
21
21
|
const fs = require('fs/promises');
|
|
22
22
|
const path = require('path');
|
|
23
23
|
|
|
@@ -35,7 +35,7 @@ const populateQuery = async (issueID, timeStamp, jobID, query) => {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
// Otherwise, i.e. if it succeeded, add the issue summary to the query.
|
|
38
|
-
query.issue =
|
|
38
|
+
query.issue = issueSpecs[issueID]?.summary;
|
|
39
39
|
// If adding the issue summary failed:
|
|
40
40
|
if (!query.issue) {
|
|
41
41
|
// Populate the query with the reason.
|
|
@@ -48,7 +48,7 @@ const populateQuery = async (issueID, timeStamp, jobID, query) => {
|
|
|
48
48
|
query.target = what;
|
|
49
49
|
query.urlLink = urlLink;
|
|
50
50
|
query.testInfo = testInfo;
|
|
51
|
-
const issue =
|
|
51
|
+
const issue = issueSpecs[issueID];
|
|
52
52
|
const {wcag, weight, why} = issue;
|
|
53
53
|
query.why = why;
|
|
54
54
|
query.priority = getWeightName(weight);
|
package/reportIssues/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
objectSort,
|
|
19
19
|
tools
|
|
20
20
|
} = require('../util');
|
|
21
|
-
const
|
|
21
|
+
const {issues: issueSpecs} = require('testaro-issues');
|
|
22
22
|
const fs = require('fs/promises');
|
|
23
23
|
const path = require('path');
|
|
24
24
|
|
|
@@ -67,7 +67,7 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
67
67
|
const {catalogIndex, issueID} = instance;
|
|
68
68
|
// If the instance identifies its rule as belonging to a non-ignorable issue:
|
|
69
69
|
if (issueID && issueID !== 'ignorable') {
|
|
70
|
-
const issueClassification =
|
|
70
|
+
const issueClassification = issueSpecs[issueID];
|
|
71
71
|
// If the issue has a current weighted classification:
|
|
72
72
|
if (issueClassification && [1, 2, 3, 4].includes(issueClassification.weight)) {
|
|
73
73
|
const {summary, wcag, weight, why} = issueClassification;
|
package/rules/index.html
CHANGED
package/rules/index.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
const {
|
|
9
9
|
getWeightName,
|
|
10
10
|
htmlSafe,
|
|
11
|
-
|
|
11
|
+
ruleEngines
|
|
12
12
|
} = require('../util');
|
|
13
|
-
const {issues} = require('
|
|
13
|
+
const {issues: issueSpecs, issueRules, rules: ruleSpecs} = require('testaro-issues');
|
|
14
14
|
const fs = require('fs/promises');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
@@ -19,8 +19,12 @@ const path = require('path');
|
|
|
19
19
|
// Adds parameters to a query for the answer page.
|
|
20
20
|
const populateQuery = async (issueID, query) => {
|
|
21
21
|
// Add facts about the issue to the query.
|
|
22
|
-
query.issue =
|
|
23
|
-
|
|
22
|
+
query.issue = issueSpecs[issueID]?.summary;
|
|
23
|
+
if (!query.issue) {
|
|
24
|
+
query.error = 'Issue not found';
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const issue = issueSpecs[issueID];
|
|
24
28
|
const {wcag, weight, why} = issue;
|
|
25
29
|
query.why = why;
|
|
26
30
|
query.priority = getWeightName(weight);
|
|
@@ -28,44 +32,61 @@ const populateQuery = async (issueID, query) => {
|
|
|
28
32
|
// Initialize the lines.
|
|
29
33
|
const lines = [];
|
|
30
34
|
const margin = ' '.repeat(6);
|
|
31
|
-
// For each
|
|
32
|
-
Object.keys(
|
|
33
|
-
// Add a
|
|
34
|
-
lines.push(`${margin}<
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
// For each rule engine with any rules belonging to the issue:
|
|
36
|
+
Object.keys(issueRules[issueID] ?? {}).forEach(engineID => {
|
|
37
|
+
// Add a heading for the rules of the rule engine.
|
|
38
|
+
lines.push(`${margin}<h3>${ruleEngines[engineID][0]} rules</h3>`);
|
|
39
|
+
const {invariant, variable} = issueRules[issueID][engineID];
|
|
40
|
+
const rulesByType = {
|
|
41
|
+
invariant,
|
|
42
|
+
variable
|
|
43
|
+
};
|
|
44
|
+
// For each rule variability:
|
|
45
|
+
Object.keys(rulesByType).forEach(typeName => {
|
|
46
|
+
// If any rules of the rule engine belonging to the issue have it:
|
|
47
|
+
if (rulesByType[typeName].length) {
|
|
48
|
+
// Add a heading for them.
|
|
49
|
+
lines.push(`${margin}<h4>${typeName} rules</h4>`);
|
|
50
|
+
// Add a list of facts about them.
|
|
51
|
+
lines.push(`${margin}<ul>`);
|
|
52
|
+
rulesByType[typeName].forEach(ruleID => {
|
|
53
|
+
const {what} = ruleSpecs[engineID][typeName][ruleID];
|
|
54
|
+
if (what === ruleID) {
|
|
55
|
+
lines.push(`${margin} <li><code>${htmlSafe(ruleID)}</code></li>`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
lines.push(`${margin} <li><code>${htmlSafe(ruleID)}</code>: ${htmlSafe(what)}</li>`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
lines.push(`${margin}</ul>`);
|
|
47
62
|
}
|
|
48
63
|
});
|
|
49
|
-
lines.push(`${margin} </ul>`);
|
|
50
|
-
lines.push(`${margin}</li>`);
|
|
51
64
|
});
|
|
52
65
|
// Add the lines to the query.
|
|
53
66
|
query.rules = lines.join('\n');
|
|
54
67
|
};
|
|
55
|
-
// Returns a page answering the
|
|
68
|
+
// Returns a page answering the issue-rules question.
|
|
56
69
|
exports.answer = async issueID => {
|
|
57
70
|
const query = {};
|
|
58
71
|
// Create a query to replace the placeholders.
|
|
59
72
|
await populateQuery(issueID, query);
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
// If this succeeded:
|
|
74
|
+
if (query.issue) {
|
|
75
|
+
// Get the template.
|
|
76
|
+
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
77
|
+
// Replace its placeholders.
|
|
78
|
+
Object.keys(query).forEach(param => {
|
|
79
|
+
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
80
|
+
});
|
|
81
|
+
// Return the populated page.
|
|
82
|
+
return {
|
|
83
|
+
status: 'ok',
|
|
84
|
+
answerPage
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Otherwise, i.e. if it failed, report this.
|
|
67
88
|
return {
|
|
68
|
-
status: '
|
|
69
|
-
|
|
89
|
+
status: 'error',
|
|
90
|
+
message: query.error
|
|
70
91
|
};
|
|
71
92
|
};
|
package/util.js
CHANGED
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {sendAlert} = require('./alerts');
|
|
9
|
-
const
|
|
10
|
-
= exports.issuesClassification
|
|
11
|
-
= require('testilo/procs/score/tic').issues;
|
|
9
|
+
const {issues: issueSpecs, rules: ruleSpecs} = require('testaro-issues');
|
|
12
10
|
const fs = require('fs/promises');
|
|
13
11
|
const path = require('path');
|
|
14
12
|
const querystring = require('querystring');
|
|
@@ -120,26 +118,25 @@ const getDateTime = exports.getDateTime = timeStamp => {
|
|
|
120
118
|
return dateTime.toString() === 'Invalid Date' ? null : dateTime;
|
|
121
119
|
};
|
|
122
120
|
// Returns the issue that a rule belongs to, or null if none.
|
|
123
|
-
const getIssue = exports.getIssue = (
|
|
124
|
-
const
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Return it.
|
|
130
|
-
return issueID;
|
|
121
|
+
const getIssue = exports.getIssue = (engineID, ruleID) => {
|
|
122
|
+
const engineRules = ruleSpecs[engineID];
|
|
123
|
+
// If the rule engine has no rule specifications:
|
|
124
|
+
if (!engineRules) {
|
|
125
|
+
// Return a failure result.
|
|
126
|
+
return null;
|
|
131
127
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (ruleID) {
|
|
138
|
-
// Return the issue ID.
|
|
139
|
-
return variable[toolID][ruleID];
|
|
128
|
+
const {invariant, variable} = engineRules;
|
|
129
|
+
// If the rule ID is invariant and classified:
|
|
130
|
+
if (invariant[ruleID]) {
|
|
131
|
+
// Return its issue ID.
|
|
132
|
+
return invariant[ruleID].issueID;
|
|
140
133
|
}
|
|
141
|
-
// Otherwise,
|
|
142
|
-
|
|
134
|
+
// Otherwise, find the first matching variable rule ID pattern.
|
|
135
|
+
const variableRuleID = Object
|
|
136
|
+
.keys(variable)
|
|
137
|
+
.find(pattern => new RegExp(`^${pattern}$`).test(ruleID));
|
|
138
|
+
// Return the issue ID if a pattern matched, or a failure result otherwise.
|
|
139
|
+
return variableRuleID ? variable[variableRuleID].issueID : null;
|
|
143
140
|
};
|
|
144
141
|
// Gets the names and categories of the job files.
|
|
145
142
|
const getJobNames = exports.getJobNames = async () => {
|
|
@@ -179,58 +176,6 @@ const getObject = exports.getObject = async filePath => {
|
|
|
179
176
|
exports.getRandomString = length => {
|
|
180
177
|
return Math.random().toString(36).slice(2, length + 2);
|
|
181
178
|
};
|
|
182
|
-
// Compiles a directory of the issue classifications of invariant and variable rules.
|
|
183
|
-
const getRuleIDs = exports.getRuleIDs = () => {
|
|
184
|
-
// Initialize data on invariant and variable rule IDs.
|
|
185
|
-
const invariant = {};
|
|
186
|
-
const variable = {};
|
|
187
|
-
// Initialize a validity checker.
|
|
188
|
-
const validityChecker = {};
|
|
189
|
-
// For each classified issue:
|
|
190
|
-
Object.keys(issuesClassification).forEach(issueID => {
|
|
191
|
-
const {tools, weight} = issuesClassification[issueID];
|
|
192
|
-
// If the weight is invalid:
|
|
193
|
-
if (weight < 1 || weight > 4) {
|
|
194
|
-
// Report this.
|
|
195
|
-
console.log(`ERROR: Issue ${issueID} weight is invalid`);
|
|
196
|
-
}
|
|
197
|
-
// For each tool that has any rules belonging to the issue:
|
|
198
|
-
Object.keys(tools).forEach(toolID => {
|
|
199
|
-
// For each such rule:
|
|
200
|
-
Object.keys(tools[toolID]).forEach(ruleID => {
|
|
201
|
-
// If it is a duplicate:
|
|
202
|
-
if (validityChecker[toolID]?.has(ruleID)) {
|
|
203
|
-
// Report this.
|
|
204
|
-
console.log(`ERROR: Rule ${ruleID} of tool ${toolID} belongs to 2 issues`);
|
|
205
|
-
}
|
|
206
|
-
// Otherwise, i.e. if it is not a duplicate:
|
|
207
|
-
else {
|
|
208
|
-
// Add it to the classified rules of the tool.
|
|
209
|
-
validityChecker[toolID] ??= new Set();
|
|
210
|
-
validityChecker[toolID].add(ruleID);
|
|
211
|
-
}
|
|
212
|
-
const rule = tools[toolID][ruleID];
|
|
213
|
-
// If it is variable:
|
|
214
|
-
if (rule.variable) {
|
|
215
|
-
variable[toolID] ??= {};
|
|
216
|
-
// Add its ID and the issue ID to the variable rule IDs.
|
|
217
|
-
variable[toolID][ruleID] = issueID;
|
|
218
|
-
}
|
|
219
|
-
// Otherwise, i.e. if it is invariant:
|
|
220
|
-
else {
|
|
221
|
-
invariant[toolID] ??= {};
|
|
222
|
-
// Add its ID and the issue ID to the invariant rule IDs.
|
|
223
|
-
invariant[toolID][ruleID] = issueID;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
// Return the data.
|
|
229
|
-
return {
|
|
230
|
-
invariant,
|
|
231
|
-
variable
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
179
|
// Returns a time stamp from a date.
|
|
235
180
|
const getTimeStamp = exports.getTimeStamp = date => {
|
|
236
181
|
const timeStamp = date.toISOString().slice(2).replace(/[-:]/g, '').slice(0, 11);
|
|
@@ -449,8 +394,6 @@ exports.processRec = async (testType, dirName, what, url, why) => {
|
|
|
449
394
|
};
|
|
450
395
|
// Concurrency lock for the `recs.json` file.
|
|
451
396
|
const recsLock = exports.recsLock = createLock();
|
|
452
|
-
// Variable and invariant rules.
|
|
453
|
-
exports.ruleIDs = getRuleIDs();
|
|
454
397
|
// Updates the test recommendations as a transaction.
|
|
455
398
|
const updateRecs = exports.updateRecs = (what, url, why) => recsLock(async () => {
|
|
456
399
|
// Get the data on waiting recommendations.
|
|
@@ -525,7 +468,7 @@ const getReport = exports.getReport = async (timeStamp, jobID) => {
|
|
|
525
468
|
}
|
|
526
469
|
};
|
|
527
470
|
// Adds issue IDs to the standard instances of a report.
|
|
528
|
-
exports.annotateReport = async (
|
|
471
|
+
exports.annotateReport = async (timeStamp, jobID) => {
|
|
529
472
|
// Get a copy of the report.
|
|
530
473
|
const report = await getReport(timeStamp, jobID);
|
|
531
474
|
// If this failed:
|
|
@@ -544,7 +487,7 @@ exports.annotateReport = async (ruleIDs, timeStamp, jobID) => {
|
|
|
544
487
|
for (const instance of result?.standardResult?.instances ?? []) {
|
|
545
488
|
const {ruleID} = instance;
|
|
546
489
|
// Classify its rule.
|
|
547
|
-
const issueID = getIssue(
|
|
490
|
+
const issueID = getIssue(which, ruleID);
|
|
548
491
|
// If the rule was classifiable:
|
|
549
492
|
if (issueID) {
|
|
550
493
|
// Add the issue ID to the instance.
|
|
@@ -617,7 +560,7 @@ exports.getReportData = async (timeStamp, jobID) => {
|
|
|
617
560
|
instances.forEach(instance => {
|
|
618
561
|
const {catalogIndex, issueID} = instance;
|
|
619
562
|
// If it has a non-ignorable classified issue ID:
|
|
620
|
-
if (issueID &&
|
|
563
|
+
if (issueID && issueSpecs[issueID] && issueID !== 'ignorable') {
|
|
621
564
|
// Ensure that the tool is in the temporary data.
|
|
622
565
|
reporterIDSet.add(which);
|
|
623
566
|
// Ensure that the issue is in the temporary data.
|