waymark-docs 0.1.0 → 0.2.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 CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to Waymark are documented in this file.
4
4
 
5
+ ## [0.2.0](https://github.com/ysfaran/waymark/compare/v0.1.1...v0.2.0) (2026-08-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * **cli:** change require-tags shorthand to -T ([#3](https://github.com/ysfaran/waymark/issues/3)) ([70a95f6](https://github.com/ysfaran/waymark/commit/70a95f6f876e4b48040d778d15bc9189c6020646))
11
+
12
+ ## [0.1.1](https://github.com/ysfaran/waymark/compare/v0.1.0...v0.1.1) (2026-08-03)
13
+
14
+
15
+ ### Features
16
+
17
+ * **cli:** add Waymark documentation discovery CLI ([ea0e515](https://github.com/ysfaran/waymark/commit/ea0e515479b323448b2a76c84c343ea314786093))
18
+
5
19
  ## 0.1.0 (2026-07-31)
6
20
 
7
21
  - Initial public release.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Waymark 🪧
1
+ # 🪧 Waymark
2
2
 
3
3
  Find the right repository docs for your coding agent.
4
4
 
@@ -35,18 +35,19 @@ npm install --save-dev waymark-docs
35
35
 
36
36
  ## Quick start
37
37
 
38
- 1. Create `waymark.yaml` in the repository root:
38
+ 1. **Create a Waymark configuration.**
39
+
40
+ Run `init` in the repository root:
39
41
 
40
42
  ```sh
41
43
  npx waymark init
42
44
  ```
43
45
 
44
- 2. Define the document kinds and tags that agents can search:
46
+ 2. **Define searchable metadata.**
45
47
 
46
- ```yaml
47
- # When true, document metadata must be nested under a `waymark` frontmatter key.
48
- require-namespace: false
48
+ Add the document kinds and tags that agents can search:
49
49
 
50
+ ```yaml
50
51
  kinds:
51
52
  adr: Read to understand past architectural decisions and their constraints
52
53
  convention: Read before changing code to follow required repository practices
@@ -56,8 +57,10 @@ npm install --save-dev waymark-docs
56
57
  typescript: TypeScript-related documentation
57
58
  ```
58
59
 
59
- 3. Register a Markdown or MDX file by adding Waymark metadata. For example,
60
- save this as `docs/conventions/typescript.md`:
60
+ 3. **Register a document.**
61
+
62
+ Add Waymark metadata to a Markdown or MDX file. For example, save this as
63
+ `docs/conventions/typescript.md`:
61
64
 
62
65
  ```yaml
63
66
  ---
@@ -68,7 +71,9 @@ npm install --save-dev waymark-docs
68
71
  # TypeScript conventions
69
72
  ```
70
73
 
71
- 4. Validate the repository:
74
+ 4. **Validate the repository.**
75
+
76
+ Check the configuration and discovered documents:
72
77
 
73
78
  ```sh
74
79
  npx waymark status
@@ -83,7 +88,9 @@ npm install --save-dev waymark-docs
83
88
  Tags: 2
84
89
  ```
85
90
 
86
- 5. Discover the document:
91
+ 5. **Discover the document.**
92
+
93
+ Find the registered convention by kind and tag:
87
94
 
88
95
  ```sh
89
96
  npx waymark find --kinds convention --tags typescript --show description
@@ -93,8 +100,10 @@ npm install --save-dev waymark-docs
93
100
  docs/conventions/typescript.md — TypeScript conventions for this repository
94
101
  ```
95
102
 
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.
103
+ Waymark uses `waymark.yml` by default and also recognizes `waymark.yaml`. It
104
+ looks for the configuration in the current directory and its ancestors, so
105
+ commands can also run from a nested repository directory. Use only one filename
106
+ per directory.
98
107
 
99
108
  ## Commands
100
109
 
@@ -108,7 +117,7 @@ commands can also be run from a nested repository directory.
108
117
 
109
118
  ### `waymark init`
110
119
 
111
- Create a starter `waymark.yaml` in the current directory.
120
+ Create a starter configuration in the current directory.
112
121
 
113
122
  ```text
114
123
  Usage: waymark init [options]
@@ -133,15 +142,28 @@ tags:
133
142
  example-tag: Explain the topic represented by this tag
134
143
  ```
135
144
 
145
+ To skip generated or vendored documentation during discovery, add `ignore`
146
+ patterns:
147
+
148
+ ```yaml
149
+ # Skip generated or vendored documentation during discovery.
150
+ ignore:
151
+ - docs/generated/**
152
+ - vendor/**
153
+ ```
154
+
155
+ Ignore patterns are relative to the repository root and support `*`, `?`, and
156
+ `**` wildcards. Waymark also honors `.gitignore` automatically.
157
+
136
158
  `init` never overwrites an existing configuration and does not allow a nested
137
159
  configuration beneath another Waymark root.
138
160
 
139
161
  ### `waymark status`
140
162
 
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.
163
+ Validate the Waymark configuration and all discovered Waymark Documents, then
164
+ print the repository root and counts for registered documents, unregistered
165
+ documents, kinds, and tags. Invalid repositories produce diagnostics and a
166
+ non-zero exit code, which makes this command suitable for CI.
145
167
 
146
168
  ```text
147
169
  Usage: waymark status [options]
@@ -180,7 +202,7 @@ Usage: waymark find [options]
180
202
  Options:
181
203
  -k, --kinds <identifiers> Match any kind (comma-separated, repeatable)
182
204
  -t, --tags <identifiers> Match any tag (comma-separated, repeatable)
183
- -r, --require-tags <identifiers> Require every tag (comma-separated, repeatable)
205
+ -T, --require-tags <identifiers> Require every tag (comma-separated, repeatable)
184
206
  -f, --filter <expression> Match a Boolean metadata filter
185
207
  -q, --query <text> Match a literal content query
186
208
  -s, --show <fields> Show kind, tags, and description
@@ -280,7 +302,7 @@ Find Markdown and MDX files that are missing Waymark metadata:
280
302
  npx waymark ls -R --unregistered docs
281
303
  ```
282
304
 
283
- `ls` respects `.gitignore`, Waymark exclusions, and Git directory boundaries.
305
+ `ls` respects `.gitignore`, Waymark ignore patterns, and Git directory boundaries.
284
306
  The selected directory must be inside the repository root.
285
307
 
286
308
  ### `waymark help`
@@ -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("-r, --require-tags <identifiers>", "Require every Document Tag (comma-separated, repeatable)", collectOptionValue, [])
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)")
@@ -1,3 +1,3 @@
1
1
  export { initializeConfiguration } from "./initialize.js";
2
- export { configurationFileName, loadConfiguration } from "./load.js";
2
+ export { allowedConfigFileNames, defaultConfigFileName, loadConfiguration, } from "./load.js";
3
3
  export type { Configuration, ConfigurationDeclaration, ConfigurationDiagnostic, ConfigurationLoadResult, } from "./load.js";
@@ -1,2 +1,2 @@
1
1
  export { initializeConfiguration } from "./initialize.js";
2
- export { configurationFileName, loadConfiguration } from "./load.js";
2
+ export { allowedConfigFileNames, defaultConfigFileName, loadConfiguration, } from "./load.js";
@@ -1,7 +1,7 @@
1
1
  import { writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
- import { isErrorWithCode, pathExists } from "../filesystem.js";
4
- import { configurationFileName, findConfigurationPath } from "./load.js";
3
+ import { isErrorWithCode } from "../filesystem.js";
4
+ import { defaultConfigFileName, findConfigurationPath, findConfigurationPathInDirectory, } from "./load.js";
5
5
  const starterConfiguration = "# When true, document metadata must be nested under a `waymark` frontmatter key.\n" +
6
6
  "require-namespace: false\n" +
7
7
  "kinds:\n" +
@@ -9,9 +9,10 @@ const starterConfiguration = "# When true, document metadata must be nested unde
9
9
  "tags:\n" +
10
10
  " example-tag: Explain the topic represented by this tag\n";
11
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}.`);
12
+ const configurationPath = join(directoryPath, defaultConfigFileName);
13
+ const existingConfigurationPath = await findConfigurationPathInDirectory(directoryPath);
14
+ if (existingConfigurationPath) {
15
+ throw new Error(`A Waymark configuration already exists at ${existingConfigurationPath}.`);
15
16
  }
16
17
  const ancestorConfigurationPath = await findConfigurationPath(dirname(directoryPath));
17
18
  if (ancestorConfigurationPath) {
@@ -1,4 +1,5 @@
1
- export declare const configurationFileName: string;
1
+ export declare const defaultConfigFileName = "waymark.yml";
2
+ export declare const allowedConfigFileNames: readonly ["waymark.yml", "waymark.yaml"];
2
3
  export type ConfigurationDeclaration = {
3
4
  description: string;
4
5
  };
@@ -11,7 +12,7 @@ export type Configuration = {
11
12
  requireNamespace: boolean;
12
13
  kinds: Map<string, ConfigurationDeclaration>;
13
14
  tags: Map<string, ConfigurationDeclaration>;
14
- exclusions: string[];
15
+ ignorePatterns: string[];
15
16
  };
16
17
  export type ConfigurationLoadResult = {
17
18
  kind: "loaded";
@@ -25,3 +26,4 @@ export type ConfigurationLoadResult = {
25
26
  };
26
27
  export declare function loadConfiguration(startingDirectoryPath: string): Promise<ConfigurationLoadResult>;
27
28
  export declare function findConfigurationPath(startingDirectoryPath: string): Promise<string | undefined>;
29
+ export declare function findConfigurationPathInDirectory(directoryPath: string): Promise<string | undefined>;
@@ -1,14 +1,19 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { dirname, join, resolve } from "node:path";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
3
  import { parse } from "yaml";
4
4
  import { pathExists } from "../filesystem.js";
5
- export const configurationFileName = "waymark.yaml";
5
+ export const defaultConfigFileName = "waymark.yml";
6
+ export const allowedConfigFileNames = [
7
+ defaultConfigFileName,
8
+ "waymark.yaml",
9
+ ];
6
10
  export async function loadConfiguration(startingDirectoryPath) {
7
11
  const configurationPath = await findConfigurationPath(startingDirectoryPath);
8
12
  if (!configurationPath) {
9
- throw new Error(`Could not find ${configurationFileName} from ${resolve(startingDirectoryPath)}.`);
13
+ throw new Error(`Could not find ${allowedConfigFileNames.join(" or ")} from ${resolve(startingDirectoryPath)}.`);
10
14
  }
11
15
  const rootPath = dirname(configurationPath);
16
+ const loadedConfigurationFileName = basename(configurationPath);
12
17
  let parsedConfiguration;
13
18
  try {
14
19
  parsedConfiguration = parse(await readFile(configurationPath, "utf8"));
@@ -19,7 +24,7 @@ export async function loadConfiguration(startingDirectoryPath) {
19
24
  rootPath,
20
25
  diagnostics: [
21
26
  {
22
- path: configurationFileName,
27
+ path: loadedConfigurationFileName,
23
28
  field: "configuration",
24
29
  message: "Malformed YAML configuration.",
25
30
  },
@@ -30,7 +35,11 @@ export async function loadConfiguration(startingDirectoryPath) {
30
35
  return {
31
36
  kind: "loaded",
32
37
  rootPath,
33
- configuration: validateConfiguration(parsedConfiguration, diagnostics),
38
+ configuration: validateConfiguration({
39
+ value: parsedConfiguration,
40
+ configurationFileName: loadedConfigurationFileName,
41
+ diagnostics,
42
+ }),
34
43
  diagnostics,
35
44
  };
36
45
  }
@@ -38,17 +47,30 @@ export async function findConfigurationPath(startingDirectoryPath) {
38
47
  let directoryPath = resolve(startingDirectoryPath);
39
48
  let foundConfigurationPath;
40
49
  while (true) {
41
- const configurationPath = join(directoryPath, configurationFileName);
42
- if (await pathExists(configurationPath)) {
50
+ const configurationPath = await findConfigurationPathInDirectory(directoryPath);
51
+ if (configurationPath)
43
52
  foundConfigurationPath = configurationPath;
44
- }
45
53
  const parentPath = dirname(directoryPath);
46
54
  if (parentPath === directoryPath)
47
55
  return foundConfigurationPath;
48
56
  directoryPath = parentPath;
49
57
  }
50
58
  }
51
- function validateConfiguration(value, diagnostics) {
59
+ export async function findConfigurationPathInDirectory(directoryPath) {
60
+ const configurationPaths = [];
61
+ for (const fileName of allowedConfigFileNames) {
62
+ const configurationPath = join(directoryPath, fileName);
63
+ if (await pathExists(configurationPath)) {
64
+ configurationPaths.push(configurationPath);
65
+ }
66
+ }
67
+ if (configurationPaths.length > 1) {
68
+ throw new Error(`Multiple Waymark configurations exist in ${resolve(directoryPath)}: ` +
69
+ `${allowedConfigFileNames.join(", ")}.`);
70
+ }
71
+ return configurationPaths[0];
72
+ }
73
+ function validateConfiguration({ value, configurationFileName, diagnostics, }) {
52
74
  if (!isRecord(value)) {
53
75
  diagnostics.push({
54
76
  path: configurationFileName,
@@ -61,7 +83,7 @@ function validateConfiguration(value, diagnostics) {
61
83
  "require-namespace",
62
84
  "kinds",
63
85
  "tags",
64
- "exclude",
86
+ "ignore",
65
87
  ]);
66
88
  for (const field of Object.keys(configuration)) {
67
89
  if (!allowedFields.has(field)) {
@@ -85,17 +107,23 @@ function validateConfiguration(value, diagnostics) {
85
107
  kinds: validateDeclarations({
86
108
  value: configuration.kinds,
87
109
  field: "kinds",
110
+ configurationFileName,
88
111
  diagnostics,
89
112
  }),
90
113
  tags: validateDeclarations({
91
114
  value: configuration.tags,
92
115
  field: "tags",
116
+ configurationFileName,
117
+ diagnostics,
118
+ }),
119
+ ignorePatterns: validateIgnorePatterns({
120
+ value: configuration.ignore,
121
+ configurationFileName,
93
122
  diagnostics,
94
123
  }),
95
- exclusions: validateExclusions(configuration.exclude, diagnostics),
96
124
  };
97
125
  }
98
- function validateDeclarations({ value, field, diagnostics, }) {
126
+ function validateDeclarations({ value, field, configurationFileName, diagnostics, }) {
99
127
  if (!isRecord(value)) {
100
128
  diagnostics.push({
101
129
  path: configurationFileName,
@@ -127,25 +155,25 @@ function validateDeclarations({ value, field, diagnostics, }) {
127
155
  }
128
156
  return declarations;
129
157
  }
130
- function validateExclusions(value, diagnostics) {
158
+ function validateIgnorePatterns({ value, configurationFileName, diagnostics, }) {
131
159
  if (value === undefined)
132
160
  return [];
133
161
  if (!Array.isArray(value)) {
134
162
  diagnostics.push({
135
163
  path: configurationFileName,
136
- field: "exclude",
137
- message: "Expected a sequence of exclusion globs.",
164
+ field: "ignore",
165
+ message: "Expected a sequence of ignore globs.",
138
166
  });
139
167
  return [];
140
168
  }
141
- const exclusions = [];
169
+ const ignorePatterns = [];
142
170
  for (const [index, pattern] of value.entries()) {
143
- const field = `exclude.${index}`;
171
+ const field = `ignore.${index}`;
144
172
  if (typeof pattern !== "string" || pattern.trim() === "") {
145
173
  diagnostics.push({
146
174
  path: configurationFileName,
147
175
  field,
148
- message: "Exclusion glob must be a non-empty string.",
176
+ message: "Ignore glob must be a non-empty string.",
149
177
  });
150
178
  continue;
151
179
  }
@@ -153,7 +181,7 @@ function validateExclusions(value, diagnostics) {
153
181
  diagnostics.push({
154
182
  path: configurationFileName,
155
183
  field,
156
- message: "Negation is not supported in exclusion globs.",
184
+ message: "Negation is not supported in ignore globs.",
157
185
  });
158
186
  continue;
159
187
  }
@@ -165,9 +193,9 @@ function validateExclusions(value, diagnostics) {
165
193
  });
166
194
  continue;
167
195
  }
168
- exclusions.push(pattern);
196
+ ignorePatterns.push(pattern);
169
197
  }
170
- return exclusions;
198
+ return ignorePatterns;
171
199
  }
172
200
  function isRecord(value) {
173
201
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1,7 +1,7 @@
1
1
  import { lstat, readFile, realpath } from "node:fs/promises";
2
2
  import { isAbsolute, join, relative, sep } from "node:path";
3
3
  import { convertPathToPattern, globby } from "globby";
4
- import { configurationFileName, } from "../configuration/index.js";
4
+ import { allowedConfigFileNames, } from "../configuration/index.js";
5
5
  import { isErrorWithCode } from "../filesystem.js";
6
6
  import { classifyDocument } from "./classify.js";
7
7
  export async function scanDocuments({ rootPath, configuration, scope, }) {
@@ -9,7 +9,7 @@ export async function scanDocuments({ rootPath, configuration, scope, }) {
9
9
  const discoveredPaths = await globby(createCandidatePatterns(resolvedScope), {
10
10
  cwd: scanRootPath,
11
11
  gitignore: true,
12
- ignore: ["**/.git", "**/.git/**", ...configuration.exclusions],
12
+ ignore: ["**/.git", "**/.git/**", ...configuration.ignorePatterns],
13
13
  dot: true,
14
14
  followSymbolicLinks: false,
15
15
  braceExpansion: false,
@@ -21,9 +21,9 @@ export async function scanDocuments({ rootPath, configuration, scope, }) {
21
21
  const diagnostics = [];
22
22
  const unregisteredDocuments = [];
23
23
  for (const path of discoveredPaths.sort(compareText)) {
24
- if (path === configurationFileName)
24
+ if (allowedConfigFileNames.some((fileName) => fileName === path))
25
25
  continue;
26
- if (path.endsWith(`/${configurationFileName}`)) {
26
+ if (allowedConfigFileNames.some((fileName) => path.endsWith(`/${fileName}`))) {
27
27
  diagnostics.push({
28
28
  path,
29
29
  field: "configuration",
@@ -102,7 +102,11 @@ async function resolveDocumentScanScope({ rootPath, scope, }) {
102
102
  }
103
103
  function createCandidatePatterns(scope) {
104
104
  if (!scope) {
105
- return ["**/*.md", "**/*.mdx", `**/${configurationFileName}`];
105
+ return [
106
+ "**/*.md",
107
+ "**/*.mdx",
108
+ ...allowedConfigFileNames.map((fileName) => `**/${fileName}`),
109
+ ];
106
110
  }
107
111
  const directoryPattern = convertPathToPattern(scope.relativeDirectoryPath);
108
112
  const directoryPrefix = directoryPattern === "" ? "" : `${directoryPattern}/`;
@@ -112,7 +116,7 @@ function createCandidatePatterns(scope) {
112
116
  return [
113
117
  `${candidatePrefix}*.md`,
114
118
  `${candidatePrefix}*.mdx`,
115
- `${candidatePrefix}${configurationFileName}`,
119
+ ...allowedConfigFileNames.map((fileName) => `${candidatePrefix}${fileName}`),
116
120
  ];
117
121
  }
118
122
  function createUsageCounts(declarations) {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "waymark-docs",
3
- "version": "0.1.0",
4
- "description": "Find repository documentation relevant to coding agents",
3
+ "version": "0.2.0",
4
+ "description": "Agent-focused CLI that surfaces repository documentation to improve coding-agent performance.",
5
5
  "keywords": [
6
6
  "ai-agents",
7
7
  "cli",
8
+ "context-engineering",
8
9
  "documentation",
9
- "markdown",
10
- "repository"
10
+ "markdown"
11
11
  ],
12
12
  "homepage": "https://github.com/ysfaran/waymark#readme",
13
13
  "bugs": {
@@ -37,7 +37,10 @@
37
37
  "dev": "tsx src/cli.ts",
38
38
  "prepack": "cp ../README.md ../CHANGELOG.md ../LICENSE . && pnpm build",
39
39
  "postpack": "rm -f README.md CHANGELOG.md LICENSE",
40
- "typecheck": "tsc --project tsconfig.json --noEmit"
40
+ "test": "pnpm test:unit && pnpm test:integration",
41
+ "test:unit": "vitest run --config vitest.unit.config.ts",
42
+ "test:integration": "vitest run --config vitest.integration.config.ts",
43
+ "typecheck": "tsc --project tsconfig.json --noEmit && tsc --project tsconfig.test.json --noEmit"
41
44
  },
42
45
  "dependencies": {
43
46
  "commander": "14.0.3",
@@ -47,7 +50,8 @@
47
50
  "devDependencies": {
48
51
  "@types/node": "24.10.10",
49
52
  "tsx": "4.20.6",
50
- "typescript": "7.0.2"
53
+ "typescript": "7.0.2",
54
+ "vitest": "4.0.18"
51
55
  },
52
56
  "engines": {
53
57
  "node": ">=24"