waymark-docs 0.1.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +302 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +29 -0
- package/dist/commands/find.d.ts +2 -0
- package/dist/commands/find.js +176 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +11 -0
- package/dist/commands/ls.d.ts +2 -0
- package/dist/commands/ls.js +39 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +71 -0
- package/dist/configuration/index.d.ts +3 -0
- package/dist/configuration/index.js +2 -0
- package/dist/configuration/initialize.d.ts +3 -0
- package/dist/configuration/initialize.js +34 -0
- package/dist/configuration/load.d.ts +27 -0
- package/dist/configuration/load.js +174 -0
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.js +13 -0
- package/dist/documents/classify.d.ts +29 -0
- package/dist/documents/classify.js +203 -0
- package/dist/documents/filter.d.ts +24 -0
- package/dist/documents/filter.js +212 -0
- package/dist/documents/index.d.ts +3 -0
- package/dist/documents/index.js +2 -0
- package/dist/documents/scan.d.ts +33 -0
- package/dist/documents/scan.js +134 -0
- package/dist/filesystem.d.ts +2 -0
- package/dist/filesystem.js +15 -0
- package/package.json +55 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { isErrorWithCode, pathExists } from "../filesystem.js";
|
|
4
|
+
import { configurationFileName, findConfigurationPath } from "./load.js";
|
|
5
|
+
const starterConfiguration = "# When true, document metadata must be nested under a `waymark` frontmatter key.\n" +
|
|
6
|
+
"require-namespace: false\n" +
|
|
7
|
+
"kinds:\n" +
|
|
8
|
+
" example-kind: Explain when agents should read this kind of document\n" +
|
|
9
|
+
"tags:\n" +
|
|
10
|
+
" example-tag: Explain the topic represented by this tag\n";
|
|
11
|
+
export async function initializeConfiguration(directoryPath) {
|
|
12
|
+
const configurationPath = join(directoryPath, configurationFileName);
|
|
13
|
+
if (await pathExists(configurationPath)) {
|
|
14
|
+
throw new Error(`A Waymark configuration already exists at ${configurationPath}.`);
|
|
15
|
+
}
|
|
16
|
+
const ancestorConfigurationPath = await findConfigurationPath(dirname(directoryPath));
|
|
17
|
+
if (ancestorConfigurationPath) {
|
|
18
|
+
throw new Error("Cannot create a nested Waymark configuration because one already " +
|
|
19
|
+
`exists at ${ancestorConfigurationPath}.`);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await writeFile(configurationPath, starterConfiguration, {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
flag: "wx",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isErrorWithCode(error, "EEXIST")) {
|
|
29
|
+
throw new Error(`A Waymark configuration already exists at ${configurationPath}.`);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
return { configurationPath };
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const configurationFileName: string;
|
|
2
|
+
export type ConfigurationDeclaration = {
|
|
3
|
+
description: string;
|
|
4
|
+
};
|
|
5
|
+
export type ConfigurationDiagnostic = {
|
|
6
|
+
path: string;
|
|
7
|
+
field: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
export type Configuration = {
|
|
11
|
+
requireNamespace: boolean;
|
|
12
|
+
kinds: Map<string, ConfigurationDeclaration>;
|
|
13
|
+
tags: Map<string, ConfigurationDeclaration>;
|
|
14
|
+
exclusions: string[];
|
|
15
|
+
};
|
|
16
|
+
export type ConfigurationLoadResult = {
|
|
17
|
+
kind: "loaded";
|
|
18
|
+
rootPath: string;
|
|
19
|
+
configuration: Configuration;
|
|
20
|
+
diagnostics: ConfigurationDiagnostic[];
|
|
21
|
+
} | {
|
|
22
|
+
kind: "malformed";
|
|
23
|
+
rootPath: string;
|
|
24
|
+
diagnostics: ConfigurationDiagnostic[];
|
|
25
|
+
};
|
|
26
|
+
export declare function loadConfiguration(startingDirectoryPath: string): Promise<ConfigurationLoadResult>;
|
|
27
|
+
export declare function findConfigurationPath(startingDirectoryPath: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { pathExists } from "../filesystem.js";
|
|
5
|
+
export const configurationFileName = "waymark.yaml";
|
|
6
|
+
export async function loadConfiguration(startingDirectoryPath) {
|
|
7
|
+
const configurationPath = await findConfigurationPath(startingDirectoryPath);
|
|
8
|
+
if (!configurationPath) {
|
|
9
|
+
throw new Error(`Could not find ${configurationFileName} from ${resolve(startingDirectoryPath)}.`);
|
|
10
|
+
}
|
|
11
|
+
const rootPath = dirname(configurationPath);
|
|
12
|
+
let parsedConfiguration;
|
|
13
|
+
try {
|
|
14
|
+
parsedConfiguration = parse(await readFile(configurationPath, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return {
|
|
18
|
+
kind: "malformed",
|
|
19
|
+
rootPath,
|
|
20
|
+
diagnostics: [
|
|
21
|
+
{
|
|
22
|
+
path: configurationFileName,
|
|
23
|
+
field: "configuration",
|
|
24
|
+
message: "Malformed YAML configuration.",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const diagnostics = [];
|
|
30
|
+
return {
|
|
31
|
+
kind: "loaded",
|
|
32
|
+
rootPath,
|
|
33
|
+
configuration: validateConfiguration(parsedConfiguration, diagnostics),
|
|
34
|
+
diagnostics,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export async function findConfigurationPath(startingDirectoryPath) {
|
|
38
|
+
let directoryPath = resolve(startingDirectoryPath);
|
|
39
|
+
let foundConfigurationPath;
|
|
40
|
+
while (true) {
|
|
41
|
+
const configurationPath = join(directoryPath, configurationFileName);
|
|
42
|
+
if (await pathExists(configurationPath)) {
|
|
43
|
+
foundConfigurationPath = configurationPath;
|
|
44
|
+
}
|
|
45
|
+
const parentPath = dirname(directoryPath);
|
|
46
|
+
if (parentPath === directoryPath)
|
|
47
|
+
return foundConfigurationPath;
|
|
48
|
+
directoryPath = parentPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function validateConfiguration(value, diagnostics) {
|
|
52
|
+
if (!isRecord(value)) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
path: configurationFileName,
|
|
55
|
+
field: "configuration",
|
|
56
|
+
message: "Expected a mapping.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const configuration = isRecord(value) ? value : {};
|
|
60
|
+
const allowedFields = new Set([
|
|
61
|
+
"require-namespace",
|
|
62
|
+
"kinds",
|
|
63
|
+
"tags",
|
|
64
|
+
"exclude",
|
|
65
|
+
]);
|
|
66
|
+
for (const field of Object.keys(configuration)) {
|
|
67
|
+
if (!allowedFields.has(field)) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
path: configurationFileName,
|
|
70
|
+
field,
|
|
71
|
+
message: "Unknown field.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (configuration["require-namespace"] !== undefined &&
|
|
76
|
+
typeof configuration["require-namespace"] !== "boolean") {
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
path: configurationFileName,
|
|
79
|
+
field: "require-namespace",
|
|
80
|
+
message: "Expected a boolean.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
requireNamespace: configuration["require-namespace"] === true,
|
|
85
|
+
kinds: validateDeclarations({
|
|
86
|
+
value: configuration.kinds,
|
|
87
|
+
field: "kinds",
|
|
88
|
+
diagnostics,
|
|
89
|
+
}),
|
|
90
|
+
tags: validateDeclarations({
|
|
91
|
+
value: configuration.tags,
|
|
92
|
+
field: "tags",
|
|
93
|
+
diagnostics,
|
|
94
|
+
}),
|
|
95
|
+
exclusions: validateExclusions(configuration.exclude, diagnostics),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function validateDeclarations({ value, field, diagnostics, }) {
|
|
99
|
+
if (!isRecord(value)) {
|
|
100
|
+
diagnostics.push({
|
|
101
|
+
path: configurationFileName,
|
|
102
|
+
field,
|
|
103
|
+
message: "Expected a mapping.",
|
|
104
|
+
});
|
|
105
|
+
return new Map();
|
|
106
|
+
}
|
|
107
|
+
const declarations = new Map();
|
|
108
|
+
for (const [identifier, description] of Object.entries(value)) {
|
|
109
|
+
const diagnosticField = `${field}.${identifier}`;
|
|
110
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identifier)) {
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
path: configurationFileName,
|
|
113
|
+
field: diagnosticField,
|
|
114
|
+
message: "Identifier must use lowercase kebab-case.",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (typeof description !== "string" || description.trim() === "") {
|
|
118
|
+
diagnostics.push({
|
|
119
|
+
path: configurationFileName,
|
|
120
|
+
field: diagnosticField,
|
|
121
|
+
message: "Description must be a non-empty string.",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
declarations.set(identifier, {
|
|
125
|
+
description: typeof description === "string" ? description : "",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return declarations;
|
|
129
|
+
}
|
|
130
|
+
function validateExclusions(value, diagnostics) {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return [];
|
|
133
|
+
if (!Array.isArray(value)) {
|
|
134
|
+
diagnostics.push({
|
|
135
|
+
path: configurationFileName,
|
|
136
|
+
field: "exclude",
|
|
137
|
+
message: "Expected a sequence of exclusion globs.",
|
|
138
|
+
});
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const exclusions = [];
|
|
142
|
+
for (const [index, pattern] of value.entries()) {
|
|
143
|
+
const field = `exclude.${index}`;
|
|
144
|
+
if (typeof pattern !== "string" || pattern.trim() === "") {
|
|
145
|
+
diagnostics.push({
|
|
146
|
+
path: configurationFileName,
|
|
147
|
+
field,
|
|
148
|
+
message: "Exclusion glob must be a non-empty string.",
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (pattern.startsWith("!")) {
|
|
153
|
+
diagnostics.push({
|
|
154
|
+
path: configurationFileName,
|
|
155
|
+
field,
|
|
156
|
+
message: "Negation is not supported in exclusion globs.",
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (/[![\]{}()]/.test(pattern)) {
|
|
161
|
+
diagnostics.push({
|
|
162
|
+
path: configurationFileName,
|
|
163
|
+
field,
|
|
164
|
+
message: "Only *, ?, and ** wildcard syntax is supported.",
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
exclusions.push(pattern);
|
|
169
|
+
}
|
|
170
|
+
return exclusions;
|
|
171
|
+
}
|
|
172
|
+
function isRecord(value) {
|
|
173
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
174
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function throwDiagnostics(diagnostics) {
|
|
2
|
+
throw new Error(diagnostics
|
|
3
|
+
.map((diagnostic) => `${diagnostic.path}: ${diagnostic.field}: ${diagnostic.message}`)
|
|
4
|
+
.join("\nerror: "));
|
|
5
|
+
}
|
|
6
|
+
export function compareDiagnostics(left, right) {
|
|
7
|
+
return (compareText(left.path, right.path) ||
|
|
8
|
+
compareText(left.field, right.field) ||
|
|
9
|
+
compareText(left.message, right.message));
|
|
10
|
+
}
|
|
11
|
+
function compareText(left, right) {
|
|
12
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ConfigurationDeclaration } from "../configuration/index.js";
|
|
2
|
+
type ClassifiedDocument = {
|
|
3
|
+
kind: string;
|
|
4
|
+
description: string;
|
|
5
|
+
tags: string[];
|
|
6
|
+
body: string;
|
|
7
|
+
};
|
|
8
|
+
type DocumentDiagnostic = {
|
|
9
|
+
path: string;
|
|
10
|
+
field: string;
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
type DocumentClassification = {
|
|
14
|
+
kind: "unregistered";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "invalid";
|
|
17
|
+
diagnostics: DocumentDiagnostic[];
|
|
18
|
+
} | {
|
|
19
|
+
kind: "registered";
|
|
20
|
+
document: ClassifiedDocument;
|
|
21
|
+
};
|
|
22
|
+
export declare function classifyDocument({ source, requireNamespace, declaredKinds, declaredTags, path, }: {
|
|
23
|
+
source: string;
|
|
24
|
+
requireNamespace: boolean;
|
|
25
|
+
declaredKinds: Map<string, ConfigurationDeclaration>;
|
|
26
|
+
declaredTags: Map<string, ConfigurationDeclaration>;
|
|
27
|
+
path: string;
|
|
28
|
+
}): DocumentClassification;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
export function classifyDocument({ source, requireNamespace, declaredKinds, declaredTags, path, }) {
|
|
3
|
+
const frontmatter = readFrontmatter(source);
|
|
4
|
+
if (frontmatter.kind === "malformed") {
|
|
5
|
+
return {
|
|
6
|
+
kind: "invalid",
|
|
7
|
+
diagnostics: [
|
|
8
|
+
{
|
|
9
|
+
path,
|
|
10
|
+
field: "frontmatter",
|
|
11
|
+
message: "Malformed YAML frontmatter.",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (frontmatter.kind === "none" || !isRecord(frontmatter.value)) {
|
|
17
|
+
return { kind: "unregistered" };
|
|
18
|
+
}
|
|
19
|
+
return validateRegistration({
|
|
20
|
+
frontmatter: frontmatter.value,
|
|
21
|
+
body: frontmatter.body,
|
|
22
|
+
requireNamespace,
|
|
23
|
+
declaredKinds,
|
|
24
|
+
declaredTags,
|
|
25
|
+
path,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function readFrontmatter(source) {
|
|
29
|
+
if (!source.startsWith("---\n") && !source.startsWith("---\r\n")) {
|
|
30
|
+
return { kind: "none" };
|
|
31
|
+
}
|
|
32
|
+
const match = /^---\r?\n([\s\S]*?)^---[ \t]*(?:\r?\n|$)/m.exec(source);
|
|
33
|
+
if (!match)
|
|
34
|
+
return { kind: "malformed" };
|
|
35
|
+
try {
|
|
36
|
+
return {
|
|
37
|
+
kind: "parsed",
|
|
38
|
+
value: parse(match[1]),
|
|
39
|
+
body: source.slice(match[0].length),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { kind: "malformed" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function validateRegistration({ frontmatter, body, requireNamespace, declaredKinds, declaredTags, path, }) {
|
|
47
|
+
const hasNamespace = Object.hasOwn(frontmatter, "waymark");
|
|
48
|
+
const hasRecognizedFlatMetadata = Object.hasOwn(frontmatter, "kind") &&
|
|
49
|
+
Object.hasOwn(frontmatter, "description");
|
|
50
|
+
if (requireNamespace && !hasNamespace)
|
|
51
|
+
return { kind: "unregistered" };
|
|
52
|
+
if (!requireNamespace && !hasNamespace && !hasRecognizedFlatMetadata) {
|
|
53
|
+
return { kind: "unregistered" };
|
|
54
|
+
}
|
|
55
|
+
const diagnostics = [];
|
|
56
|
+
if (hasNamespace && hasRecognizedFlatMetadata) {
|
|
57
|
+
diagnostics.push({
|
|
58
|
+
path,
|
|
59
|
+
field: "waymark",
|
|
60
|
+
message: "Flat and namespaced metadata cannot both be declared.",
|
|
61
|
+
});
|
|
62
|
+
return { kind: "invalid", diagnostics };
|
|
63
|
+
}
|
|
64
|
+
const namespaced = hasNamespace;
|
|
65
|
+
const value = namespaced ? frontmatter.waymark : frontmatter;
|
|
66
|
+
const fieldPrefix = namespaced ? "waymark." : "";
|
|
67
|
+
if (!isRecord(value)) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
path,
|
|
70
|
+
field: namespaced ? "waymark" : "frontmatter",
|
|
71
|
+
message: "Expected a mapping.",
|
|
72
|
+
});
|
|
73
|
+
return { kind: "invalid", diagnostics };
|
|
74
|
+
}
|
|
75
|
+
if (namespaced) {
|
|
76
|
+
const allowedFields = new Set(["kind", "description", "tags"]);
|
|
77
|
+
for (const field of Object.keys(value)) {
|
|
78
|
+
if (!allowedFields.has(field)) {
|
|
79
|
+
diagnostics.push({
|
|
80
|
+
path,
|
|
81
|
+
field: `${fieldPrefix}${field}`,
|
|
82
|
+
message: "Unknown field.",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const context = { path, declaredKinds, declaredTags, diagnostics };
|
|
88
|
+
const kind = validateDocumentKind({
|
|
89
|
+
value: value.kind,
|
|
90
|
+
field: `${fieldPrefix}kind`,
|
|
91
|
+
context,
|
|
92
|
+
});
|
|
93
|
+
const description = validateDocumentDescription({
|
|
94
|
+
value: value.description,
|
|
95
|
+
field: `${fieldPrefix}description`,
|
|
96
|
+
context,
|
|
97
|
+
});
|
|
98
|
+
const tags = validateDocumentTags({
|
|
99
|
+
value: value.tags,
|
|
100
|
+
field: `${fieldPrefix}tags`,
|
|
101
|
+
context,
|
|
102
|
+
});
|
|
103
|
+
if (diagnostics.length > 0 ||
|
|
104
|
+
kind === undefined ||
|
|
105
|
+
description === undefined ||
|
|
106
|
+
tags === undefined) {
|
|
107
|
+
return { kind: "invalid", diagnostics };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
kind: "registered",
|
|
111
|
+
document: { kind, description, tags, body },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function validateDocumentKind({ value, field, context, }) {
|
|
115
|
+
if (value === undefined) {
|
|
116
|
+
context.diagnostics.push({
|
|
117
|
+
path: context.path,
|
|
118
|
+
field,
|
|
119
|
+
message: "Document kind is required.",
|
|
120
|
+
});
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
124
|
+
context.diagnostics.push({
|
|
125
|
+
path: context.path,
|
|
126
|
+
field,
|
|
127
|
+
message: "Document kind must be a non-empty string.",
|
|
128
|
+
});
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
if (!context.declaredKinds.has(value)) {
|
|
132
|
+
context.diagnostics.push({
|
|
133
|
+
path: context.path,
|
|
134
|
+
field,
|
|
135
|
+
message: `Undeclared kind "${value}".`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
function validateDocumentDescription({ value, field, context, }) {
|
|
141
|
+
if (value === undefined) {
|
|
142
|
+
context.diagnostics.push({
|
|
143
|
+
path: context.path,
|
|
144
|
+
field,
|
|
145
|
+
message: "Document Description is required.",
|
|
146
|
+
});
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
150
|
+
context.diagnostics.push({
|
|
151
|
+
path: context.path,
|
|
152
|
+
field,
|
|
153
|
+
message: "Document Description must be a non-empty string.",
|
|
154
|
+
});
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
function validateDocumentTags({ value, field, context, }) {
|
|
160
|
+
if (value === undefined)
|
|
161
|
+
return [];
|
|
162
|
+
if (!Array.isArray(value)) {
|
|
163
|
+
context.diagnostics.push({
|
|
164
|
+
path: context.path,
|
|
165
|
+
field,
|
|
166
|
+
message: "Expected a YAML sequence of tags.",
|
|
167
|
+
});
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const tags = [];
|
|
171
|
+
const seenTags = new Set();
|
|
172
|
+
for (const tag of value) {
|
|
173
|
+
if (typeof tag !== "string" || tag.trim() === "") {
|
|
174
|
+
context.diagnostics.push({
|
|
175
|
+
path: context.path,
|
|
176
|
+
field,
|
|
177
|
+
message: "Every tag must be a non-empty string.",
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (seenTags.has(tag)) {
|
|
182
|
+
context.diagnostics.push({
|
|
183
|
+
path: context.path,
|
|
184
|
+
field,
|
|
185
|
+
message: `Duplicate tag "${tag}".`,
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
seenTags.add(tag);
|
|
190
|
+
tags.push(tag);
|
|
191
|
+
if (!context.declaredTags.has(tag)) {
|
|
192
|
+
context.diagnostics.push({
|
|
193
|
+
path: context.path,
|
|
194
|
+
field,
|
|
195
|
+
message: `Undeclared tag "${tag}".`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return tags;
|
|
200
|
+
}
|
|
201
|
+
function isRecord(value) {
|
|
202
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
203
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Configuration } from "../configuration/index.js";
|
|
2
|
+
import type { WaymarkDocument } from "./scan.js";
|
|
3
|
+
type FilterableDocument = {
|
|
4
|
+
kind: string;
|
|
5
|
+
tags: string[];
|
|
6
|
+
};
|
|
7
|
+
type DocumentCriteria = {
|
|
8
|
+
kinds: string[];
|
|
9
|
+
tags: string[];
|
|
10
|
+
requiredTags: string[];
|
|
11
|
+
filter?: string;
|
|
12
|
+
query?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function filterDocuments({ documents, configuration, criteria, }: {
|
|
15
|
+
documents: WaymarkDocument[];
|
|
16
|
+
configuration: Configuration;
|
|
17
|
+
criteria: DocumentCriteria;
|
|
18
|
+
}): WaymarkDocument[];
|
|
19
|
+
export declare function parseMetadataFilter({ expression, declaredKinds, declaredTags, }: {
|
|
20
|
+
expression: string;
|
|
21
|
+
declaredKinds: Set<string>;
|
|
22
|
+
declaredTags: Set<string>;
|
|
23
|
+
}): (document: FilterableDocument) => boolean;
|
|
24
|
+
export {};
|