semantic-js-mcp 0.10.2 → 0.10.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +18 -72
- package/SETUP.md +413 -0
- package/docs/distribution.md +9 -16
- package/package.json +4 -4
- package/protocol.mjs +1 -1
- package/scripts/distribution-policy.mjs +1 -1
- package/scripts/documentation-contract.mjs +60 -19
- package/scripts/documentation-gate-smoke.mjs +182 -41
- package/scripts/documentation-gate.mjs +93 -9
- package/skills/semantic-navigation/references/protocol-literals.md +1 -1
- package/AGENT_SETUP.md +0 -176
|
@@ -2,7 +2,8 @@ import {DOCTOR_REASON, SEMANTIC_EVIDENCE_FOLLOW_UP_REASON} from "../protocol.mjs
|
|
|
2
2
|
|
|
3
3
|
export const DOCUMENTATION_FILE = Object.freeze({
|
|
4
4
|
README: "README.md",
|
|
5
|
-
|
|
5
|
+
SETUP: "SETUP.md",
|
|
6
|
+
DISTRIBUTION: "docs/distribution.md",
|
|
6
7
|
GETTING_STARTED: "docs/getting-started.md",
|
|
7
8
|
SEMANTIC_NAVIGATION_SKILL: "skills/semantic-navigation/SKILL.md",
|
|
8
9
|
CONTRIBUTING: "CONTRIBUTING.md",
|
|
@@ -10,26 +11,36 @@ export const DOCUMENTATION_FILE = Object.freeze({
|
|
|
10
11
|
});
|
|
11
12
|
|
|
12
13
|
export const PUBLIC_ROOT_DOCUMENT = Object.freeze([
|
|
13
|
-
"AGENT_SETUP.md",
|
|
14
14
|
"CHANGELOG.md",
|
|
15
15
|
"CONTRIBUTING.md",
|
|
16
16
|
"README.md",
|
|
17
17
|
"ROADMAP.md",
|
|
18
18
|
"SECURITY.md",
|
|
19
|
+
"SETUP.md",
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
export const PUBLIC_DOCUMENT_DIRECTORY = Object.freeze(["docs", "skills"]);
|
|
22
23
|
|
|
23
24
|
export const DOCUMENTATION_REQUIREMENT = Object.freeze({
|
|
24
|
-
README_HEADINGS: Object.freeze([
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
25
|
+
README_HEADINGS: Object.freeze(["Setup", "Runtime", "Development Setup", "Verification", "Current Limitations", "Reporting Problems"]),
|
|
26
|
+
README_FIRST_SECTION: "Setup",
|
|
27
|
+
README_SETUP_ENTRY_LITERALS: Object.freeze([
|
|
28
|
+
"Before running an installation command, follow [SETUP.md](SETUP.md).",
|
|
29
|
+
"single source of truth",
|
|
30
|
+
"Package installation alone does not configure an MCP host.",
|
|
31
|
+
]),
|
|
32
|
+
README_FORBIDDEN_SETUP_HEADINGS: Object.freeze(["Agent Installation", "Installation", "Updating the Codex plugin"]),
|
|
33
|
+
README_FORBIDDEN_SETUP_LITERALS: Object.freeze([
|
|
34
|
+
"codex plugin marketplace add elnonathan/semantic-js-mcp",
|
|
35
|
+
"codex plugin add semantic-js-mcp@elnonathan",
|
|
36
|
+
"npm install --global semantic-js-mcp",
|
|
37
|
+
"npm ci",
|
|
38
|
+
]),
|
|
39
|
+
DISTRIBUTION_FORBIDDEN_SETUP_LITERALS: Object.freeze([
|
|
40
|
+
"codex plugin marketplace add elnonathan/semantic-js-mcp",
|
|
41
|
+
"codex plugin marketplace upgrade elnonathan",
|
|
42
|
+
"codex plugin add semantic-js-mcp@elnonathan",
|
|
31
43
|
]),
|
|
32
|
-
README_LITERALS: Object.freeze(["codex plugin marketplace upgrade elnonathan", "codex plugin add semantic-js-mcp@elnonathan"]),
|
|
33
44
|
GETTING_STARTED_HEADINGS: Object.freeze([
|
|
34
45
|
"Trace A Symbol",
|
|
35
46
|
"Review A Security-Sensitive Change",
|
|
@@ -40,24 +51,48 @@ export const DOCUMENTATION_REQUIREMENT = Object.freeze({
|
|
|
40
51
|
"Untrusted Diagnostics",
|
|
41
52
|
"Startup Failure",
|
|
42
53
|
]),
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
54
|
+
SETUP_HEADINGS: Object.freeze([
|
|
55
|
+
"Guards",
|
|
56
|
+
"Prerequisites",
|
|
57
|
+
"Choose A Version",
|
|
58
|
+
"Install With Codex",
|
|
59
|
+
"Install With Another MCP Host",
|
|
60
|
+
"Verify The Installation",
|
|
61
|
+
"Troubleshooting",
|
|
62
|
+
"Update",
|
|
63
|
+
"Rollback",
|
|
49
64
|
"Removal",
|
|
50
|
-
"
|
|
65
|
+
"Source Checkout",
|
|
66
|
+
"Background",
|
|
51
67
|
]),
|
|
68
|
+
SETUP_FIRST_SECTION: "Guards",
|
|
69
|
+
SETUP_LAST_SECTION: "Background",
|
|
70
|
+
DISTRIBUTION_HEADINGS: Object.freeze(["Release Verification", "npm Trusted Publishing"]),
|
|
52
71
|
SEMANTIC_NAVIGATION_SKILL_HEADINGS: Object.freeze(["Preserve Combination Invariants"]),
|
|
53
|
-
README_LINKS: Object.freeze(["
|
|
72
|
+
README_LINKS: Object.freeze(["SETUP.md", "docs/getting-started.md", "CONTRIBUTING.md", "SECURITY.md"]),
|
|
54
73
|
GETTING_STARTED_LITERALS: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_PARTIAL, DOCTOR_REASON.RUNTIME_COMPONENT_MISSING]),
|
|
55
|
-
|
|
74
|
+
SETUP_LITERALS: Object.freeze([
|
|
56
75
|
"semantic-js-mcp doctor",
|
|
57
76
|
"semantic-js-mcp serve",
|
|
58
77
|
"does not override system, developer, organization, repository",
|
|
59
78
|
"npm uninstall --global semantic-js-mcp",
|
|
60
79
|
"server name: `semanticjsmcp`",
|
|
80
|
+
"codex plugin marketplace upgrade elnonathan",
|
|
81
|
+
"Do not run `npm install semantic-js-mcp` without `--global`.",
|
|
82
|
+
"Treat `EPERM`, `EACCES`, `TAR_ENTRY_ERROR`, or a missing-file error as a partial installation.",
|
|
83
|
+
"Do not start `semantic-js-mcp serve` manually.",
|
|
84
|
+
"Choose exactly one installation route.",
|
|
85
|
+
"pending-restart",
|
|
86
|
+
"No source-code call is required",
|
|
87
|
+
"Do not run the global executable checks",
|
|
88
|
+
]),
|
|
89
|
+
DISTRIBUTION_LITERALS: Object.freeze([
|
|
90
|
+
"npm run release:verify",
|
|
91
|
+
"npm run verify:published -- <version>",
|
|
92
|
+
"The `publish.yml` workflow publishes tags matching `v*`",
|
|
93
|
+
"protected `npm-publish` environment",
|
|
94
|
+
"No long-lived npm token is used.",
|
|
95
|
+
"Verify the published package before creating the matching GitHub release.",
|
|
61
96
|
]),
|
|
62
97
|
SEMANTIC_NAVIGATION_SKILL_LITERALS: Object.freeze([
|
|
63
98
|
"Identify every producer of the value",
|
|
@@ -71,6 +106,12 @@ export const DOCUMENTATION_REASON = Object.freeze({
|
|
|
71
106
|
HEADING_MISSING: "required-heading-missing",
|
|
72
107
|
LINK_MISSING: "required-link-missing",
|
|
73
108
|
LITERAL_MISSING: "required-literal-missing",
|
|
109
|
+
SETUP_ENTRY_POINT_NOT_FIRST: "setup-entry-point-not-first",
|
|
110
|
+
DUPLICATE_SETUP_SECTION: "duplicate-setup-section",
|
|
111
|
+
DUPLICATE_SETUP_COMMAND: "duplicate-setup-command",
|
|
112
|
+
SETUP_GUARDS_NOT_FIRST: "setup-guards-not-first",
|
|
113
|
+
SETUP_SECTION_ORDER_INVALID: "setup-section-order-invalid",
|
|
114
|
+
SETUP_BACKGROUND_NOT_LAST: "setup-background-not-last",
|
|
74
115
|
PRIVATE_COORDINATION: "private-coordination-language-found",
|
|
75
116
|
LOCAL_PATH: "local-absolute-path-found",
|
|
76
117
|
});
|
|
@@ -6,8 +6,9 @@ import {evaluateDocumentation} from "./documentation-gate.mjs";
|
|
|
6
6
|
|
|
7
7
|
const headingText = (headings) => headings.map((heading) => `## ${heading}`).join("\n");
|
|
8
8
|
const valid = {
|
|
9
|
-
[DOCUMENTATION_FILE.README]: `${headingText(DOCUMENTATION_REQUIREMENT.README_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.README_LINKS.map((link) => `[doc](${link})`).join("\n")}\n${DOCUMENTATION_REQUIREMENT.
|
|
10
|
-
[DOCUMENTATION_FILE.
|
|
9
|
+
[DOCUMENTATION_FILE.README]: `${headingText(DOCUMENTATION_REQUIREMENT.README_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.README_LINKS.map((link) => `[doc](${link})`).join("\n")}\n${DOCUMENTATION_REQUIREMENT.README_SETUP_ENTRY_LITERALS.join("\n")}`,
|
|
10
|
+
[DOCUMENTATION_FILE.SETUP]: `${headingText(DOCUMENTATION_REQUIREMENT.SETUP_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.SETUP_LITERALS.join("\n")}`,
|
|
11
|
+
[DOCUMENTATION_FILE.DISTRIBUTION]: `${headingText(DOCUMENTATION_REQUIREMENT.DISTRIBUTION_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS.join("\n")}`,
|
|
11
12
|
[DOCUMENTATION_FILE.GETTING_STARTED]: `${headingText(DOCUMENTATION_REQUIREMENT.GETTING_STARTED_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.GETTING_STARTED_LITERALS.join("\n")}`,
|
|
12
13
|
[DOCUMENTATION_FILE.SEMANTIC_NAVIGATION_SKILL]: `${headingText(DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_HEADINGS)}\n${DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_LITERALS.join("\n")}`,
|
|
13
14
|
[DOCUMENTATION_FILE.CONTRIBUTING]: "# Contributing",
|
|
@@ -16,6 +17,105 @@ const valid = {
|
|
|
16
17
|
|
|
17
18
|
strictEqual(evaluateDocumentation(valid).length, 0, "Valid public documentation failed the gate");
|
|
18
19
|
|
|
20
|
+
const lateSetupGuards = {
|
|
21
|
+
...valid,
|
|
22
|
+
[DOCUMENTATION_FILE.SETUP]: valid[DOCUMENTATION_FILE.SETUP].replace("## Guards\n", "").concat("\n## Guards\n"),
|
|
23
|
+
};
|
|
24
|
+
strictEqual(
|
|
25
|
+
evaluateDocumentation(lateSetupGuards).some((finding) => finding.reason === DOCUMENTATION_REASON.SETUP_GUARDS_NOT_FIRST),
|
|
26
|
+
true,
|
|
27
|
+
"Late setup guards were accepted",
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const fencedSetupGuards = {
|
|
31
|
+
...valid,
|
|
32
|
+
[DOCUMENTATION_FILE.SETUP]: valid[DOCUMENTATION_FILE.SETUP].replace("## Guards\n", "```md\n## Guards\n```\n"),
|
|
33
|
+
};
|
|
34
|
+
strictEqual(
|
|
35
|
+
evaluateDocumentation(fencedSetupGuards).some(
|
|
36
|
+
(finding) =>
|
|
37
|
+
finding.reason === DOCUMENTATION_REASON.HEADING_MISSING &&
|
|
38
|
+
finding.file === DOCUMENTATION_FILE.SETUP &&
|
|
39
|
+
finding.heading === DOCUMENTATION_REQUIREMENT.SETUP_FIRST_SECTION,
|
|
40
|
+
),
|
|
41
|
+
true,
|
|
42
|
+
"Setup heading inside a fenced block was accepted",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const invalidSetupOrder = {
|
|
46
|
+
...valid,
|
|
47
|
+
[DOCUMENTATION_FILE.SETUP]: valid[DOCUMENTATION_FILE.SETUP]
|
|
48
|
+
.replace("## Prerequisites\n", "## Setup Order Placeholder\n")
|
|
49
|
+
.replace("## Choose A Version\n", "## Prerequisites\n")
|
|
50
|
+
.replace("## Setup Order Placeholder\n", "## Choose A Version\n"),
|
|
51
|
+
};
|
|
52
|
+
strictEqual(
|
|
53
|
+
evaluateDocumentation(invalidSetupOrder).some((finding) => finding.reason === DOCUMENTATION_REASON.SETUP_SECTION_ORDER_INVALID),
|
|
54
|
+
true,
|
|
55
|
+
"Invalid setup section order was accepted",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const earlySetupBackground = {
|
|
59
|
+
...valid,
|
|
60
|
+
[DOCUMENTATION_FILE.SETUP]: valid[DOCUMENTATION_FILE.SETUP]
|
|
61
|
+
.replace("## Background\n", "")
|
|
62
|
+
.replace("## Troubleshooting\n", "## Background\n## Troubleshooting\n"),
|
|
63
|
+
};
|
|
64
|
+
strictEqual(
|
|
65
|
+
evaluateDocumentation(earlySetupBackground).some((finding) => finding.reason === DOCUMENTATION_REASON.SETUP_BACKGROUND_NOT_LAST),
|
|
66
|
+
true,
|
|
67
|
+
"Setup background outside the final section was accepted",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const lateSetupEntryPoint = {
|
|
71
|
+
...valid,
|
|
72
|
+
[DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].replace("## Setup\n", "").concat("\n## Setup\n"),
|
|
73
|
+
};
|
|
74
|
+
strictEqual(
|
|
75
|
+
evaluateDocumentation(lateSetupEntryPoint).some((finding) => finding.reason === DOCUMENTATION_REASON.SETUP_ENTRY_POINT_NOT_FIRST),
|
|
76
|
+
true,
|
|
77
|
+
"Late setup entry point was accepted",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const duplicateSetupSection = {
|
|
81
|
+
...valid,
|
|
82
|
+
[DOCUMENTATION_FILE.README]: `${valid[DOCUMENTATION_FILE.README]}\n## ${DOCUMENTATION_REQUIREMENT.README_FORBIDDEN_SETUP_HEADINGS[1]}`,
|
|
83
|
+
};
|
|
84
|
+
strictEqual(
|
|
85
|
+
evaluateDocumentation(duplicateSetupSection).some((finding) => finding.reason === DOCUMENTATION_REASON.DUPLICATE_SETUP_SECTION),
|
|
86
|
+
true,
|
|
87
|
+
"Duplicate README setup section was accepted",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.README_FORBIDDEN_SETUP_LITERALS) {
|
|
91
|
+
const duplicateSetupCommand = {
|
|
92
|
+
...valid,
|
|
93
|
+
[DOCUMENTATION_FILE.README]: `${valid[DOCUMENTATION_FILE.README]}\n${literal}`,
|
|
94
|
+
};
|
|
95
|
+
strictEqual(
|
|
96
|
+
evaluateDocumentation(duplicateSetupCommand).some(
|
|
97
|
+
(finding) => finding.reason === DOCUMENTATION_REASON.DUPLICATE_SETUP_COMMAND && finding.literal === literal,
|
|
98
|
+
),
|
|
99
|
+
true,
|
|
100
|
+
`Duplicate README setup command was accepted: ${literal}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const duplicateDistributionSetupCommand = {
|
|
105
|
+
...valid,
|
|
106
|
+
[DOCUMENTATION_FILE.DISTRIBUTION]: `${valid[DOCUMENTATION_FILE.DISTRIBUTION]}\n${DOCUMENTATION_REQUIREMENT.DISTRIBUTION_FORBIDDEN_SETUP_LITERALS[0]}`,
|
|
107
|
+
};
|
|
108
|
+
strictEqual(
|
|
109
|
+
evaluateDocumentation(duplicateDistributionSetupCommand).some(
|
|
110
|
+
(finding) =>
|
|
111
|
+
finding.file === DOCUMENTATION_FILE.DISTRIBUTION &&
|
|
112
|
+
finding.reason === DOCUMENTATION_REASON.DUPLICATE_SETUP_COMMAND &&
|
|
113
|
+
finding.literal === DOCUMENTATION_REQUIREMENT.DISTRIBUTION_FORBIDDEN_SETUP_LITERALS[0],
|
|
114
|
+
),
|
|
115
|
+
true,
|
|
116
|
+
"Duplicate distribution setup command was accepted",
|
|
117
|
+
);
|
|
118
|
+
|
|
19
119
|
const missingHeading = {...valid, [DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].replace("## Verification", "")};
|
|
20
120
|
strictEqual(
|
|
21
121
|
evaluateDocumentation(missingHeading).some((finding) => finding.reason === DOCUMENTATION_REASON.HEADING_MISSING),
|
|
@@ -48,96 +148,137 @@ strictEqual(
|
|
|
48
148
|
"Missing canonical example literal was accepted",
|
|
49
149
|
);
|
|
50
150
|
|
|
51
|
-
const
|
|
151
|
+
const missingSetupLink = {
|
|
52
152
|
...valid,
|
|
53
|
-
[DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].
|
|
153
|
+
[DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].replaceAll(
|
|
154
|
+
`](${DOCUMENTATION_REQUIREMENT.README_LINKS[0]})`,
|
|
155
|
+
"](missing-setup.md)",
|
|
156
|
+
),
|
|
54
157
|
};
|
|
55
158
|
strictEqual(
|
|
56
|
-
evaluateDocumentation(
|
|
57
|
-
(finding) =>
|
|
58
|
-
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING && finding.literal === DOCUMENTATION_REQUIREMENT.README_LITERALS[0],
|
|
159
|
+
evaluateDocumentation(missingSetupLink).some(
|
|
160
|
+
(finding) => finding.reason === DOCUMENTATION_REASON.LINK_MISSING && finding.link === DOCUMENTATION_REQUIREMENT.README_LINKS[0],
|
|
59
161
|
),
|
|
60
162
|
true,
|
|
61
|
-
"Missing
|
|
163
|
+
"Missing setup README link was accepted",
|
|
62
164
|
);
|
|
63
165
|
|
|
64
|
-
const
|
|
166
|
+
const missingSetupEntryInstruction = {
|
|
65
167
|
...valid,
|
|
66
|
-
[DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].replace(
|
|
168
|
+
[DOCUMENTATION_FILE.README]: valid[DOCUMENTATION_FILE.README].replace(DOCUMENTATION_REQUIREMENT.README_SETUP_ENTRY_LITERALS[0], ""),
|
|
67
169
|
};
|
|
68
170
|
strictEqual(
|
|
69
|
-
evaluateDocumentation(
|
|
70
|
-
(finding) =>
|
|
171
|
+
evaluateDocumentation(missingSetupEntryInstruction).some(
|
|
172
|
+
(finding) =>
|
|
173
|
+
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING &&
|
|
174
|
+
finding.literal === DOCUMENTATION_REQUIREMENT.README_SETUP_ENTRY_LITERALS[0],
|
|
71
175
|
),
|
|
72
176
|
true,
|
|
73
|
-
"Missing
|
|
177
|
+
"Missing README setup instruction was accepted",
|
|
74
178
|
);
|
|
75
179
|
|
|
76
|
-
const
|
|
77
|
-
delete
|
|
180
|
+
const missingSetup = {...valid};
|
|
181
|
+
delete missingSetup[DOCUMENTATION_FILE.SETUP];
|
|
78
182
|
strictEqual(
|
|
79
|
-
evaluateDocumentation(
|
|
80
|
-
(finding) => finding.reason === DOCUMENTATION_REASON.FILE_MISSING && finding.file === DOCUMENTATION_FILE.
|
|
183
|
+
evaluateDocumentation(missingSetup).some(
|
|
184
|
+
(finding) => finding.reason === DOCUMENTATION_REASON.FILE_MISSING && finding.file === DOCUMENTATION_FILE.SETUP,
|
|
81
185
|
),
|
|
82
186
|
true,
|
|
83
|
-
"Missing
|
|
187
|
+
"Missing setup guide was accepted",
|
|
84
188
|
);
|
|
85
189
|
|
|
86
|
-
const
|
|
190
|
+
for (const [literal, message] of [
|
|
191
|
+
["does not override system, developer, organization, repository", "Missing setup safety boundary was accepted"],
|
|
192
|
+
["server name: `semanticjsmcp`", "Missing generic MCP server name was accepted"],
|
|
193
|
+
["codex plugin marketplace upgrade elnonathan", "Missing Codex marketplace refresh was accepted"],
|
|
194
|
+
["Do not run `npm install semantic-js-mcp` without `--global`.", "Missing local npm installation prohibition was accepted"],
|
|
195
|
+
[
|
|
196
|
+
"Treat `EPERM`, `EACCES`, `TAR_ENTRY_ERROR`, or a missing-file error as a partial installation.",
|
|
197
|
+
"Missing partial-installation warning was accepted",
|
|
198
|
+
],
|
|
199
|
+
["Do not start `semantic-js-mcp serve` manually.", "Missing manual stdio startup prohibition was accepted"],
|
|
200
|
+
["Choose exactly one installation route.", "Missing single-route boundary was accepted"],
|
|
201
|
+
["No source-code call is required", "Missing installation authority boundary was accepted"],
|
|
202
|
+
]) {
|
|
203
|
+
const missingInstruction = {
|
|
204
|
+
...valid,
|
|
205
|
+
[DOCUMENTATION_FILE.SETUP]: valid[DOCUMENTATION_FILE.SETUP].replace(literal, ""),
|
|
206
|
+
};
|
|
207
|
+
strictEqual(
|
|
208
|
+
evaluateDocumentation(missingInstruction).some(
|
|
209
|
+
(finding) => finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING && finding.literal === literal,
|
|
210
|
+
),
|
|
211
|
+
true,
|
|
212
|
+
message,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const missingCombinationInvariant = {
|
|
87
217
|
...valid,
|
|
88
|
-
[DOCUMENTATION_FILE.
|
|
218
|
+
[DOCUMENTATION_FILE.SEMANTIC_NAVIGATION_SKILL]: valid[DOCUMENTATION_FILE.SEMANTIC_NAVIGATION_SKILL].replace(
|
|
219
|
+
DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_LITERALS[1],
|
|
220
|
+
"",
|
|
221
|
+
),
|
|
89
222
|
};
|
|
90
223
|
strictEqual(
|
|
91
|
-
evaluateDocumentation(
|
|
224
|
+
evaluateDocumentation(missingCombinationInvariant).some(
|
|
92
225
|
(finding) =>
|
|
93
|
-
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING &&
|
|
226
|
+
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING &&
|
|
227
|
+
finding.literal === DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_LITERALS[1],
|
|
94
228
|
),
|
|
95
229
|
true,
|
|
96
|
-
"Missing
|
|
230
|
+
"Missing combination-invariant guidance was accepted",
|
|
97
231
|
);
|
|
98
232
|
|
|
99
|
-
const
|
|
233
|
+
const missingTrustedPublishingPolicy = {
|
|
100
234
|
...valid,
|
|
101
|
-
[DOCUMENTATION_FILE.
|
|
235
|
+
[DOCUMENTATION_FILE.DISTRIBUTION]: valid[DOCUMENTATION_FILE.DISTRIBUTION].replace(DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS[2], ""),
|
|
102
236
|
};
|
|
103
237
|
strictEqual(
|
|
104
|
-
evaluateDocumentation(
|
|
238
|
+
evaluateDocumentation(missingTrustedPublishingPolicy).some(
|
|
105
239
|
(finding) =>
|
|
106
|
-
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING && finding.literal === DOCUMENTATION_REQUIREMENT.
|
|
240
|
+
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING && finding.literal === DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS[2],
|
|
107
241
|
),
|
|
108
242
|
true,
|
|
109
|
-
"Missing
|
|
243
|
+
"Missing trusted-publishing policy was accepted",
|
|
110
244
|
);
|
|
111
245
|
|
|
112
|
-
const
|
|
246
|
+
const missingPostpublicationOrder = {
|
|
113
247
|
...valid,
|
|
114
|
-
[DOCUMENTATION_FILE.
|
|
115
|
-
DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_LITERALS[1],
|
|
116
|
-
"",
|
|
117
|
-
),
|
|
248
|
+
[DOCUMENTATION_FILE.DISTRIBUTION]: valid[DOCUMENTATION_FILE.DISTRIBUTION].replace(DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS[5], ""),
|
|
118
249
|
};
|
|
119
250
|
strictEqual(
|
|
120
|
-
evaluateDocumentation(
|
|
251
|
+
evaluateDocumentation(missingPostpublicationOrder).some(
|
|
121
252
|
(finding) =>
|
|
122
|
-
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING &&
|
|
123
|
-
finding.literal === DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_LITERALS[1],
|
|
253
|
+
finding.reason === DOCUMENTATION_REASON.LITERAL_MISSING && finding.literal === DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS[5],
|
|
124
254
|
),
|
|
125
255
|
true,
|
|
126
|
-
"Missing
|
|
256
|
+
"Missing postpublication order was accepted",
|
|
127
257
|
);
|
|
128
258
|
|
|
129
259
|
process.stdout.write(
|
|
130
260
|
`${JSON.stringify(
|
|
131
261
|
{
|
|
132
262
|
validDocumentation: "pass",
|
|
263
|
+
lateSetupGuards: "rejected",
|
|
264
|
+
fencedSetupGuards: "rejected",
|
|
265
|
+
invalidSetupOrder: "rejected",
|
|
266
|
+
earlySetupBackground: "rejected",
|
|
267
|
+
lateSetupEntryPoint: "rejected",
|
|
268
|
+
duplicateSetupSection: "rejected",
|
|
269
|
+
duplicateSetupCommand: "rejected",
|
|
270
|
+
duplicateDistributionSetupCommand: "rejected",
|
|
133
271
|
missingHeading: "rejected",
|
|
134
272
|
missingLiteral: "rejected",
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
273
|
+
missingSetupLink: "rejected",
|
|
274
|
+
missingSetupEntryInstruction: "rejected",
|
|
275
|
+
missingSetup: "rejected",
|
|
276
|
+
missingSetupSafetyBoundary: "rejected",
|
|
139
277
|
missingGenericServerName: "rejected",
|
|
278
|
+
missingCodexMarketplaceRefresh: "rejected",
|
|
140
279
|
missingCombinationInvariant: "rejected",
|
|
280
|
+
missingTrustedPublishingPolicy: "rejected",
|
|
281
|
+
missingPostpublicationOrder: "rejected",
|
|
141
282
|
privateCoordination: "rejected",
|
|
142
283
|
},
|
|
143
284
|
null,
|
|
@@ -13,15 +13,49 @@ import {
|
|
|
13
13
|
PUBLIC_ROOT_DOCUMENT,
|
|
14
14
|
} from "./documentation-contract.mjs";
|
|
15
15
|
|
|
16
|
+
function markdownStructuralLines(source) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
let fence;
|
|
19
|
+
for (const line of source.split("\n")) {
|
|
20
|
+
const match = /^\s{0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
21
|
+
if (match && fence === undefined) {
|
|
22
|
+
fence = {marker: match[1][0], length: match[1].length};
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (match && match[1][0] === fence?.marker && match[1].length >= fence.length && match[2].trim() === "") {
|
|
26
|
+
fence = undefined;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (fence === undefined) lines.push(line);
|
|
30
|
+
}
|
|
31
|
+
return lines;
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
function markdownHeadings(source) {
|
|
17
35
|
return new Set(
|
|
18
|
-
source
|
|
19
|
-
.split("\n")
|
|
36
|
+
markdownStructuralLines(source)
|
|
20
37
|
.map((line) => /^#{1,6}\s+(.+?)\s*$/.exec(line)?.[1])
|
|
21
38
|
.filter(Boolean),
|
|
22
39
|
);
|
|
23
40
|
}
|
|
24
41
|
|
|
42
|
+
function levelTwoHeadings(source) {
|
|
43
|
+
return markdownStructuralLines(source)
|
|
44
|
+
.map((line) => /^##\s+(.+?)\s*$/.exec(line)?.[1])
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function requiredHeadingsAreOrdered(source, requiredHeadings) {
|
|
49
|
+
const headings = levelTwoHeadings(source);
|
|
50
|
+
let nextIndex = 0;
|
|
51
|
+
for (const requiredHeading of requiredHeadings) {
|
|
52
|
+
const index = headings.indexOf(requiredHeading, nextIndex);
|
|
53
|
+
if (index === -1) return false;
|
|
54
|
+
nextIndex = index + 1;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
function repositoryPath(root, absolute) {
|
|
26
60
|
return path.relative(root, absolute).split(path.sep).join("/");
|
|
27
61
|
}
|
|
@@ -35,7 +69,8 @@ export function evaluateDocumentation(documents) {
|
|
|
35
69
|
|
|
36
70
|
const headingRequirements = [
|
|
37
71
|
[DOCUMENTATION_FILE.README, DOCUMENTATION_REQUIREMENT.README_HEADINGS],
|
|
38
|
-
[DOCUMENTATION_FILE.
|
|
72
|
+
[DOCUMENTATION_FILE.SETUP, DOCUMENTATION_REQUIREMENT.SETUP_HEADINGS],
|
|
73
|
+
[DOCUMENTATION_FILE.DISTRIBUTION, DOCUMENTATION_REQUIREMENT.DISTRIBUTION_HEADINGS],
|
|
39
74
|
[DOCUMENTATION_FILE.GETTING_STARTED, DOCUMENTATION_REQUIREMENT.GETTING_STARTED_HEADINGS],
|
|
40
75
|
[DOCUMENTATION_FILE.SEMANTIC_NAVIGATION_SKILL, DOCUMENTATION_REQUIREMENT.SEMANTIC_NAVIGATION_SKILL_HEADINGS],
|
|
41
76
|
];
|
|
@@ -50,14 +85,30 @@ export function evaluateDocumentation(documents) {
|
|
|
50
85
|
|
|
51
86
|
const readme = documents[DOCUMENTATION_FILE.README];
|
|
52
87
|
if (readme !== undefined) {
|
|
88
|
+
if (levelTwoHeadings(readme)[0] !== DOCUMENTATION_REQUIREMENT.README_FIRST_SECTION) {
|
|
89
|
+
findings.push({
|
|
90
|
+
file: DOCUMENTATION_FILE.README,
|
|
91
|
+
reason: DOCUMENTATION_REASON.SETUP_ENTRY_POINT_NOT_FIRST,
|
|
92
|
+
expectedHeading: DOCUMENTATION_REQUIREMENT.README_FIRST_SECTION,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
53
95
|
for (const link of DOCUMENTATION_REQUIREMENT.README_LINKS) {
|
|
54
96
|
if (readme.includes(`](${link})`)) continue;
|
|
55
97
|
findings.push({file: DOCUMENTATION_FILE.README, reason: DOCUMENTATION_REASON.LINK_MISSING, link});
|
|
56
98
|
}
|
|
57
|
-
for (const literal of DOCUMENTATION_REQUIREMENT.
|
|
99
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.README_SETUP_ENTRY_LITERALS) {
|
|
58
100
|
if (readme.includes(literal)) continue;
|
|
59
101
|
findings.push({file: DOCUMENTATION_FILE.README, reason: DOCUMENTATION_REASON.LITERAL_MISSING, literal});
|
|
60
102
|
}
|
|
103
|
+
const readmeHeadings = markdownHeadings(readme);
|
|
104
|
+
for (const heading of DOCUMENTATION_REQUIREMENT.README_FORBIDDEN_SETUP_HEADINGS) {
|
|
105
|
+
if (!readmeHeadings.has(heading)) continue;
|
|
106
|
+
findings.push({file: DOCUMENTATION_FILE.README, reason: DOCUMENTATION_REASON.DUPLICATE_SETUP_SECTION, heading});
|
|
107
|
+
}
|
|
108
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.README_FORBIDDEN_SETUP_LITERALS) {
|
|
109
|
+
if (!readme.includes(literal)) continue;
|
|
110
|
+
findings.push({file: DOCUMENTATION_FILE.README, reason: DOCUMENTATION_REASON.DUPLICATE_SETUP_COMMAND, literal});
|
|
111
|
+
}
|
|
61
112
|
}
|
|
62
113
|
|
|
63
114
|
const gettingStarted = documents[DOCUMENTATION_FILE.GETTING_STARTED];
|
|
@@ -68,11 +119,32 @@ export function evaluateDocumentation(documents) {
|
|
|
68
119
|
}
|
|
69
120
|
}
|
|
70
121
|
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
findings.push({
|
|
122
|
+
const setup = documents[DOCUMENTATION_FILE.SETUP];
|
|
123
|
+
if (setup !== undefined) {
|
|
124
|
+
const setupHeadings = levelTwoHeadings(setup);
|
|
125
|
+
if (setupHeadings[0] !== DOCUMENTATION_REQUIREMENT.SETUP_FIRST_SECTION) {
|
|
126
|
+
findings.push({
|
|
127
|
+
file: DOCUMENTATION_FILE.SETUP,
|
|
128
|
+
reason: DOCUMENTATION_REASON.SETUP_GUARDS_NOT_FIRST,
|
|
129
|
+
expectedHeading: DOCUMENTATION_REQUIREMENT.SETUP_FIRST_SECTION,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (!requiredHeadingsAreOrdered(setup, DOCUMENTATION_REQUIREMENT.SETUP_HEADINGS)) {
|
|
133
|
+
findings.push({
|
|
134
|
+
file: DOCUMENTATION_FILE.SETUP,
|
|
135
|
+
reason: DOCUMENTATION_REASON.SETUP_SECTION_ORDER_INVALID,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (setupHeadings.at(-1) !== DOCUMENTATION_REQUIREMENT.SETUP_LAST_SECTION) {
|
|
139
|
+
findings.push({
|
|
140
|
+
file: DOCUMENTATION_FILE.SETUP,
|
|
141
|
+
reason: DOCUMENTATION_REASON.SETUP_BACKGROUND_NOT_LAST,
|
|
142
|
+
expectedHeading: DOCUMENTATION_REQUIREMENT.SETUP_LAST_SECTION,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.SETUP_LITERALS) {
|
|
146
|
+
if (setup.includes(literal)) continue;
|
|
147
|
+
findings.push({file: DOCUMENTATION_FILE.SETUP, reason: DOCUMENTATION_REASON.LITERAL_MISSING, literal});
|
|
76
148
|
}
|
|
77
149
|
}
|
|
78
150
|
|
|
@@ -88,6 +160,18 @@ export function evaluateDocumentation(documents) {
|
|
|
88
160
|
}
|
|
89
161
|
}
|
|
90
162
|
|
|
163
|
+
const distribution = documents[DOCUMENTATION_FILE.DISTRIBUTION];
|
|
164
|
+
if (distribution !== undefined) {
|
|
165
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.DISTRIBUTION_LITERALS) {
|
|
166
|
+
if (distribution.includes(literal)) continue;
|
|
167
|
+
findings.push({file: DOCUMENTATION_FILE.DISTRIBUTION, reason: DOCUMENTATION_REASON.LITERAL_MISSING, literal});
|
|
168
|
+
}
|
|
169
|
+
for (const literal of DOCUMENTATION_REQUIREMENT.DISTRIBUTION_FORBIDDEN_SETUP_LITERALS) {
|
|
170
|
+
if (!distribution.includes(literal)) continue;
|
|
171
|
+
findings.push({file: DOCUMENTATION_FILE.DISTRIBUTION, reason: DOCUMENTATION_REASON.DUPLICATE_SETUP_COMMAND, literal});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
91
175
|
for (const [file, source] of Object.entries(documents)) {
|
|
92
176
|
if (PRIVATE_COORDINATION_PATTERN.test(source)) {
|
|
93
177
|
findings.push({file, reason: DOCUMENTATION_REASON.PRIVATE_COORDINATION});
|