waypoint-codex 0.13.3 → 0.14.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/README.md CHANGED
@@ -72,6 +72,10 @@ The philosophy is simple:
72
72
  - investigation before status narration
73
73
  - structured workflows that stay in their own tools
74
74
 
75
+ By default, Waypoint routes docs from `.waypoint/docs/` and plans from `.waypoint/plans/`.
76
+ If your repo keeps routable docs elsewhere, you can add more explicit roots in `.waypoint/config.toml` with `docs_dirs` and `plans_dirs`.
77
+ Waypoint scans each configured root recursively and only includes Markdown files with valid Waypoint frontmatter.
78
+
75
79
  ## Best fit
76
80
 
77
81
  Waypoint is most useful when you want:
@@ -117,6 +121,20 @@ repo/
117
121
 
118
122
  From there, start your Codex session in the repo and follow the generated bootstrap in `AGENTS.md`.
119
123
 
124
+ If you want to add more routable roots, extend `.waypoint/config.toml` like this:
125
+
126
+ ```toml
127
+ docs_dirs = [
128
+ ".waypoint/docs",
129
+ "services/app/docs",
130
+ ]
131
+
132
+ plans_dirs = [
133
+ ".waypoint/plans",
134
+ "services/app/plans",
135
+ ]
136
+ ```
137
+
120
138
  ## Built-in skills
121
139
 
122
140
  Waypoint ships a strong default skill pack for real coding work:
package/dist/src/core.js CHANGED
@@ -1,9 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
2
2
  import path from "node:path";
3
3
  import * as TOML from "@iarna/toml";
4
4
  import { renderDocsIndex } from "./docs-index.js";
5
5
  import { renderTracksIndex } from "./track-index.js";
6
- import { readTemplate, renderWaypointConfig, MANAGED_BLOCK_END, MANAGED_BLOCK_START, templatePath } from "./templates.js";
6
+ import { defaultWaypointConfig, readTemplate, renderWaypointConfig, MANAGED_BLOCK_END, MANAGED_BLOCK_START, templatePath, } from "./templates.js";
7
7
  const DEFAULT_CONFIG_PATH = ".waypoint/config.toml";
8
8
  const DEFAULT_DOCS_DIR = ".waypoint/docs";
9
9
  const DEFAULT_DOCS_INDEX = ".waypoint/DOCS_INDEX.md";
@@ -84,18 +84,75 @@ const TIMESTAMPED_WORKSPACE_SECTIONS = new Set([
84
84
  "## Done Recently",
85
85
  ]);
86
86
  const TIMESTAMPED_ENTRY_PATTERN = /^(?:[-*]|\d+\.)\s+\[\d{4}-\d{2}-\d{2} \d{2}:\d{2} [A-Z]{2,5}\]/;
87
+ function configuredRootDirs(projectRoot, roots, legacyRoot, fallbackRoot) {
88
+ const configuredRoots = roots && roots.length > 0
89
+ ? roots
90
+ : legacyRoot
91
+ ? [legacyRoot]
92
+ : [fallbackRoot];
93
+ const normalizedRoots = [];
94
+ const seen = new Set();
95
+ for (const root of configuredRoots) {
96
+ const trimmedRoot = root.trim();
97
+ if (trimmedRoot.length === 0) {
98
+ continue;
99
+ }
100
+ const resolvedRoot = path.resolve(projectRoot, trimmedRoot);
101
+ let dedupeKey = path.normalize(resolvedRoot);
102
+ if (existsSync(resolvedRoot)) {
103
+ try {
104
+ dedupeKey = realpathSync(resolvedRoot);
105
+ }
106
+ catch {
107
+ dedupeKey = path.normalize(resolvedRoot);
108
+ }
109
+ }
110
+ if (seen.has(dedupeKey)) {
111
+ continue;
112
+ }
113
+ seen.add(dedupeKey);
114
+ normalizedRoots.push(resolvedRoot);
115
+ }
116
+ return normalizedRoots.length > 0 ? normalizedRoots : [path.resolve(projectRoot, fallbackRoot)];
117
+ }
118
+ function docsRootDirs(projectRoot, config) {
119
+ return configuredRootDirs(projectRoot, config?.docs_dirs, config?.docs_dir, DEFAULT_DOCS_DIR);
120
+ }
121
+ function plansRootDirs(projectRoot, config) {
122
+ return configuredRootDirs(projectRoot, config?.plans_dirs, config?.plans_dir, DEFAULT_PLANS_DIR);
123
+ }
124
+ function docsSectionHeading(projectRoot, dir) {
125
+ const relativePath = path.relative(projectRoot, dir).split(path.sep).join("/");
126
+ const normalizedPath = relativePath.length === 0 ? "." : relativePath;
127
+ return normalizedPath.endsWith("/") ? normalizedPath : `${normalizedPath}/`;
128
+ }
87
129
  function docsIndexSections(projectRoot, config) {
88
130
  return [
89
- {
90
- heading: ".waypoint/docs/",
91
- dir: path.join(projectRoot, config?.docs_dir ?? DEFAULT_DOCS_DIR),
92
- },
93
- {
94
- heading: ".waypoint/plans/",
95
- dir: path.join(projectRoot, config?.plans_dir ?? DEFAULT_PLANS_DIR),
96
- },
131
+ ...docsRootDirs(projectRoot, config).map((dir) => ({
132
+ heading: docsSectionHeading(projectRoot, dir),
133
+ dir,
134
+ })),
135
+ ...plansRootDirs(projectRoot, config).map((dir) => ({
136
+ heading: docsSectionHeading(projectRoot, dir),
137
+ dir,
138
+ })),
97
139
  ];
98
140
  }
