waymark-docs 0.1.1 → 0.2.1
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 +14 -0
- package/README.md +38 -24
- package/dist/commands/find.js +49 -13
- package/dist/commands/ls.js +1 -4
- package/dist/commands/status.js +3 -8
- package/dist/configuration/load.d.ts +16 -4
- package/dist/configuration/load.js +96 -131
- package/dist/documents/classify.d.ts +10 -4
- package/dist/documents/classify.js +116 -137
- package/dist/documents/filter.d.ts +10 -4
- package/dist/documents/filter.js +10 -46
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Waymark are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.1](https://github.com/ysfaran/waymark/compare/v0.2.0...v0.2.1) (2026-08-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
|
|
10
|
+
* **readme:** explain where Waymark fits ([ffe25dc](https://github.com/ysfaran/waymark/commit/ffe25dc15f1651519ad34e9e67f7fee90c69ef62))
|
|
11
|
+
|
|
12
|
+
## [0.2.0](https://github.com/ysfaran/waymark/compare/v0.1.1...v0.2.0) (2026-08-03)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **cli:** change require-tags shorthand to -T ([#3](https://github.com/ysfaran/waymark/issues/3)) ([70a95f6](https://github.com/ysfaran/waymark/commit/70a95f6f876e4b48040d778d15bc9189c6020646))
|
|
18
|
+
|
|
5
19
|
## [0.1.1](https://github.com/ysfaran/waymark/compare/v0.1.0...v0.1.1) (2026-08-03)
|
|
6
20
|
|
|
7
21
|
|
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# 🪧 Waymark
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/waymark-docs)
|
|
4
4
|
|
|
5
|
-
Waymark is a small, offline-first CLI
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Waymark is a small, offline-first CLI that helps coding agents find the right
|
|
6
|
+
repository docs for each task. Add structured frontmatter to existing Markdown
|
|
7
|
+
or MDX files, and agents can discover only relevant paths before opening a
|
|
8
|
+
document—without reading a large index or following linked navigation files
|
|
9
|
+
that load unrelated context. It is as easy to set up as file-based navigation,
|
|
10
|
+
but remains deterministic and token-efficient; unlike RAG or MCP-backed
|
|
11
|
+
retrieval, it needs no ranking, maintained index, or retrieval infrastructure.
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
searching unrelated files and more time working with the context they need.
|
|
13
|
+

|
|
12
14
|
|
|
13
15
|
## Table of contents
|
|
14
16
|
|
|
@@ -35,23 +37,19 @@ npm install --save-dev waymark-docs
|
|
|
35
37
|
|
|
36
38
|
## Quick start
|
|
37
39
|
|
|
38
|
-
1. Create a Waymark configuration
|
|
40
|
+
1. **Create a Waymark configuration.**
|
|
41
|
+
|
|
42
|
+
Run `init` in the repository root:
|
|
39
43
|
|
|
40
44
|
```sh
|
|
41
45
|
npx waymark init
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
2. Define
|
|
45
|
-
|
|
46
|
-
```yaml
|
|
47
|
-
# When true, document metadata must be nested under a `waymark` frontmatter key.
|
|
48
|
-
require-namespace: false
|
|
48
|
+
2. **Define searchable metadata.**
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
ignore:
|
|
52
|
-
- docs/generated/**
|
|
53
|
-
- vendor/**
|
|
50
|
+
Add the document kinds and tags that agents can search:
|
|
54
51
|
|
|
52
|
+
```yaml
|
|
55
53
|
kinds:
|
|
56
54
|
adr: Read to understand past architectural decisions and their constraints
|
|
57
55
|
convention: Read before changing code to follow required repository practices
|
|
@@ -61,11 +59,10 @@ npm install --save-dev waymark-docs
|
|
|
61
59
|
typescript: TypeScript-related documentation
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
and `**` wildcards. Waymark also honors `.gitignore` automatically.
|
|
62
|
+
3. **Register a document.**
|
|
66
63
|
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
Add Waymark metadata to a Markdown or MDX file. For example, save this as
|
|
65
|
+
`docs/conventions/typescript.md`:
|
|
69
66
|
|
|
70
67
|
```yaml
|
|
71
68
|
---
|
|
@@ -76,7 +73,9 @@ npm install --save-dev waymark-docs
|
|
|
76
73
|
# TypeScript conventions
|
|
77
74
|
```
|
|
78
75
|
|
|
79
|
-
4. Validate the repository
|
|
76
|
+
4. **Validate the repository.**
|
|
77
|
+
|
|
78
|
+
Check the configuration and discovered documents:
|
|
80
79
|
|
|
81
80
|
```sh
|
|
82
81
|
npx waymark status
|
|
@@ -91,7 +90,9 @@ npm install --save-dev waymark-docs
|
|
|
91
90
|
Tags: 2
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
5. Discover the document
|
|
93
|
+
5. **Discover the document.**
|
|
94
|
+
|
|
95
|
+
Find the registered convention by kind and tag:
|
|
95
96
|
|
|
96
97
|
```sh
|
|
97
98
|
npx waymark find --kinds convention --tags typescript --show description
|
|
@@ -143,6 +144,19 @@ tags:
|
|
|
143
144
|
example-tag: Explain the topic represented by this tag
|
|
144
145
|
```
|
|
145
146
|
|
|
147
|
+
To skip generated or vendored documentation during discovery, add `ignore`
|
|
148
|
+
patterns:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
# Skip generated or vendored documentation during discovery.
|
|
152
|
+
ignore:
|
|
153
|
+
- docs/generated/**
|
|
154
|
+
- vendor/**
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Ignore patterns are relative to the repository root and support `*`, `?`, and
|
|
158
|
+
`**` wildcards. Waymark also honors `.gitignore` automatically.
|
|
159
|
+
|
|
146
160
|
`init` never overwrites an existing configuration and does not allow a nested
|
|
147
161
|
configuration beneath another Waymark root.
|
|
148
162
|
|
|
@@ -190,7 +204,7 @@ Usage: waymark find [options]
|
|
|
190
204
|
Options:
|
|
191
205
|
-k, --kinds <identifiers> Match any kind (comma-separated, repeatable)
|
|
192
206
|
-t, --tags <identifiers> Match any tag (comma-separated, repeatable)
|
|
193
|
-
-
|
|
207
|
+
-T, --require-tags <identifiers> Require every tag (comma-separated, repeatable)
|
|
194
208
|
-f, --filter <expression> Match a Boolean metadata filter
|
|
195
209
|
-q, --query <text> Match a literal content query
|
|
196
210
|
-s, --show <fields> Show kind, tags, and description
|
package/dist/commands/find.js
CHANGED
|
@@ -7,7 +7,7 @@ export function createFindCommand() {
|
|
|
7
7
|
.description("Discover Waymark Documents")
|
|
8
8
|
.option("-k, --kinds <identifiers>", "Match any Document Kind (comma-separated, repeatable)", collectOptionValue, [])
|
|
9
9
|
.option("-t, --tags <identifiers>", "Match any Document Tag (comma-separated, repeatable)", collectOptionValue, [])
|
|
10
|
-
.option("-
|
|
10
|
+
.option("-T, --require-tags <identifiers>", "Require every Document Tag (comma-separated, repeatable)", collectOptionValue, [])
|
|
11
11
|
.option("-f, --filter <expression>", "Match a Boolean Metadata Filter")
|
|
12
12
|
.option("-q, --query <text>", "Match a literal Content Query")
|
|
13
13
|
.option("-s, --show <fields>", "Show kind, tags, and description (comma-separated)")
|
|
@@ -25,27 +25,45 @@ export function createFindCommand() {
|
|
|
25
25
|
}
|
|
26
26
|
const shownFields = parseShownFields(options.show);
|
|
27
27
|
const loadedConfiguration = await loadConfiguration(process.cwd());
|
|
28
|
-
if (loadedConfiguration.kind === "
|
|
28
|
+
if (loadedConfiguration.kind === "invalid") {
|
|
29
29
|
throwDiagnostics(loadedConfiguration.diagnostics);
|
|
30
30
|
}
|
|
31
31
|
const { configuration, rootPath } = loadedConfiguration;
|
|
32
|
+
const metadataCriteria = options.filter === undefined
|
|
33
|
+
? {
|
|
34
|
+
method: "filter-groups",
|
|
35
|
+
kinds: parseIdentifierOptions({
|
|
36
|
+
optionName: "--kinds",
|
|
37
|
+
values: options.kinds,
|
|
38
|
+
declarations: configuration.kinds,
|
|
39
|
+
declarationName: "kind",
|
|
40
|
+
}),
|
|
41
|
+
tags: parseIdentifierOptions({
|
|
42
|
+
optionName: "--tags",
|
|
43
|
+
values: options.tags,
|
|
44
|
+
declarations: configuration.tags,
|
|
45
|
+
declarationName: "tag",
|
|
46
|
+
}),
|
|
47
|
+
requiredTags: parseIdentifierOptions({
|
|
48
|
+
optionName: "--require-tags",
|
|
49
|
+
values: options.requireTags,
|
|
50
|
+
declarations: configuration.tags,
|
|
51
|
+
declarationName: "tag",
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
: {
|
|
55
|
+
method: "filter-expression",
|
|
56
|
+
expression: options.filter,
|
|
57
|
+
};
|
|
32
58
|
const documentScan = await scanDocuments({ rootPath, configuration });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
...(documentScan.kind === "invalid" ? documentScan.diagnostics : []),
|
|
36
|
-
].sort(compareDiagnostics);
|
|
37
|
-
if (loadedConfiguration.diagnostics.length > 0 ||
|
|
38
|
-
documentScan.kind === "invalid") {
|
|
39
|
-
throwDiagnostics(diagnostics);
|
|
59
|
+
if (documentScan.kind === "invalid") {
|
|
60
|
+
throwDiagnostics(documentScan.diagnostics.sort(compareDiagnostics));
|
|
40
61
|
}
|
|
41
62
|
const matchingDocuments = filterDocuments({
|
|
42
63
|
documents: documentScan.documents,
|
|
43
64
|
configuration,
|
|
44
65
|
criteria: {
|
|
45
|
-
|
|
46
|
-
tags: options.tags,
|
|
47
|
-
requiredTags: options.requireTags,
|
|
48
|
-
filter: options.filter,
|
|
66
|
+
metadata: metadataCriteria,
|
|
49
67
|
query: options.query,
|
|
50
68
|
},
|
|
51
69
|
});
|
|
@@ -171,6 +189,24 @@ function createTreeDirectory() {
|
|
|
171
189
|
function collectOptionValue(value, previous) {
|
|
172
190
|
return [...previous, value];
|
|
173
191
|
}
|
|
192
|
+
function parseIdentifierOptions({ optionName, values, declarations, declarationName, }) {
|
|
193
|
+
const identifiers = new Set();
|
|
194
|
+
for (const value of values) {
|
|
195
|
+
for (const identifier of value.split(",")) {
|
|
196
|
+
if (identifier === "") {
|
|
197
|
+
throw new Error(`${optionName} contains an empty identifier.`);
|
|
198
|
+
}
|
|
199
|
+
if (identifiers.has(identifier)) {
|
|
200
|
+
throw new Error(`${optionName} contains duplicate identifier "${identifier}".`);
|
|
201
|
+
}
|
|
202
|
+
if (!declarations.has(identifier)) {
|
|
203
|
+
throw new Error(`${optionName} contains undeclared ${declarationName} "${identifier}".`);
|
|
204
|
+
}
|
|
205
|
+
identifiers.add(identifier);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return identifiers;
|
|
209
|
+
}
|
|
174
210
|
function compareText(left, right) {
|
|
175
211
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
176
212
|
}
|
package/dist/commands/ls.js
CHANGED
|
@@ -11,10 +11,7 @@ export function createLsCommand() {
|
|
|
11
11
|
.option("-u, --unregistered", "List only Unregistered Documents")
|
|
12
12
|
.action(async (directory, options) => {
|
|
13
13
|
const loadedConfiguration = await loadConfiguration(process.cwd());
|
|
14
|
-
if (loadedConfiguration.kind === "
|
|
15
|
-
throwDiagnostics(loadedConfiguration.diagnostics);
|
|
16
|
-
}
|
|
17
|
-
if (loadedConfiguration.diagnostics.length > 0) {
|
|
14
|
+
if (loadedConfiguration.kind === "invalid") {
|
|
18
15
|
throwDiagnostics(loadedConfiguration.diagnostics);
|
|
19
16
|
}
|
|
20
17
|
const rootPath = loadedConfiguration.rootPath;
|
package/dist/commands/status.js
CHANGED
|
@@ -9,20 +9,15 @@ export function createStatusCommand() {
|
|
|
9
9
|
.action(async (options) => {
|
|
10
10
|
const shownFields = parseShownFields(options.show);
|
|
11
11
|
const loadedConfiguration = await loadConfiguration(process.cwd());
|
|
12
|
-
if (loadedConfiguration.kind === "
|
|
12
|
+
if (loadedConfiguration.kind === "invalid") {
|
|
13
13
|
process.stdout.write(`Root: ${loadedConfiguration.rootPath}\n` + "Status: invalid\n");
|
|
14
14
|
throwDiagnostics(loadedConfiguration.diagnostics);
|
|
15
15
|
}
|
|
16
16
|
const { configuration, rootPath } = loadedConfiguration;
|
|
17
17
|
const documentScan = await scanDocuments({ rootPath, configuration });
|
|
18
|
-
|
|
19
|
-
...loadedConfiguration.diagnostics,
|
|
20
|
-
...(documentScan.kind === "invalid" ? documentScan.diagnostics : []),
|
|
21
|
-
].sort(compareDiagnostics);
|
|
22
|
-
if (loadedConfiguration.diagnostics.length > 0 ||
|
|
23
|
-
documentScan.kind === "invalid") {
|
|
18
|
+
if (documentScan.kind === "invalid") {
|
|
24
19
|
process.stdout.write(`Root: ${rootPath}\n` + "Status: invalid\n");
|
|
25
|
-
throwDiagnostics(diagnostics);
|
|
20
|
+
throwDiagnostics(documentScan.diagnostics.sort(compareDiagnostics));
|
|
26
21
|
}
|
|
27
22
|
let output = `Root: ${rootPath}\n` +
|
|
28
23
|
"Status: valid\n" +
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
export declare const defaultConfigFileName = "waymark.yml";
|
|
2
3
|
export declare const allowedConfigFileNames: readonly ["waymark.yml", "waymark.yaml"];
|
|
3
4
|
export type ConfigurationDeclaration = {
|
|
@@ -8,22 +9,33 @@ export type ConfigurationDiagnostic = {
|
|
|
8
9
|
field: string;
|
|
9
10
|
message: string;
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
+
declare const configurationSchema: z.ZodPipe<z.ZodObject<{
|
|
13
|
+
"require-namespace": z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
kinds: z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodTransform<Map<string, ConfigurationDeclaration>, Record<string, unknown>>>;
|
|
15
|
+
tags: z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodTransform<Map<string, ConfigurationDeclaration>, Record<string, unknown>>>;
|
|
16
|
+
ignore: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
17
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
12
18
|
requireNamespace: boolean;
|
|
13
19
|
kinds: Map<string, ConfigurationDeclaration>;
|
|
14
20
|
tags: Map<string, ConfigurationDeclaration>;
|
|
15
21
|
ignorePatterns: string[];
|
|
16
|
-
}
|
|
22
|
+
}, {
|
|
23
|
+
"require-namespace": boolean;
|
|
24
|
+
kinds: Map<string, ConfigurationDeclaration>;
|
|
25
|
+
tags: Map<string, ConfigurationDeclaration>;
|
|
26
|
+
ignore: string[];
|
|
27
|
+
}>>;
|
|
28
|
+
export type Configuration = z.infer<typeof configurationSchema>;
|
|
17
29
|
export type ConfigurationLoadResult = {
|
|
18
30
|
kind: "loaded";
|
|
19
31
|
rootPath: string;
|
|
20
32
|
configuration: Configuration;
|
|
21
|
-
diagnostics: ConfigurationDiagnostic[];
|
|
22
33
|
} | {
|
|
23
|
-
kind: "
|
|
34
|
+
kind: "invalid";
|
|
24
35
|
rootPath: string;
|
|
25
36
|
diagnostics: ConfigurationDiagnostic[];
|
|
26
37
|
};
|
|
27
38
|
export declare function loadConfiguration(startingDirectoryPath: string): Promise<ConfigurationLoadResult>;
|
|
28
39
|
export declare function findConfigurationPath(startingDirectoryPath: string): Promise<string | undefined>;
|
|
29
40
|
export declare function findConfigurationPathInDirectory(directoryPath: string): Promise<string | undefined>;
|
|
41
|
+
export {};
|
|
@@ -1,12 +1,80 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { parse } from "yaml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { compareDiagnostics } from "../diagnostics.js";
|
|
4
6
|
import { pathExists } from "../filesystem.js";
|
|
5
7
|
export const defaultConfigFileName = "waymark.yml";
|
|
6
8
|
export const allowedConfigFileNames = [
|
|
7
9
|
defaultConfigFileName,
|
|
8
10
|
"waymark.yaml",
|
|
9
11
|
];
|
|
12
|
+
const ignorePatternSchema = z
|
|
13
|
+
.string({ error: "Ignore glob must be a non-empty string." })
|
|
14
|
+
.superRefine((pattern, context) => {
|
|
15
|
+
if (pattern.trim() === "") {
|
|
16
|
+
context.addIssue({
|
|
17
|
+
code: "custom",
|
|
18
|
+
message: "Ignore glob must be a non-empty string.",
|
|
19
|
+
});
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (pattern.startsWith("!")) {
|
|
23
|
+
context.addIssue({
|
|
24
|
+
code: "custom",
|
|
25
|
+
message: "Negation is not supported in ignore globs.",
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (/[![\]{}()]/.test(pattern)) {
|
|
30
|
+
context.addIssue({
|
|
31
|
+
code: "custom",
|
|
32
|
+
message: "Only *, ?, and ** wildcard syntax is supported.",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const configurationDeclarationsSchema = z
|
|
37
|
+
.record(z.string(), z.unknown(), { error: "Expected a mapping." })
|
|
38
|
+
.superRefine((declarations, context) => {
|
|
39
|
+
for (const [identifier, description] of Object.entries(declarations)) {
|
|
40
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identifier)) {
|
|
41
|
+
context.addIssue({
|
|
42
|
+
code: "custom",
|
|
43
|
+
path: [identifier],
|
|
44
|
+
message: "Identifier must use lowercase kebab-case.",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (typeof description !== "string" || description.trim() === "") {
|
|
48
|
+
context.addIssue({
|
|
49
|
+
code: "custom",
|
|
50
|
+
path: [identifier],
|
|
51
|
+
message: "Description must be a non-empty string.",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.transform((declarations) => new Map(Object.entries(declarations)
|
|
57
|
+
.filter((entry) => typeof entry[1] === "string")
|
|
58
|
+
.map(([identifier, description]) => [identifier, { description }])));
|
|
59
|
+
const configurationSchema = z
|
|
60
|
+
.strictObject({
|
|
61
|
+
"require-namespace": z
|
|
62
|
+
.boolean({ error: "Expected a boolean." })
|
|
63
|
+
.default(false),
|
|
64
|
+
kinds: configurationDeclarationsSchema,
|
|
65
|
+
tags: configurationDeclarationsSchema,
|
|
66
|
+
ignore: z
|
|
67
|
+
.array(ignorePatternSchema, {
|
|
68
|
+
error: "Expected a sequence of ignore globs.",
|
|
69
|
+
})
|
|
70
|
+
.default([]),
|
|
71
|
+
}, { error: "Expected a mapping." })
|
|
72
|
+
.transform((configuration) => ({
|
|
73
|
+
requireNamespace: configuration["require-namespace"],
|
|
74
|
+
kinds: configuration.kinds,
|
|
75
|
+
tags: configuration.tags,
|
|
76
|
+
ignorePatterns: configuration.ignore,
|
|
77
|
+
}));
|
|
10
78
|
export async function loadConfiguration(startingDirectoryPath) {
|
|
11
79
|
const configurationPath = await findConfigurationPath(startingDirectoryPath);
|
|
12
80
|
if (!configurationPath) {
|
|
@@ -20,7 +88,7 @@ export async function loadConfiguration(startingDirectoryPath) {
|
|
|
20
88
|
}
|
|
21
89
|
catch {
|
|
22
90
|
return {
|
|
23
|
-
kind: "
|
|
91
|
+
kind: "invalid",
|
|
24
92
|
rootPath,
|
|
25
93
|
diagnostics: [
|
|
26
94
|
{
|
|
@@ -31,16 +99,21 @@ export async function loadConfiguration(startingDirectoryPath) {
|
|
|
31
99
|
],
|
|
32
100
|
};
|
|
33
101
|
}
|
|
34
|
-
const
|
|
102
|
+
const configurationResult = configurationSchema.safeParse(parsedConfiguration);
|
|
103
|
+
if (!configurationResult.success) {
|
|
104
|
+
return {
|
|
105
|
+
kind: "invalid",
|
|
106
|
+
rootPath,
|
|
107
|
+
diagnostics: createConfigurationDiagnostics({
|
|
108
|
+
error: configurationResult.error,
|
|
109
|
+
configurationFileName: loadedConfigurationFileName,
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
35
113
|
return {
|
|
36
114
|
kind: "loaded",
|
|
37
115
|
rootPath,
|
|
38
|
-
configuration:
|
|
39
|
-
value: parsedConfiguration,
|
|
40
|
-
configurationFileName: loadedConfigurationFileName,
|
|
41
|
-
diagnostics,
|
|
42
|
-
}),
|
|
43
|
-
diagnostics,
|
|
116
|
+
configuration: configurationResult.data,
|
|
44
117
|
};
|
|
45
118
|
}
|
|
46
119
|
export async function findConfigurationPath(startingDirectoryPath) {
|
|
@@ -70,133 +143,25 @@ export async function findConfigurationPathInDirectory(directoryPath) {
|
|
|
70
143
|
}
|
|
71
144
|
return configurationPaths[0];
|
|
72
145
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"ignore",
|
|
87
|
-
]);
|
|
88
|
-
for (const field of Object.keys(configuration)) {
|
|
89
|
-
if (!allowedFields.has(field)) {
|
|
90
|
-
diagnostics.push({
|
|
91
|
-
path: configurationFileName,
|
|
92
|
-
field,
|
|
93
|
-
message: "Unknown field.",
|
|
94
|
-
});
|
|
146
|
+
function createConfigurationDiagnostics({ error, configurationFileName, }) {
|
|
147
|
+
const diagnostics = [];
|
|
148
|
+
for (const issue of error.issues) {
|
|
149
|
+
const field = issue.path.join(".") || "configuration";
|
|
150
|
+
if (issue.code === "unrecognized_keys") {
|
|
151
|
+
for (const key of issue.keys) {
|
|
152
|
+
diagnostics.push({
|
|
153
|
+
path: configurationFileName,
|
|
154
|
+
field: [...issue.path, key].join("."),
|
|
155
|
+
message: "Unknown field.",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
95
159
|
}
|
|
96
|
-
}
|
|
97
|
-
if (configuration["require-namespace"] !== undefined &&
|
|
98
|
-
typeof configuration["require-namespace"] !== "boolean") {
|
|
99
|
-
diagnostics.push({
|
|
100
|
-
path: configurationFileName,
|
|
101
|
-
field: "require-namespace",
|
|
102
|
-
message: "Expected a boolean.",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
requireNamespace: configuration["require-namespace"] === true,
|
|
107
|
-
kinds: validateDeclarations({
|
|
108
|
-
value: configuration.kinds,
|
|
109
|
-
field: "kinds",
|
|
110
|
-
configurationFileName,
|
|
111
|
-
diagnostics,
|
|
112
|
-
}),
|
|
113
|
-
tags: validateDeclarations({
|
|
114
|
-
value: configuration.tags,
|
|
115
|
-
field: "tags",
|
|
116
|
-
configurationFileName,
|
|
117
|
-
diagnostics,
|
|
118
|
-
}),
|
|
119
|
-
ignorePatterns: validateIgnorePatterns({
|
|
120
|
-
value: configuration.ignore,
|
|
121
|
-
configurationFileName,
|
|
122
|
-
diagnostics,
|
|
123
|
-
}),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function validateDeclarations({ value, field, configurationFileName, diagnostics, }) {
|
|
127
|
-
if (!isRecord(value)) {
|
|
128
160
|
diagnostics.push({
|
|
129
161
|
path: configurationFileName,
|
|
130
162
|
field,
|
|
131
|
-
message:
|
|
132
|
-
});
|
|
133
|
-
return new Map();
|
|
134
|
-
}
|
|
135
|
-
const declarations = new Map();
|
|
136
|
-
for (const [identifier, description] of Object.entries(value)) {
|
|
137
|
-
const diagnosticField = `${field}.${identifier}`;
|
|
138
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identifier)) {
|
|
139
|
-
diagnostics.push({
|
|
140
|
-
path: configurationFileName,
|
|
141
|
-
field: diagnosticField,
|
|
142
|
-
message: "Identifier must use lowercase kebab-case.",
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
if (typeof description !== "string" || description.trim() === "") {
|
|
146
|
-
diagnostics.push({
|
|
147
|
-
path: configurationFileName,
|
|
148
|
-
field: diagnosticField,
|
|
149
|
-
message: "Description must be a non-empty string.",
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
declarations.set(identifier, {
|
|
153
|
-
description: typeof description === "string" ? description : "",
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
return declarations;
|
|
157
|
-
}
|
|
158
|
-
function validateIgnorePatterns({ value, configurationFileName, diagnostics, }) {
|
|
159
|
-
if (value === undefined)
|
|
160
|
-
return [];
|
|
161
|
-
if (!Array.isArray(value)) {
|
|
162
|
-
diagnostics.push({
|
|
163
|
-
path: configurationFileName,
|
|
164
|
-
field: "ignore",
|
|
165
|
-
message: "Expected a sequence of ignore globs.",
|
|
163
|
+
message: issue.message,
|
|
166
164
|
});
|
|
167
|
-
return [];
|
|
168
165
|
}
|
|
169
|
-
|
|
170
|
-
for (const [index, pattern] of value.entries()) {
|
|
171
|
-
const field = `ignore.${index}`;
|
|
172
|
-
if (typeof pattern !== "string" || pattern.trim() === "") {
|
|
173
|
-
diagnostics.push({
|
|
174
|
-
path: configurationFileName,
|
|
175
|
-
field,
|
|
176
|
-
message: "Ignore glob must be a non-empty string.",
|
|
177
|
-
});
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (pattern.startsWith("!")) {
|
|
181
|
-
diagnostics.push({
|
|
182
|
-
path: configurationFileName,
|
|
183
|
-
field,
|
|
184
|
-
message: "Negation is not supported in ignore globs.",
|
|
185
|
-
});
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (/[![\]{}()]/.test(pattern)) {
|
|
189
|
-
diagnostics.push({
|
|
190
|
-
path: configurationFileName,
|
|
191
|
-
field,
|
|
192
|
-
message: "Only *, ?, and ** wildcard syntax is supported.",
|
|
193
|
-
});
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
ignorePatterns.push(pattern);
|
|
197
|
-
}
|
|
198
|
-
return ignorePatterns;
|
|
199
|
-
}
|
|
200
|
-
function isRecord(value) {
|
|
201
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
166
|
+
return diagnostics.sort(compareDiagnostics);
|
|
202
167
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import type { ConfigurationDeclaration } from "../configuration/index.js";
|
|
2
|
-
type ClassifiedDocument = {
|
|
3
|
-
kind: string;
|
|
4
|
-
description: string;
|
|
5
|
-
tags: string[];
|
|
3
|
+
type ClassifiedDocument = z.infer<ReturnType<typeof createDocumentSchema>> & {
|
|
6
4
|
body: string;
|
|
7
5
|
};
|
|
8
6
|
type DocumentDiagnostic = {
|
|
@@ -26,4 +24,12 @@ export declare function classifyDocument({ source, requireNamespace, declaredKin
|
|
|
26
24
|
declaredTags: Map<string, ConfigurationDeclaration>;
|
|
27
25
|
path: string;
|
|
28
26
|
}): DocumentClassification;
|
|
27
|
+
declare function createDocumentSchema({ declaredKinds, declaredTags, }: {
|
|
28
|
+
declaredKinds: Map<string, ConfigurationDeclaration>;
|
|
29
|
+
declaredTags: Map<string, ConfigurationDeclaration>;
|
|
30
|
+
}): z.ZodObject<{
|
|
31
|
+
kind: z.ZodString;
|
|
32
|
+
description: z.ZodString;
|
|
33
|
+
tags: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodUnknown>>, z.ZodTransform<string[], unknown[]>>;
|
|
34
|
+
}, z.core.$strict>;
|
|
29
35
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { parse } from "yaml";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const rawFrontmatterSchema = z.record(z.string(), z.unknown());
|
|
2
4
|
export function classifyDocument({ source, requireNamespace, declaredKinds, declaredTags, path, }) {
|
|
3
5
|
const frontmatter = readFrontmatter(source);
|
|
4
6
|
if (frontmatter.kind === "malformed") {
|
|
@@ -13,11 +15,14 @@ export function classifyDocument({ source, requireNamespace, declaredKinds, decl
|
|
|
13
15
|
],
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
|
-
if (frontmatter.kind === "none"
|
|
18
|
+
if (frontmatter.kind === "none") {
|
|
17
19
|
return { kind: "unregistered" };
|
|
18
20
|
}
|
|
21
|
+
const frontmatterResult = rawFrontmatterSchema.safeParse(frontmatter.value);
|
|
22
|
+
if (!frontmatterResult.success)
|
|
23
|
+
return { kind: "unregistered" };
|
|
19
24
|
return validateRegistration({
|
|
20
|
-
frontmatter:
|
|
25
|
+
frontmatter: frontmatterResult.data,
|
|
21
26
|
body: frontmatter.body,
|
|
22
27
|
requireNamespace,
|
|
23
28
|
declaredKinds,
|
|
@@ -52,152 +57,126 @@ function validateRegistration({ frontmatter, body, requireNamespace, declaredKin
|
|
|
52
57
|
if (!requireNamespace && !hasNamespace && !hasRecognizedFlatMetadata) {
|
|
53
58
|
return { kind: "unregistered" };
|
|
54
59
|
}
|
|
55
|
-
const diagnostics = [];
|
|
56
60
|
if (hasNamespace && hasRecognizedFlatMetadata) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
return {
|
|
62
|
+
kind: "invalid",
|
|
63
|
+
diagnostics: [
|
|
64
|
+
{
|
|
65
|
+
path,
|
|
66
|
+
field: "waymark",
|
|
67
|
+
message: "Flat and namespaced metadata cannot both be declared.",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
63
71
|
}
|
|
64
72
|
const namespaced = hasNamespace;
|
|
65
|
-
const value = namespaced
|
|
73
|
+
const value = namespaced
|
|
74
|
+
? frontmatter.waymark
|
|
75
|
+
: {
|
|
76
|
+
kind: frontmatter.kind,
|
|
77
|
+
description: frontmatter.description,
|
|
78
|
+
tags: frontmatter.tags,
|
|
79
|
+
};
|
|
66
80
|
const fieldPrefix = namespaced ? "waymark." : "";
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 };
|
|
81
|
+
const metadataResult = createDocumentSchema({
|
|
82
|
+
declaredKinds,
|
|
83
|
+
declaredTags,
|
|
84
|
+
}).safeParse(value);
|
|
85
|
+
if (!metadataResult.success) {
|
|
86
|
+
return {
|
|
87
|
+
kind: "invalid",
|
|
88
|
+
diagnostics: createDocumentDiagnostics({
|
|
89
|
+
error: metadataResult.error,
|
|
90
|
+
path,
|
|
91
|
+
fieldPrefix,
|
|
92
|
+
rootField: namespaced ? "waymark" : "frontmatter",
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
108
95
|
}
|
|
109
96
|
return {
|
|
110
97
|
kind: "registered",
|
|
111
|
-
document: {
|
|
98
|
+
document: { ...metadataResult.data, body },
|
|
112
99
|
};
|
|
113
100
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
101
|
+
function createDocumentSchema({ declaredKinds, declaredTags, }) {
|
|
102
|
+
return z.strictObject({
|
|
103
|
+
kind: z
|
|
104
|
+
.string({
|
|
105
|
+
error: (issue) => issue.input === undefined
|
|
106
|
+
? "Document kind is required."
|
|
107
|
+
: "Document kind must be a non-empty string.",
|
|
108
|
+
})
|
|
109
|
+
.refine((kind) => kind.trim() !== "", {
|
|
110
|
+
error: "Document kind must be a non-empty string.",
|
|
111
|
+
})
|
|
112
|
+
.superRefine((kind, context) => {
|
|
113
|
+
if (kind.trim() !== "" && !declaredKinds.has(kind)) {
|
|
114
|
+
context.addIssue({
|
|
115
|
+
code: "custom",
|
|
116
|
+
message: `Undeclared kind "${kind}".`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
description: z
|
|
121
|
+
.string({
|
|
122
|
+
error: (issue) => issue.input === undefined
|
|
123
|
+
? "Document Description is required."
|
|
124
|
+
: "Document Description must be a non-empty string.",
|
|
125
|
+
})
|
|
126
|
+
.refine((description) => description.trim() !== "", {
|
|
127
|
+
error: "Document Description must be a non-empty string.",
|
|
128
|
+
}),
|
|
129
|
+
tags: z
|
|
130
|
+
.array(z.unknown(), { error: "Expected a YAML sequence of tags." })
|
|
131
|
+
.default([])
|
|
132
|
+
.superRefine((tags, context) => {
|
|
133
|
+
const seenTags = new Set();
|
|
134
|
+
for (const tag of tags) {
|
|
135
|
+
if (typeof tag !== "string" || tag.trim() === "") {
|
|
136
|
+
context.addIssue({
|
|
137
|
+
code: "custom",
|
|
138
|
+
message: "Every tag must be a non-empty string.",
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (seenTags.has(tag)) {
|
|
143
|
+
context.addIssue({
|
|
144
|
+
code: "custom",
|
|
145
|
+
message: `Duplicate tag "${tag}".`,
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
seenTags.add(tag);
|
|
150
|
+
if (!declaredTags.has(tag)) {
|
|
151
|
+
context.addIssue({
|
|
152
|
+
code: "custom",
|
|
153
|
+
message: `Undeclared tag "${tag}".`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.transform((tags) => tags.filter((tag) => typeof tag === "string")),
|
|
159
|
+
}, { error: "Expected a mapping." });
|
|
158
160
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
});
|
|
161
|
+
function createDocumentDiagnostics({ error, path, fieldPrefix, rootField, }) {
|
|
162
|
+
const diagnostics = [];
|
|
163
|
+
for (const issue of error.issues) {
|
|
164
|
+
if (issue.code === "unrecognized_keys") {
|
|
165
|
+
for (const key of issue.keys) {
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
path,
|
|
168
|
+
field: `${fieldPrefix}${key}`,
|
|
169
|
+
message: "Unknown field.",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
187
172
|
continue;
|
|
188
173
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
message: `Undeclared tag "${tag}".`,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
174
|
+
const field = issue.path.find((segment) => typeof segment === "string");
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
path,
|
|
177
|
+
field: field === undefined ? rootField : `${fieldPrefix}${field}`,
|
|
178
|
+
message: issue.message,
|
|
179
|
+
});
|
|
198
180
|
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
function isRecord(value) {
|
|
202
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
181
|
+
return diagnostics;
|
|
203
182
|
}
|
|
@@ -4,11 +4,17 @@ type FilterableDocument = {
|
|
|
4
4
|
kind: string;
|
|
5
5
|
tags: string[];
|
|
6
6
|
};
|
|
7
|
+
type MetadataCriteria = {
|
|
8
|
+
method: "filter-groups";
|
|
9
|
+
kinds: Set<string>;
|
|
10
|
+
tags: Set<string>;
|
|
11
|
+
requiredTags: Set<string>;
|
|
12
|
+
} | {
|
|
13
|
+
method: "filter-expression";
|
|
14
|
+
expression: string;
|
|
15
|
+
};
|
|
7
16
|
type DocumentCriteria = {
|
|
8
|
-
|
|
9
|
-
tags: string[];
|
|
10
|
-
requiredTags: string[];
|
|
11
|
-
filter?: string;
|
|
17
|
+
metadata: MetadataCriteria;
|
|
12
18
|
query?: string;
|
|
13
19
|
};
|
|
14
20
|
export declare function filterDocuments({ documents, configuration, criteria, }: {
|
package/dist/documents/filter.js
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
export function filterDocuments({ documents, configuration, criteria, }) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
declarationName: "kind",
|
|
7
|
-
});
|
|
8
|
-
const tags = parseIdentifierOptions({
|
|
9
|
-
optionName: "--tags",
|
|
10
|
-
values: criteria.tags,
|
|
11
|
-
declarations: configuration.tags,
|
|
12
|
-
declarationName: "tag",
|
|
13
|
-
});
|
|
14
|
-
const requiredTags = parseIdentifierOptions({
|
|
15
|
-
optionName: "--require-tags",
|
|
16
|
-
values: criteria.requiredTags,
|
|
17
|
-
declarations: configuration.tags,
|
|
18
|
-
declarationName: "tag",
|
|
19
|
-
});
|
|
20
|
-
const matchesMetadataFilter = criteria.filter === undefined
|
|
21
|
-
? undefined
|
|
22
|
-
: parseMetadataFilter({
|
|
23
|
-
expression: criteria.filter,
|
|
2
|
+
const metadata = criteria.metadata;
|
|
3
|
+
const matchesMetadata = metadata.method === "filter-expression"
|
|
4
|
+
? parseMetadataFilter({
|
|
5
|
+
expression: metadata.expression,
|
|
24
6
|
declaredKinds: new Set(configuration.kinds.keys()),
|
|
25
7
|
declaredTags: new Set(configuration.tags.keys()),
|
|
26
|
-
})
|
|
8
|
+
})
|
|
9
|
+
: (document) => (metadata.kinds.size === 0 || metadata.kinds.has(document.kind)) &&
|
|
10
|
+
(metadata.tags.size === 0 ||
|
|
11
|
+
document.tags.some((tag) => metadata.tags.has(tag))) &&
|
|
12
|
+
[...metadata.requiredTags].every((tag) => document.tags.includes(tag));
|
|
27
13
|
const normalizedQuery = criteria.query?.toLowerCase();
|
|
28
14
|
return documents
|
|
29
|
-
.filter((document) => (
|
|
30
|
-
(tags.size === 0 || document.tags.some((tag) => tags.has(tag))) &&
|
|
31
|
-
[...requiredTags].every((tag) => document.tags.includes(tag)) &&
|
|
32
|
-
(matchesMetadataFilter === undefined ||
|
|
33
|
-
matchesMetadataFilter(document)) &&
|
|
15
|
+
.filter((document) => matchesMetadata(document) &&
|
|
34
16
|
(normalizedQuery === undefined ||
|
|
35
17
|
document.body.toLowerCase().includes(normalizedQuery)))
|
|
36
18
|
.sort((left, right) => compareText(left.path, right.path));
|
|
@@ -189,24 +171,6 @@ function describeToken(token) {
|
|
|
189
171
|
return '"("';
|
|
190
172
|
return `"${token.type.toUpperCase()}"`;
|
|
191
173
|
}
|
|
192
|
-
function parseIdentifierOptions({ optionName, values, declarations, declarationName, }) {
|
|
193
|
-
const identifiers = new Set();
|
|
194
|
-
for (const value of values) {
|
|
195
|
-
for (const identifier of value.split(",")) {
|
|
196
|
-
if (identifier === "") {
|
|
197
|
-
throw new Error(`${optionName} contains an empty identifier.`);
|
|
198
|
-
}
|
|
199
|
-
if (identifiers.has(identifier)) {
|
|
200
|
-
throw new Error(`${optionName} contains duplicate identifier "${identifier}".`);
|
|
201
|
-
}
|
|
202
|
-
if (!declarations.has(identifier)) {
|
|
203
|
-
throw new Error(`${optionName} contains undeclared ${declarationName} "${identifier}".`);
|
|
204
|
-
}
|
|
205
|
-
identifiers.add(identifier);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return identifiers;
|
|
209
|
-
}
|
|
210
174
|
function compareText(left, right) {
|
|
211
175
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
212
176
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waymark-docs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Agent-focused CLI that surfaces repository documentation to improve coding-agent performance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"commander": "14.0.3",
|
|
47
47
|
"globby": "16.2.2",
|
|
48
|
-
"yaml": "2.9.0"
|
|
48
|
+
"yaml": "2.9.0",
|
|
49
|
+
"zod": "4.4.3"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "24.10.10",
|