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 ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ All notable changes to Waymark are documented in this file.
4
+
5
+ ## 0.1.0 (2026-07-31)
6
+
7
+ - Initial public release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Aran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # Waymark 🪧
2
+
3
+ Find the right repository docs for your coding agent.
4
+
5
+ Waymark is a small, offline-first CLI built for AI agents. Add structured
6
+ frontmatter to Markdown or MDX files, then let the agent select the kinds and
7
+ tags relevant to its task. Waymark returns deterministic matches without
8
+ ranking results or maintaining an index.
9
+
10
+ This makes documentation discovery token-efficient: agents spend less time
11
+ searching unrelated files and more time working with the context they need.
12
+
13
+ ## Table of contents
14
+
15
+ - [Installation](#installation)
16
+ - [Quick start](#quick-start)
17
+ - [Commands](#commands)
18
+ - [`waymark init`](#waymark-init)
19
+ - [`waymark status`](#waymark-status)
20
+ - [`waymark find`](#waymark-find)
21
+ - [`waymark ls`](#waymark-ls)
22
+ - [`waymark help`](#waymark-help)
23
+ - [Contributing](#contributing)
24
+
25
+ ## Installation
26
+
27
+ Install the `waymark-docs` package as a development dependency:
28
+
29
+ ```sh
30
+ pnpm add -D waymark-docs
31
+ yarn add -D waymark-docs
32
+ bun add -d waymark-docs
33
+ npm install --save-dev waymark-docs
34
+ ```
35
+
36
+ ## Quick start
37
+
38
+ 1. Create `waymark.yaml` in the repository root:
39
+
40
+ ```sh
41
+ npx waymark init
42
+ ```
43
+
44
+ 2. Define the document kinds and tags that agents can search:
45
+
46
+ ```yaml
47
+ # When true, document metadata must be nested under a `waymark` frontmatter key.
48
+ require-namespace: false
49
+
50
+ kinds:
51
+ adr: Read to understand past architectural decisions and their constraints
52
+ convention: Read before changing code to follow required repository practices
53
+
54
+ tags:
55
+ architecture: System boundaries, component relationships, and dependencies
56
+ typescript: TypeScript-related documentation
57
+ ```
58
+
59
+ 3. Register a Markdown or MDX file by adding Waymark metadata. For example,
60
+ save this as `docs/conventions/typescript.md`:
61
+
62
+ ```yaml
63
+ ---
64
+ kind: convention
65
+ description: TypeScript conventions for this repository
66
+ tags: [typescript]
67
+ ---
68
+ # TypeScript conventions
69
+ ```
70
+
71
+ 4. Validate the repository:
72
+
73
+ ```sh
74
+ npx waymark status
75
+ ```
76
+
77
+ ```text
78
+ Root: /path/to/repository
79
+ Status: valid
80
+ Waymark Documents: 1
81
+ Unregistered Documents: 1
82
+ Kinds: 2
83
+ Tags: 2
84
+ ```
85
+
86
+ 5. Discover the document:
87
+
88
+ ```sh
89
+ npx waymark find --kinds convention --tags typescript --show description
90
+ ```
91
+
92
+ ```text
93
+ docs/conventions/typescript.md — TypeScript conventions for this repository
94
+ ```
95
+
96
+ Waymark looks for `waymark.yaml` in the current directory and its ancestors, so
97
+ commands can also be run from a nested repository directory.
98
+
99
+ ## Commands
100
+
101
+ | Command | Purpose |
102
+ | ---------------- | --------------------------------------------------------- |
103
+ | `waymark init` | Create a starter configuration |
104
+ | `waymark status` | Validate and summarize the repository |
105
+ | `waymark find` | Find registered documents across the repository |
106
+ | `waymark ls` | Audit registered or unregistered documents in a directory |
107
+ | `waymark help` | Show CLI or command-specific help |
108
+
109
+ ### `waymark init`
110
+
111
+ Create a starter `waymark.yaml` in the current directory.
112
+
113
+ ```text
114
+ Usage: waymark init [options]
115
+
116
+ Options:
117
+ -h, --help Display help for the command
118
+ ```
119
+
120
+ ```sh
121
+ npx waymark init
122
+ ```
123
+
124
+ The generated file explains metadata namespacing and includes declarations to
125
+ replace with your own kind and tag:
126
+
127
+ ```yaml
128
+ # When true, document metadata must be nested under a `waymark` frontmatter key.
129
+ require-namespace: false
130
+ kinds:
131
+ example-kind: Explain when agents should read this kind of document
132
+ tags:
133
+ example-tag: Explain the topic represented by this tag
134
+ ```
135
+
136
+ `init` never overwrites an existing configuration and does not allow a nested
137
+ configuration beneath another Waymark root.
138
+
139
+ ### `waymark status`
140
+
141
+ Validate `waymark.yaml` and all discovered Waymark Documents, then print the
142
+ repository root and counts for registered documents, unregistered documents,
143
+ kinds, and tags. Invalid repositories produce diagnostics and a non-zero exit
144
+ code, which makes this command suitable for CI.
145
+
146
+ ```text
147
+ Usage: waymark status [options]
148
+
149
+ Options:
150
+ -s, --show <fields> Show declared kind and tag details (kind,tags)
151
+ -h, --help Display help for the command
152
+ ```
153
+
154
+ Validate the repository:
155
+
156
+ ```sh
157
+ npx waymark status
158
+ ```
159
+
160
+ List every declared kind and tag with its description and usage count:
161
+
162
+ ```sh
163
+ npx waymark status --show kind,tags
164
+ ```
165
+
166
+ Show only kind details:
167
+
168
+ ```sh
169
+ npx waymark status --show kind
170
+ ```
171
+
172
+ ### `waymark find`
173
+
174
+ Find registered Waymark Documents across the repository. With no filters,
175
+ `find` returns every registered document in deterministic path order.
176
+
177
+ ```text
178
+ Usage: waymark find [options]
179
+
180
+ Options:
181
+ -k, --kinds <identifiers> Match any kind (comma-separated, repeatable)
182
+ -t, --tags <identifiers> Match any tag (comma-separated, repeatable)
183
+ -r, --require-tags <identifiers> Require every tag (comma-separated, repeatable)
184
+ -f, --filter <expression> Match a Boolean metadata filter
185
+ -q, --query <text> Match a literal content query
186
+ -s, --show <fields> Show kind, tags, and description
187
+ --json Return a flat JSON array
188
+ --tree Return a directory tree
189
+ -h, --help Display help for the command
190
+ ```
191
+
192
+ Simple filter values use OR within an option. Different options combine with
193
+ AND:
194
+
195
+ ```sh
196
+ # Kind is adr OR convention, and at least one tag is typescript OR architecture
197
+ npx waymark find --kinds adr,convention --tags typescript,architecture
198
+
199
+ # Kind is adr, and both architecture AND typescript tags are required
200
+ npx waymark find --kinds adr --require-tags architecture,typescript
201
+ ```
202
+
203
+ The three simple metadata filters are repeatable. Repeating an option is
204
+ equivalent to passing a comma-separated list:
205
+
206
+ ```sh
207
+ npx waymark find --kinds adr --kinds convention
208
+ ```
209
+
210
+ Use `--query` for a case-insensitive literal search of document bodies. It can
211
+ be combined with either simple or Boolean metadata filters:
212
+
213
+ ```sh
214
+ npx waymark find --kinds convention --query "dependency injection"
215
+ ```
216
+
217
+ Use `--filter` for advanced metadata expressions with `kind:`, `tag:`, `NOT`,
218
+ `AND`, `OR`, and parentheses:
219
+
220
+ ```sh
221
+ npx waymark find --filter '(kind:adr OR kind:convention) AND tag:typescript AND NOT tag:architecture'
222
+ ```
223
+
224
+ `--filter` cannot be combined with `--kinds`, `--tags`, or `--require-tags`.
225
+
226
+ Add metadata fields to the default line-oriented output with `--show`:
227
+
228
+ ```sh
229
+ npx waymark find --kinds convention --show kind,tags,description
230
+ ```
231
+
232
+ Return structured output for scripts and agents:
233
+
234
+ ```sh
235
+ npx waymark find --tags typescript --show kind,description --json
236
+ ```
237
+
238
+ Or visualize matching documents by directory:
239
+
240
+ ```sh
241
+ npx waymark find --kinds adr,convention --show kind --tree
242
+ ```
243
+
244
+ `--json` and `--tree` are mutually exclusive. A search with no matches succeeds
245
+ and returns an empty result.
246
+
247
+ ### `waymark ls`
248
+
249
+ Inventory Markdown and MDX registration within a directory. By default, `ls`
250
+ inspects only the current directory and lists registered Waymark Documents.
251
+ Paths are returned relative to the repository root in deterministic order.
252
+
253
+ ```text
254
+ Usage: waymark ls [options] [directory]
255
+
256
+ Arguments:
257
+ directory Directory to inspect (defaults to the current directory)
258
+
259
+ Options:
260
+ -R, --recursive Inspect directories recursively
261
+ -u, --unregistered List only unregistered documents
262
+ -h, --help Display help for the command
263
+ ```
264
+
265
+ List registered documents directly inside `docs`:
266
+
267
+ ```sh
268
+ npx waymark ls docs
269
+ ```
270
+
271
+ Include all nested directories:
272
+
273
+ ```sh
274
+ npx waymark ls --recursive docs
275
+ ```
276
+
277
+ Find Markdown and MDX files that are missing Waymark metadata:
278
+
279
+ ```sh
280
+ npx waymark ls -R --unregistered docs
281
+ ```
282
+
283
+ `ls` respects `.gitignore`, Waymark exclusions, and Git directory boundaries.
284
+ The selected directory must be inside the repository root.
285
+
286
+ ### `waymark help`
287
+
288
+ Show the command list or detailed help for one command:
289
+
290
+ ```text
291
+ Usage: waymark help [command]
292
+ ```
293
+
294
+ ```sh
295
+ npx waymark --help
296
+ npx waymark help find
297
+ npx waymark find --help
298
+ ```
299
+
300
+ ## Contributing
301
+
302
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and checks.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { CommanderError, Command } from "commander";
3
+ import packageJson from "../package.json" with { type: "json" };
4
+ import { createFindCommand } from "./commands/find.js";
5
+ import { createInitCommand } from "./commands/init.js";
6
+ import { createLsCommand } from "./commands/ls.js";
7
+ import { createStatusCommand } from "./commands/status.js";
8
+ const program = new Command()
9
+ .name("waymark")
10
+ .description("Discover repository documentation deterministically")
11
+ .version(packageJson.version)
12
+ .exitOverride();
13
+ program.addCommand(createInitCommand());
14
+ program.addCommand(createStatusCommand());
15
+ program.addCommand(createFindCommand());
16
+ program.addCommand(createLsCommand());
17
+ try {
18
+ await program.parseAsync();
19
+ }
20
+ catch (error) {
21
+ if (error instanceof CommanderError) {
22
+ process.exitCode = error.exitCode;
23
+ }
24
+ else {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ process.stderr.write(`error: ${message}\n`);
27
+ process.exitCode = 1;
28
+ }
29
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createFindCommand(): Command;
@@ -0,0 +1,176 @@
1
+ import { Command } from "commander";
2
+ import { loadConfiguration } from "../configuration/index.js";
3
+ import { compareDiagnostics, throwDiagnostics } from "../diagnostics.js";
4
+ import { filterDocuments, scanDocuments, } from "../documents/index.js";
5
+ export function createFindCommand() {
6
+ return new Command("find")
7
+ .description("Discover Waymark Documents")
8
+ .option("-k, --kinds <identifiers>", "Match any Document Kind (comma-separated, repeatable)", collectOptionValue, [])
9
+ .option("-t, --tags <identifiers>", "Match any Document Tag (comma-separated, repeatable)", collectOptionValue, [])
10
+ .option("-r, --require-tags <identifiers>", "Require every Document Tag (comma-separated, repeatable)", collectOptionValue, [])
11
+ .option("-f, --filter <expression>", "Match a Boolean Metadata Filter")
12
+ .option("-q, --query <text>", "Match a literal Content Query")
13
+ .option("-s, --show <fields>", "Show kind, tags, and description (comma-separated)")
14
+ .option("--json", "Return a flat JSON array")
15
+ .option("--tree", "Return a directory-tree presentation")
16
+ .action(async (options) => {
17
+ if (options.filter !== undefined &&
18
+ (options.kinds.length > 0 ||
19
+ options.tags.length > 0 ||
20
+ options.requireTags.length > 0)) {
21
+ throw new Error("--filter cannot be combined with --kinds, --tags, or --require-tags.");
22
+ }
23
+ if (options.json && options.tree) {
24
+ throw new Error("--json cannot be combined with --tree.");
25
+ }
26
+ const shownFields = parseShownFields(options.show);
27
+ const loadedConfiguration = await loadConfiguration(process.cwd());
28
+ if (loadedConfiguration.kind === "malformed") {
29
+ throwDiagnostics(loadedConfiguration.diagnostics);
30
+ }
31
+ const { configuration, rootPath } = loadedConfiguration;
32
+ const documentScan = await scanDocuments({ rootPath, configuration });
33
+ const diagnostics = [
34
+ ...loadedConfiguration.diagnostics,
35
+ ...(documentScan.kind === "invalid" ? documentScan.diagnostics : []),
36
+ ].sort(compareDiagnostics);
37
+ if (loadedConfiguration.diagnostics.length > 0 ||
38
+ documentScan.kind === "invalid") {
39
+ throwDiagnostics(diagnostics);
40
+ }
41
+ const matchingDocuments = filterDocuments({
42
+ documents: documentScan.documents,
43
+ configuration,
44
+ criteria: {
45
+ kinds: options.kinds,
46
+ tags: options.tags,
47
+ requiredTags: options.requireTags,
48
+ filter: options.filter,
49
+ query: options.query,
50
+ },
51
+ });
52
+ if (options.json) {
53
+ process.stdout.write(`${JSON.stringify(matchingDocuments.map((document) => projectDocument(document, shownFields)), undefined, 2)}\n`);
54
+ }
55
+ else if (options.tree) {
56
+ process.stdout.write(renderDocumentTree(matchingDocuments, shownFields));
57
+ }
58
+ else {
59
+ process.stdout.write(matchingDocuments
60
+ .map((document) => renderDocumentLine(document, shownFields))
61
+ .join("\n") + (matchingDocuments.length > 0 ? "\n" : ""));
62
+ }
63
+ });
64
+ }
65
+ function projectDocument(document, shownFields) {
66
+ const projection = { path: document.path };
67
+ if (shownFields.has("kind"))
68
+ projection.kind = document.kind;
69
+ if (shownFields.has("tags"))
70
+ projection.tags = document.tags;
71
+ if (shownFields.has("description")) {
72
+ projection.description = document.description;
73
+ }
74
+ return projection;
75
+ }
76
+ function parseShownFields(value) {
77
+ if (value === undefined)
78
+ return new Set();
79
+ const shownFields = new Set();
80
+ for (const field of value.split(",")) {
81
+ if (field !== "kind" && field !== "tags" && field !== "description") {
82
+ throw new Error(`Unknown find field "${field}". Expected kind, tags, or description.`);
83
+ }
84
+ if (shownFields.has(field)) {
85
+ throw new Error(`Duplicate find field "${field}".`);
86
+ }
87
+ shownFields.add(field);
88
+ }
89
+ return shownFields;
90
+ }
91
+ function renderDocumentLine(document, shownFields, displayedPath = document.path) {
92
+ let line = displayedPath;
93
+ if (shownFields.has("kind"))
94
+ line += ` [${document.kind}]`;
95
+ if (shownFields.has("tags"))
96
+ line += ` [${document.tags.join(",")}]`;
97
+ if (shownFields.has("description")) {
98
+ const description = document.description.replaceAll(/\s+/g, " ").trim();
99
+ line += ` — ${description}`;
100
+ }
101
+ return line;
102
+ }
103
+ function renderDocumentTree(documents, shownFields) {
104
+ const root = createTreeDirectory();
105
+ for (const document of documents) {
106
+ const pathParts = document.path.split("/");
107
+ const fileName = pathParts.pop();
108
+ if (!fileName)
109
+ continue;
110
+ let directory = root;
111
+ for (const pathPart of pathParts) {
112
+ let childDirectory = directory.directories.get(pathPart);
113
+ if (!childDirectory) {
114
+ childDirectory = createTreeDirectory();
115
+ directory.directories.set(pathPart, childDirectory);
116
+ }
117
+ directory = childDirectory;
118
+ }
119
+ directory.documents.set(fileName, document);
120
+ }
121
+ let output = "";
122
+ for (const entry of sortedTreeEntries(root)) {
123
+ if (entry.kind === "document") {
124
+ output += `${renderDocumentLine(entry.document, shownFields, entry.name)}\n`;
125
+ }
126
+ else {
127
+ output += `${entry.name}/\n`;
128
+ output += renderTreeDirectory(entry.directory, shownFields, "");
129
+ }
130
+ }
131
+ return output;
132
+ }
133
+ function renderTreeDirectory(directory, shownFields, prefix) {
134
+ const entries = sortedTreeEntries(directory);
135
+ let output = "";
136
+ for (const [index, entry] of entries.entries()) {
137
+ const isLast = index === entries.length - 1;
138
+ const connector = isLast ? "└── " : "├── ";
139
+ if (entry.kind === "document") {
140
+ output +=
141
+ `${prefix}${connector}` +
142
+ `${renderDocumentLine(entry.document, shownFields, entry.name)}\n`;
143
+ }
144
+ else {
145
+ output += `${prefix}${connector}${entry.name}/\n`;
146
+ output += renderTreeDirectory(entry.directory, shownFields, `${prefix}${isLast ? " " : "│ "}`);
147
+ }
148
+ }
149
+ return output;
150
+ }
151
+ function sortedTreeEntries(directory) {
152
+ return [
153
+ ...[...directory.directories].map(([name, childDirectory]) => ({
154
+ kind: "directory",
155
+ name,
156
+ directory: childDirectory,
157
+ })),
158
+ ...[...directory.documents].map(([name, document]) => ({
159
+ kind: "document",
160
+ name,
161
+ document,
162
+ })),
163
+ ].sort((left, right) => compareText(left.kind === "directory" ? `${left.name}/` : left.name, right.kind === "directory" ? `${right.name}/` : right.name));
164
+ }
165
+ function createTreeDirectory() {
166
+ return {
167
+ directories: new Map(),
168
+ documents: new Map(),
169
+ };
170
+ }
171
+ function collectOptionValue(value, previous) {
172
+ return [...previous, value];
173
+ }
174
+ function compareText(left, right) {
175
+ return left < right ? -1 : left > right ? 1 : 0;
176
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createInitCommand(): Command;
@@ -0,0 +1,11 @@
1
+ import { basename } from "node:path";
2
+ import { Command } from "commander";
3
+ import { initializeConfiguration } from "../configuration/index.js";
4
+ export function createInitCommand() {
5
+ return new Command("init")
6
+ .description("Create a starter Waymark configuration")
7
+ .action(async () => {
8
+ const { configurationPath } = await initializeConfiguration(process.cwd());
9
+ process.stdout.write(`Created ${basename(configurationPath)}\n`);
10
+ });
11
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createLsCommand(): Command;
@@ -0,0 +1,39 @@
1
+ import { resolve } from "node:path";
2
+ import { Command } from "commander";
3
+ import { loadConfiguration } from "../configuration/index.js";
4
+ import { throwDiagnostics } from "../diagnostics.js";
5
+ import { scanDocuments } from "../documents/index.js";
6
+ export function createLsCommand() {
7
+ return new Command("ls")
8
+ .description("Inventory document registration in a directory")
9
+ .argument("[directory]", "Directory to inspect")
10
+ .option("-R, --recursive", "Inspect directories recursively")
11
+ .option("-u, --unregistered", "List only Unregistered Documents")
12
+ .action(async (directory, options) => {
13
+ const loadedConfiguration = await loadConfiguration(process.cwd());
14
+ if (loadedConfiguration.kind === "malformed") {
15
+ throwDiagnostics(loadedConfiguration.diagnostics);
16
+ }
17
+ if (loadedConfiguration.diagnostics.length > 0) {
18
+ throwDiagnostics(loadedConfiguration.diagnostics);
19
+ }
20
+ const rootPath = loadedConfiguration.rootPath;
21
+ const documentScan = await scanDocuments({
22
+ rootPath,
23
+ configuration: loadedConfiguration.configuration,
24
+ scope: {
25
+ directoryPath: resolve(process.cwd(), directory ?? "."),
26
+ recursive: options.recursive === true,
27
+ },
28
+ });
29
+ if (documentScan.kind === "invalid") {
30
+ throwDiagnostics(documentScan.diagnostics);
31
+ }
32
+ const paths = options.unregistered
33
+ ? documentScan.unregisteredDocuments
34
+ : documentScan.documents.map((document) => document.path);
35
+ const output = paths.join("\n");
36
+ if (output !== "")
37
+ process.stdout.write(`${output}\n`);
38
+ });
39
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createStatusCommand(): Command;
@@ -0,0 +1,71 @@
1
+ import { Command } from "commander";
2
+ import { loadConfiguration, } from "../configuration/index.js";
3
+ import { compareDiagnostics, throwDiagnostics } from "../diagnostics.js";
4
+ import { scanDocuments } from "../documents/index.js";
5
+ export function createStatusCommand() {
6
+ return new Command("status")
7
+ .description("Validate and summarize the Waymark repository")
8
+ .option("-s, --show <fields>", "Show declared kind and tag details (kind,tags)")
9
+ .action(async (options) => {
10
+ const shownFields = parseShownFields(options.show);
11
+ const loadedConfiguration = await loadConfiguration(process.cwd());
12
+ if (loadedConfiguration.kind === "malformed") {
13
+ process.stdout.write(`Root: ${loadedConfiguration.rootPath}\n` + "Status: invalid\n");
14
+ throwDiagnostics(loadedConfiguration.diagnostics);
15
+ }
16
+ const { configuration, rootPath } = loadedConfiguration;
17
+ const documentScan = await scanDocuments({ rootPath, configuration });
18
+ const diagnostics = [
19
+ ...loadedConfiguration.diagnostics,
20
+ ...(documentScan.kind === "invalid" ? documentScan.diagnostics : []),
21
+ ].sort(compareDiagnostics);
22
+ if (loadedConfiguration.diagnostics.length > 0 ||
23
+ documentScan.kind === "invalid") {
24
+ process.stdout.write(`Root: ${rootPath}\n` + "Status: invalid\n");
25
+ throwDiagnostics(diagnostics);
26
+ }
27
+ let output = `Root: ${rootPath}\n` +
28
+ "Status: valid\n" +
29
+ `Waymark Documents: ${documentScan.documents.length}\n` +
30
+ `Unregistered Documents: ${documentScan.unregisteredDocuments.length}\n` +
31
+ `Kinds: ${configuration.kinds.size}\n` +
32
+ `Tags: ${configuration.tags.size}\n`;
33
+ if (shownFields.size > 0)
34
+ output += "\n";
35
+ if (shownFields.has("kind")) {
36
+ output += renderDeclaredValues("Kinds", configuration.kinds, documentScan.kindUsageCounts);
37
+ }
38
+ if (shownFields.has("tags")) {
39
+ output += renderDeclaredValues("Tags", configuration.tags, documentScan.tagUsageCounts);
40
+ }
41
+ process.stdout.write(output);
42
+ });
43
+ }
44
+ function parseShownFields(value) {
45
+ if (value === undefined)
46
+ return new Set();
47
+ const fields = value.split(",");
48
+ const shownFields = new Set();
49
+ for (const field of fields) {
50
+ if (field !== "kind" && field !== "tags") {
51
+ throw new Error(`Unknown status field "${field}". Expected kind or tags.`);
52
+ }
53
+ if (shownFields.has(field)) {
54
+ throw new Error(`Duplicate status field "${field}".`);
55
+ }
56
+ shownFields.add(field);
57
+ }
58
+ return shownFields;
59
+ }
60
+ function renderDeclaredValues(heading, values, usageCounts) {
61
+ let output = `${heading}:\n`;
62
+ const sortedValues = [...values.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
63
+ for (const [identifier, value] of sortedValues) {
64
+ const documentCount = usageCounts.get(identifier) ?? 0;
65
+ const noun = documentCount === 1 ? "document" : "documents";
66
+ const description = value.description.replaceAll(/\s+/g, " ").trim();
67
+ output +=
68
+ ` ${identifier} — ${description} ` + `(${documentCount} ${noun})\n`;
69
+ }
70
+ return output;
71
+ }
@@ -0,0 +1,3 @@
1
+ export { initializeConfiguration } from "./initialize.js";
2
+ export { configurationFileName, loadConfiguration } from "./load.js";
3
+ export type { Configuration, ConfigurationDeclaration, ConfigurationDiagnostic, ConfigurationLoadResult, } from "./load.js";