141
+ function buildWaypointConfig(projectRoot, existingConfig, options) {
142
+ const defaults = defaultWaypointConfig({ profile: options.profile });
143
+ return {
144
+ version: existingConfig?.version ?? defaults.version,
145
+ profile: options.profile,
146
+ workspace_file: existingConfig?.workspace_file ?? defaults.workspace_file,
147
+ docs_dirs: configuredRootDirs(projectRoot, existingConfig?.docs_dirs, existingConfig?.docs_dir, DEFAULT_DOCS_DIR).map((dir) => path.relative(projectRoot, dir).split(path.sep).join("/")),
148
+ plans_dirs: configuredRootDirs(projectRoot, existingConfig?.plans_dirs, existingConfig?.plans_dir, DEFAULT_PLANS_DIR).map((dir) => path.relative(projectRoot, dir).split(path.sep).join("/")),
149
+ docs_index_file: existingConfig?.docs_index_file ?? defaults.docs_index_file,
150
+ features: {
151
+ repo_skills: existingConfig?.features?.repo_skills ?? defaults.features?.repo_skills,
152
+ docs_index: existingConfig?.features?.docs_index ?? defaults.features?.docs_index,
153
+ },
154
+ };
155
+ }
99
156
  function ensureDir(dirPath) {
100
157
  mkdirSync(dirPath, { recursive: true });
101
158
  }
