waymark-docs 0.1.0 → 0.1.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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to Waymark are documented in this file.
4
4
 
5
+ ## [0.1.1](https://github.com/ysfaran/waymark/compare/v0.1.0...v0.1.1) (2026-08-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * **cli:** add Waymark documentation discovery CLI ([ea0e515](https://github.com/ysfaran/waymark/commit/ea0e515479b323448b2a76c84c343ea314786093))
11
+
5
12
  ## 0.1.0 (2026-07-31)
6
13
 
7
14
  - 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,7 +35,7 @@ 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 in the repository root:
39
39
 
40
40
  ```sh
41
41
  npx waymark init
@@ -47,6 +47,11 @@ npm install --save-dev waymark-docs
47
47
  # When true, document metadata must be nested under a `waymark` frontmatter key.
48
48
  require-namespace: false
49
49
 
50
+ # Skip generated or vendored documentation during discovery.
51
+ ignore:
52
+ - docs/generated/**
53
+ - vendor/**
54
+
50
55
  kinds:
51
56
  adr: Read to understand past architectural decisions and their constraints
52
57
  convention: Read before changing code to follow required repository practices
@@ -56,6 +61,9 @@ npm install --save-dev waymark-docs
56
61
  typescript: TypeScript-related documentation
57
62
  ```
58
63
 
64
+ Ignore patterns are relative to the repository root and support `*`, `?`,
65
+ and `**` wildcards. Waymark also honors `.gitignore` automatically.
66
+
59
67
  3. Register a Markdown or MDX file by adding Waymark metadata. For example,
60
68
  save this as `docs/conventions/typescript.md`:
61
69
 
@@ -93,8 +101,10 @@ npm install --save-dev waymark-docs
93
101
  docs/conventions/typescript.md — TypeScript conventions for this repository
94
102
  ```
95
103
 
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.
104
+ Waymark uses `waymark.yml` by default and also recognizes `waymark.yaml`. It
105
+ looks for the configuration in the current directory and its ancestors, so
106
+ commands can also run from a nested repository directory. Use only one filename
107
+ per directory.
98
108
 
99
109
  ## Commands
100
110
 
@@ -108,7 +118,7 @@ commands can also be run from a nested repository directory.
108
118
 
109
119
  ### `waymark init`
110
120
 
111
- Create a starter `waymark.yaml` in the current directory.
121
+ Create a starter configuration in the current directory.
112
122
 
113
123
  ```text
114
124
  Usage: waymark init [options]
@@ -138,10 +148,10 @@ configuration beneath another Waymark root.
138
148
 
139
149
  ### `waymark status`
140
150
 
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.
151
+ Validate the Waymark configuration and all discovered Waymark Documents, then
152
+ print the repository root and counts for registered documents, unregistered
153
+ documents, kinds, and tags. Invalid repositories produce diagnostics and a
154
+ non-zero exit code, which makes this command suitable for CI.
145
155
 
146
156
  ```text
147
157
  Usage: waymark status [options]
@@ -280,7 +290,7 @@ Find Markdown and MDX files that are missing Waymark metadata:
280
290
  npx waymark ls -R --unregistered docs
281
291
  ```
282
292
 
283
- `ls` respects `.gitignore`, Waymark exclusions, and Git directory boundaries.
293
+ `ls` respects `.gitignore`, Waymark ignore patterns, and Git directory boundaries.
284
294
  The selected directory must be inside the repository root.
285
295
 
286
296
  ### `waymark help`
@@ -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.1.1",
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"