waypoint-codex 0.13.2 → 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";
@@ -52,6 +52,7 @@ const LEGACY_WAYPOINT_GITIGNORE_RULES = new Set([
52
52
  ".waypoint/context/",
53
53
  ".waypoint/scripts/",
54
54
  ".waypoint/track/",
55
+ ".waypoint/plans/",
55
56
  ".waypoint/*",
56
57
  "!.waypoint/docs/",
57
58
  "!.waypoint/docs/**",
@@ -83,18 +84,75 @@ const TIMESTAMPED_WORKSPACE_SECTIONS = new Set([
83
84
  "## Done Recently",
84
85
  ]);
85
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
+ }
86
129
  function docsIndexSections(projectRoot, config) {
87
130
  return [
88
- {
89
- heading: ".waypoint/docs/",
90
- dir: path.join(projectRoot, config?.docs_dir ?? DEFAULT_DOCS_DIR),
91
- },
92
- {
93
- heading: ".waypoint/plans/",
94
- dir: path.join(projectRoot, config?.plans_dir ?? DEFAULT_PLANS_DIR),
95
- },
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
+ })),
96
139
  ];
97
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
+ }
98
156
  function ensureDir(dirPath) {
99
157
  mkdirSync(dirPath, { recursive: true });
100
158
  }
@@ -287,6 +345,7 @@ function scaffoldOptionalCodex(projectRoot) {
287
345
  export function initRepository(projectRoot, options) {
288
346
  ensureDir(projectRoot);
289
347
  migrateLegacyRootFiles(projectRoot);
348
+ const config = buildWaypointConfig(projectRoot, loadWaypointConfig(projectRoot), options);
290
349
  // Any Waypoint-owned path removed from the scaffold should be added here
291
350
  // so `waypoint init` / `waypoint upgrade` can actively prune stale copies.
292
351
  for (const deprecatedPath of [
@@ -324,9 +383,7 @@ export function initRepository(projectRoot, options) {
324
383
  writeText(path.join(projectRoot, ".waypoint/agent-operating-manual.md"), readTemplate(".waypoint/agent-operating-manual.md"));
325
384
  writeIfMissing(path.join(projectRoot, DEFAULT_MEMORY), readTemplate(".waypoint/MEMORY.md"));
326
385
  scaffoldWaypointOptionalTemplates(projectRoot);
327
- writeText(path.join(projectRoot, DEFAULT_CONFIG_PATH), renderWaypointConfig({
328
- profile: options.profile,
329
- }));
386
+ writeText(path.join(projectRoot, DEFAULT_CONFIG_PATH), renderWaypointConfig(config));
330
387
  writeIfMissing(path.join(projectRoot, DEFAULT_WORKSPACE), readTemplate("WORKSPACE.md"));
331
388
  ensureDir(path.join(projectRoot, DEFAULT_DOCS_DIR));
332
389
  ensureDir(path.join(projectRoot, DEFAULT_PLANS_DIR));
@@ -338,9 +395,10 @@ export function initRepository(projectRoot, options) {
338
395
  scaffoldSkills(projectRoot);
339
396
  scaffoldOptionalCodex(projectRoot);
340
397
  appendGitignoreSnippet(projectRoot);
341
- 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));
342
400
  const tracksIndex = renderTracksIndex(projectRoot, path.join(projectRoot, DEFAULT_TRACK_DIR));
343
- writeText(path.join(projectRoot, DEFAULT_DOCS_INDEX), `${docsIndex.content}\n`);
401
+ writeText(docsIndexPath, `${docsIndex.content}\n`);
344
402
  writeText(path.join(projectRoot, DEFAULT_TRACKS_INDEX), `${tracksIndex.content}\n`);
345
403
  return [
346
404
  "Initialized Waypoint scaffold",
@@ -475,27 +533,33 @@ export function doctorRepository(projectRoot) {
475
533
  }
476
534
  }
477
535
  const docsIndexPath = path.join(projectRoot, config.docs_index_file ?? DEFAULT_DOCS_INDEX);
478
- const docsDir = path.join(projectRoot, config.docs_dir ?? DEFAULT_DOCS_DIR);
479
- const plansDir = path.join(projectRoot, config.plans_dir ?? DEFAULT_PLANS_DIR);
536
+ const configuredDocsDirs = docsRootDirs(projectRoot, config);
537
+ const configuredPlansDirs = plansRootDirs(projectRoot, config);
480
538
  const docsIndex = renderDocsIndex(projectRoot, docsIndexSections(projectRoot, config));
481
539
  const trackDir = path.join(projectRoot, DEFAULT_TRACK_DIR);
482
540
  const tracksIndexPath = path.join(projectRoot, DEFAULT_TRACKS_INDEX);
483
541
  const tracksIndex = renderTracksIndex(projectRoot, trackDir);
484
- if (!existsSync(docsDir)) {
542
+ for (const docsDir of configuredDocsDirs) {
543
+ if (existsSync(docsDir)) {
544
+ continue;
545
+ }
485
546
  findings.push({
486
547
  severity: "error",
487
548
  category: "docs",
488
- message: ".waypoint/docs/ directory is missing.",
489
- 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`.",
490
551
  paths: [docsDir],
491
552
  });
492
553
  }
493
- if (!existsSync(plansDir)) {
554
+ for (const plansDir of configuredPlansDirs) {
555
+ if (existsSync(plansDir)) {
556
+ continue;
557
+ }
494
558
  findings.push({
495
559
  severity: "error",
496
560
  category: "docs",
497
- message: ".waypoint/plans/ directory is missing.",
498
- 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`.",
499
563
  paths: [plansDir],
500
564
  });
501
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.2",
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",
@@ -30,6 +30,7 @@
30
30
  .waypoint/scripts/
31
31
  .waypoint/state/
32
32
  .waypoint/track/
33
+ .waypoint/plans/
33
34
  .waypoint/docs/README.md
34
35
  .waypoint/docs/code-guide.md
35
36
  # End Waypoint state
@@ -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]