@@ -288,6 +345,7 @@ function scaffoldOptionalCodex(projectRoot) {
288
345
  export function initRepository(projectRoot, options) {
289
346
  ensureDir(projectRoot);
290
347
  migrateLegacyRootFiles(projectRoot);
348
+ const config = buildWaypointConfig(projectRoot, loadWaypointConfig(projectRoot), options);
291
349
  // Any Waypoint-owned path removed from the scaffold should be added here
292
350
  // so `waypoint init` / `waypoint upgrade` can actively prune stale copies.
293
351
  for (const deprecatedPath of [
@@ -325,9 +383,7 @@ export function initRepository(projectRoot, options) {
325
383
  writeText(path.join(projectRoot, ".waypoint/agent-operating-manual.md"), readTemplate(".waypoint/agent-operating-manual.md"));
326
384
  writeIfMissing(path.join(projectRoot, DEFAULT_MEMORY), readTemplate(".waypoint/MEMORY.md"));
327
385
  scaffoldWaypointOptionalTemplates(projectRoot);
328
- writeText(path.join(projectRoot, DEFAULT_CONFIG_PATH), renderWaypointConfig({
329
- profile: options.profile,
330
- }));
386
+ writeText(path.join(projectRoot, DEFAULT_CONFIG_PATH), renderWaypointConfig(config));
331
387
  writeIfMissing(path.join(projectRoot, DEFAULT_WORKSPACE), readTemplate("WORKSPACE.md"));
332
388
  ensureDir(path.join(projectRoot, DEFAULT_DOCS_DIR));
333
389
  ensureDir(path.join(projectRoot, DEFAULT_PLANS_DIR));
@@ -339,9 +395,10 @@ export function initRepository(projectRoot, options) {
339
395
  scaffoldSkills(projectRoot);
340
396
  scaffoldOptionalCodex(projectRoot);
341
397
  appendGitignoreSnippet(projectRoot);
342
- const docsIndex = renderDocsIndex(projectRoot, docsIndexSections(projectRoot));
398
+ const docsIndexPath = path.join(projectRoot, config.docs_index_file ?? DEFAULT_DOCS_INDEX);
399
+ const docsIndex = renderDocsIndex(projectRoot, docsIndexSections(projectRoot, config));
343
400
  const tracksIndex = renderTracksIndex(projectRoot, path.join(projectRoot, DEFAULT_TRACK_DIR));
344
- writeText(path.join(projectRoot, DEFAULT_DOCS_INDEX), `${docsIndex.content}\n`);
401
+ writeText(docsIndexPath, `${docsIndex.content}\n`);
345
402
  writeText(path.join(projectRoot, DEFAULT_TRACKS_INDEX), `${tracksIndex.content}\n`);
346
403
  return [
347
404
  "Initialized Waypoint scaffold",
@@ -476,27 +533,33 @@ export function doctorRepository(projectRoot) {
476
533
  }
477
534
  }
478
535
  const docsIndexPath = path.join(projectRoot, config.docs_index_file ?? DEFAULT_DOCS_INDEX);
479
- const docsDir = path.join(projectRoot, config.docs_dir ?? DEFAULT_DOCS_DIR);
480
- const plansDir = path.join(projectRoot, config.plans_dir ?? DEFAULT_PLANS_DIR);
536
+ const configuredDocsDirs = docsRootDirs(projectRoot, config);
537
+ const configuredPlansDirs = plansRootDirs(projectRoot, config);
481
538
  const docsIndex = renderDocsIndex(projectRoot, docsIndexSections(projectRoot, config));
482
539
  const trackDir = path.join(projectRoot, DEFAULT_TRACK_DIR);
483
540
  const tracksIndexPath = path.join(projectRoot, DEFAULT_TRACKS_INDEX);
484
541
  const tracksIndex = renderTracksIndex(projectRoot, trackDir);
485
- if (!existsSync(docsDir)) {
542
+ for (const docsDir of configuredDocsDirs) {
543
+ if (existsSync(docsDir)) {
544
+ continue;
545
+ }
486
546
  findings.push({
487
547
  severity: "error",
488
548
  category: "docs",
489
- message: ".waypoint/docs/ directory is missing.",
490
- remediation: "Run `waypoint init` to scaffold docs.",
549
+ message: `${docsSectionHeading(projectRoot, docsDir)} directory is missing.`,
550
+ remediation: "Create the configured docs directory or update `.waypoint/config.toml`.",
491
551
  paths: [docsDir],
492
552
  });
493
553
  }
494
- if (!existsSync(plansDir)) {
554
+ for (const plansDir of configuredPlansDirs) {
555
+ if (existsSync(plansDir)) {
556
+ continue;
557
+ }
495
558
  findings.push({
496
559
  severity: "error",
497
560
  category: "docs",
498
- message: ".waypoint/plans/ directory is missing.",
499
- remediation: "Run `waypoint init` to scaffold plans.",
561
+ message: `${docsSectionHeading(projectRoot, plansDir)} directory is missing.`,
562
+ remediation: "Create the configured plans directory or update `.waypoint/config.toml`.",
500
563
  paths: [plansDir],
501
564
  });
502
565
  }
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  const SKIP_DIRS = new Set([
4
4
  ".git",
@@ -50,18 +50,25 @@ function parseFrontmatter(filePath) {
50
50
  }
51
51
  return { summary, lastUpdated, readWhen };
52
52
  }
53
- function walkDocs(projectRoot, currentDir, output, invalid) {
54
- for (const entry of readdirSync(currentDir)) {
55
- const fullPath = path.join(currentDir, entry);
56
- const stat = statSync(fullPath);
57
- if (stat.isDirectory()) {
58
- if (SKIP_DIRS.has(entry)) {
53
+ function walkDocs(projectRoot, currentDir, output, invalid, visitedDirs) {
54
+ const resolvedCurrentDir = realpathSync(currentDir);
55
+ if (visitedDirs.has(resolvedCurrentDir)) {
56
+ return;
57
+ }
58
+ visitedDirs.add(resolvedCurrentDir);
59
+ for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
60
+ if (entry.isSymbolicLink()) {
61
+ continue;
62
+ }
63
+ const fullPath = path.join(currentDir, entry.name);
64
+ if (entry.isDirectory()) {
65
+ if (SKIP_DIRS.has(entry.name)) {
59
66
  continue;
60
67
  }
61
- walkDocs(projectRoot, fullPath, output, invalid);
68
+ walkDocs(projectRoot, fullPath, output, invalid, visitedDirs);
62
69
  continue;
63
70
  }
64
- if (!entry.endsWith(".md") || SKIP_NAMES.has(entry)) {
71
+ if (!isMarkdownDoc(entry)) {
65
72
  continue;
66
73
  }
67
74
  const { summary, lastUpdated, readWhen } = parseFrontmatter(fullPath);
@@ -77,10 +84,13 @@ function collectDocEntries(projectRoot, docsDir) {
77
84
  const entries = [];
78
85
  const invalidDocs = [];
79
86
  if (existsSync(docsDir)) {
80
- walkDocs(projectRoot, docsDir, entries, invalidDocs);
87
+ walkDocs(projectRoot, docsDir, entries, invalidDocs, new Set());
81
88
  }
82
89
  return { entries, invalidDocs };
83
90
  }
91
+ function isMarkdownDoc(entry) {
92
+ return entry.isFile() && entry.name.endsWith(".md") && !SKIP_NAMES.has(entry.name);
93
+ }
84
94
  export function renderDocsIndex(projectRoot, sections) {
85
95
  const lines = [
86
96
  "# Docs Index",
@@ -1,3 +1,4 @@
1
+ import * as TOML from "@iarna/toml";
1
2
  import { readFileSync } from "node:fs";
2
3
  import { existsSync } from "node:fs";
3
4
  import { fileURLToPath } from "node:url";
@@ -25,7 +26,32 @@ export function templatePath(relativePath) {
25
26
  export function readTemplate(relativePath) {
26
27
  return readFileSync(templatePath(relativePath), "utf8");
27
28
  }
28
- export function renderWaypointConfig(options) {
29
- return readTemplate(".waypoint/config.toml")
30
- .replace("__PROFILE__", options.profile);
29
+ export function defaultWaypointConfig(options) {
30
+ return {
31
+ version: 1,
32
+ profile: options.profile,
33
+ workspace_file: ".waypoint/WORKSPACE.md",
34
+ docs_dirs: [".waypoint/docs"],
35
+ plans_dirs: [".waypoint/plans"],
36
+ docs_index_file: ".waypoint/DOCS_INDEX.md",
37
+ features: {
38
+ repo_skills: true,
39
+ docs_index: true,
40
+ },
41
+ };
42
+ }
43
+ export function renderWaypointConfig(config) {
44
+ const renderedConfig = {
45
+ version: config.version,
46
+ profile: config.profile,
47
+ workspace_file: config.workspace_file,
48
+ docs_dirs: config.docs_dirs,
49
+ plans_dirs: config.plans_dirs,
50
+ docs_index_file: config.docs_index_file,
51
+ features: config.features ? {
52
+ repo_skills: config.features.repo_skills,
53
+ docs_index: config.features.docs_index,
54
+ } : undefined,
55
+ };
56
+ return TOML.stringify(renderedConfig);
31
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waypoint-codex",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "Codex-native repository operating system: scaffolding, docs routing, repo-local skills, doctor, and sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,8 +1,8 @@
1
1
  version = 1
2
2
  profile = "__PROFILE__"
3
3
  workspace_file = ".waypoint/WORKSPACE.md"
4
- docs_dir = ".waypoint/docs"
5
- plans_dir = ".waypoint/plans"
4
+ docs_dirs = [".waypoint/docs"]
5
+ plans_dirs = [".waypoint/plans"]
6
6
  docs_index_file = ".waypoint/DOCS_INDEX.md"
7
7
 
8
8
  [features]