@open-agent-toolkit/cli 0.3.10 → 0.3.12

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.
Files changed (163) hide show
  1. package/README.md +2 -1
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/cli-utilities/meta.json +13 -0
  4. package/assets/docs/cli-utilities/tool-packs.md +8 -2
  5. package/assets/docs/cli-utilities/workflow-gates.md +13 -0
  6. package/assets/docs/contributing/code.md +8 -2
  7. package/assets/docs/contributing/documentation.md +7 -1
  8. package/assets/docs/contributing/meta.json +15 -0
  9. package/assets/docs/docs-tooling/add-docs-to-a-repo.md +14 -5
  10. package/assets/docs/docs-tooling/commands.md +26 -8
  11. package/assets/docs/docs-tooling/meta.json +4 -0
  12. package/assets/docs/docs-tooling/workflows.md +2 -2
  13. package/assets/docs/guide/meta.json +4 -0
  14. package/assets/docs/meta.json +14 -0
  15. package/assets/docs/provider-sync/instruction-sync.md +8 -0
  16. package/assets/docs/provider-sync/meta.json +11 -0
  17. package/assets/docs/provider-sync/scope-and-surface.md +3 -4
  18. package/assets/docs/reference/cli-reference.md +5 -2
  19. package/assets/docs/reference/docs-index-contract.md +23 -6
  20. package/assets/docs/reference/file-locations.md +7 -2
  21. package/assets/docs/reference/index.md +1 -1
  22. package/assets/docs/reference/meta.json +10 -0
  23. package/assets/docs/reference/troubleshooting.md +41 -6
  24. package/assets/docs/workflows/ideas/meta.json +4 -0
  25. package/assets/docs/workflows/meta.json +4 -0
  26. package/assets/docs/workflows/projects/cursor-cloud.md +8 -3
  27. package/assets/docs/workflows/projects/evidence-layers.md +6 -8
  28. package/assets/docs/workflows/projects/implementation-execution.md +30 -4
  29. package/assets/docs/workflows/projects/lifecycle.md +7 -1
  30. package/assets/docs/workflows/projects/meta.json +25 -0
  31. package/assets/docs/workflows/projects/orchestration-model.md +5 -6
  32. package/assets/docs/workflows/skills/meta.json +10 -0
  33. package/assets/docs/workflows/skills/recon.md +31 -3
  34. package/assets/public-package-versions.json +4 -4
  35. package/assets/skills/oat-cursor-cloud-projects/SKILL.md +30 -16
  36. package/assets/skills/oat-dispatch-subagents/SKILL.md +4 -12
  37. package/assets/skills/oat-dispatch-subagents/references/managed-claude-example.json +1 -1
  38. package/assets/skills/oat-dispatch-subagents/references/provider-codex.md +51 -0
  39. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +8 -7
  40. package/assets/skills/oat-docs-analyze/SKILL.md +28 -3
  41. package/assets/skills/oat-docs-apply/SKILL.md +3 -3
  42. package/assets/skills/oat-docs-authoring/SKILL.md +1 -1
  43. package/assets/skills/oat-docs-authoring/references/oat-fumadocs-contract.md +5 -2
  44. package/assets/skills/oat-docs-bootstrap/SKILL.md +4 -3
  45. package/assets/skills/oat-docs-bootstrap/assets/AGENTS.md.template +3 -3
  46. package/assets/skills/oat-pjm-decision/SKILL.md +3 -2
  47. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +4 -4
  48. package/assets/skills/oat-project-complete/SKILL.md +47 -2
  49. package/assets/skills/oat-project-design/SKILL.md +10 -6
  50. package/assets/skills/oat-project-discover/SKILL.md +3 -3
  51. package/assets/skills/oat-project-dispatch-subagents/SKILL.md +3 -10
  52. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +4 -4
  53. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  54. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +135 -3
  55. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +7 -9
  56. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +4 -4
  57. package/assets/skills/oat-project-implement/references/plan-and-resume.md +1 -1
  58. package/assets/skills/oat-project-import-plan/SKILL.md +3 -3
  59. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +4 -4
  60. package/assets/skills/oat-project-new/SKILL.md +3 -3
  61. package/assets/skills/oat-project-next/SKILL.md +50 -7
  62. package/assets/skills/oat-project-plan/SKILL.md +4 -4
  63. package/assets/skills/oat-project-plan-writing/SKILL.md +2 -2
  64. package/assets/skills/oat-project-pr-final/SKILL.md +11 -1
  65. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +4 -4
  66. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +2 -2
  67. package/assets/skills/oat-project-quick-start/SKILL.md +12 -8
  68. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +4 -4
  69. package/assets/skills/oat-project-retro/SKILL.md +6 -3
  70. package/assets/skills/oat-project-review-provide/SKILL.md +2 -2
  71. package/assets/skills/oat-project-review-provide-remote/SKILL.md +2 -2
  72. package/assets/skills/oat-project-spec/SKILL.md +3 -3
  73. package/assets/skills/oat-project-summary/SKILL.md +22 -3
  74. package/assets/skills/oat-repo-improve/SKILL.md +2 -2
  75. package/assets/skills/recon/SKILL.md +24 -3
  76. package/assets/skills/recon/references/packet-contract.md +36 -6
  77. package/assets/skills/recon/references/profiles.md +3 -0
  78. package/assets/skills/recon/references/worker-contract.md +16 -3
  79. package/assets/skills/recon/scripts/create-review-brief.mjs +17 -86
  80. package/assets/skills/recon/scripts/lib/contracts.mjs +158 -41
  81. package/assets/skills/recon/scripts/lib/review-binding.mjs +123 -0
  82. package/assets/skills/recon/scripts/lib/routing.mjs +5 -2
  83. package/assets/skills/recon/scripts/reconcile-ledger.mjs +18 -7
  84. package/assets/skills/recon/scripts/render-packet.mjs +120 -1
  85. package/assets/skills/recon/scripts/validate-packet.mjs +119 -88
  86. package/assets/templates/state.md +1 -0
  87. package/dist/commands/backlog/new.d.ts +2 -2
  88. package/dist/commands/backlog/new.d.ts.map +1 -1
  89. package/dist/commands/backlog/new.js +2 -2
  90. package/dist/commands/decision/new.d.ts +2 -2
  91. package/dist/commands/decision/new.d.ts.map +1 -1
  92. package/dist/commands/decision/new.js +2 -2
  93. package/dist/commands/docs/nav/contents.d.ts +9 -0
  94. package/dist/commands/docs/nav/contents.d.ts.map +1 -1
  95. package/dist/commands/docs/nav/contents.js +7 -2
  96. package/dist/commands/docs/nav/fumadocs.d.ts +58 -0
  97. package/dist/commands/docs/nav/fumadocs.d.ts.map +1 -0
  98. package/dist/commands/docs/nav/fumadocs.js +326 -0
  99. package/dist/commands/docs/nav/sync.d.ts +31 -1
  100. package/dist/commands/docs/nav/sync.d.ts.map +1 -1
  101. package/dist/commands/docs/nav/sync.js +163 -6
  102. package/dist/commands/index.d.ts.map +1 -1
  103. package/dist/commands/index.js +2 -0
  104. package/dist/commands/instructions/instructions.utils.d.ts +19 -0
  105. package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
  106. package/dist/commands/instructions/instructions.utils.js +89 -17
  107. package/dist/commands/instructions/sync/sync.d.ts.map +1 -1
  108. package/dist/commands/instructions/sync/sync.js +49 -2
  109. package/dist/commands/pjm/init.d.ts.map +1 -1
  110. package/dist/commands/pjm/init.js +2 -2
  111. package/dist/commands/project/closeout-check/closeout-invariant.d.ts +97 -0
  112. package/dist/commands/project/closeout-check/closeout-invariant.d.ts.map +1 -0
  113. package/dist/commands/project/closeout-check/closeout-invariant.js +301 -0
  114. package/dist/commands/project/closeout-check/index.d.ts +38 -0
  115. package/dist/commands/project/closeout-check/index.d.ts.map +1 -0
  116. package/dist/commands/project/closeout-check/index.js +117 -0
  117. package/dist/commands/project/complete-state/index.d.ts +4 -0
  118. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  119. package/dist/commands/project/complete-state/index.js +11 -13
  120. package/dist/commands/project/dispatch/index.d.ts.map +1 -1
  121. package/dist/commands/project/dispatch/index.js +6 -30
  122. package/dist/commands/project/dispatch/record.d.ts +9 -18
  123. package/dist/commands/project/dispatch/record.d.ts.map +1 -1
  124. package/dist/commands/project/dispatch/record.js +15 -212
  125. package/dist/commands/project/index.d.ts.map +1 -1
  126. package/dist/commands/project/index.js +2 -0
  127. package/dist/commands/project/new/scaffold.d.ts +0 -1
  128. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  129. package/dist/commands/project/new/scaffold.js +19 -20
  130. package/dist/commands/project/promote/promote.d.ts +2 -2
  131. package/dist/commands/project/promote/promote.d.ts.map +1 -1
  132. package/dist/commands/project/promote/promote.js +13 -11
  133. package/dist/commands/shared/frontmatter.d.ts +21 -0
  134. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  135. package/dist/commands/shared/frontmatter.js +70 -4
  136. package/dist/commands/shared/template-source.d.ts +29 -0
  137. package/dist/commands/shared/template-source.d.ts.map +1 -0
  138. package/dist/commands/{pjm → shared}/template-source.js +24 -7
  139. package/dist/commands/state/generate.js +2 -2
  140. package/dist/commands/template/index.d.ts +10 -0
  141. package/dist/commands/template/index.d.ts.map +1 -0
  142. package/dist/commands/template/index.js +87 -0
  143. package/dist/commands/template/resolve.d.ts +31 -0
  144. package/dist/commands/template/resolve.d.ts.map +1 -0
  145. package/dist/commands/template/resolve.js +86 -0
  146. package/dist/fs/io.d.ts +0 -41
  147. package/dist/fs/io.d.ts.map +1 -1
  148. package/dist/fs/io.js +2 -414
  149. package/dist/providers/identity/absolute-paths.d.ts +4 -13
  150. package/dist/providers/identity/absolute-paths.d.ts.map +1 -1
  151. package/dist/providers/identity/absolute-paths.js +4 -17
  152. package/dist/providers/identity/generic-dispatch-record.d.ts +1 -1
  153. package/dist/providers/identity/generic-dispatch-record.js +2 -2
  154. package/dist/providers/identity/oat-dispatch-record.d.ts +4 -357
  155. package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -1
  156. package/dist/providers/identity/oat-dispatch-record.js +4 -236
  157. package/dist/release/public-package-contract.d.ts.map +1 -1
  158. package/dist/release/public-package-contract.js +12 -4
  159. package/dist/validation/skills.d.ts.map +1 -1
  160. package/dist/validation/skills.js +32 -6
  161. package/package.json +2 -2
  162. package/dist/commands/pjm/template-source.d.ts +0 -14
  163. package/dist/commands/pjm/template-source.d.ts.map +0 -1
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The `meta.json` keys nav sync owns. Every other key (`icon`, `defaultOpen`,
3
+ * `description`, ...) is left as the author wrote it.
4
+ */
5
+ export interface FumadocsMeta {
6
+ title?: string;
7
+ pages: string[];
8
+ }
9
+ export interface FumadocsMetaFile {
10
+ /** Docs-relative POSIX path of the `meta.json` file. */
11
+ path: string;
12
+ meta: FumadocsMeta;
13
+ changed: boolean;
14
+ }
15
+ export interface SyncFumadocsNavigationResult {
16
+ metaFiles: FumadocsMetaFile[];
17
+ /** Docs-relative paths of the `meta.json` files this run wrote. */
18
+ written: string[];
19
+ /**
20
+ * Docs-relative paths of the `meta.json` files that were missing or differed
21
+ * by meaning before this run. Equal to `written` unless `check` was set.
22
+ */
23
+ stale: string[];
24
+ /**
25
+ * Docs-relative paths no Contents map places in the page tree. A folder is
26
+ * reported once as `folder/` instead of once per page inside it.
27
+ */
28
+ unlisted: string[];
29
+ }
30
+ /**
31
+ * Reduce a Markdown heading to the plain text a sidebar shows: images and
32
+ * links keep their text, code spans keep their content, and emphasis and
33
+ * strikethrough markers are dropped.
34
+ */
35
+ export declare function stripInlineMarkdown(text: string): string;
36
+ /** Folder title: frontmatter `title`, else the first H1 outside code fences. */
37
+ export declare function resolveFolderTitle(markdown: string): string | undefined;
38
+ /**
39
+ * The `pages` item for a page or folder in the same directory. A name the
40
+ * builder would read as a directive (`!hidden`, `z...a`, `---`, ...) is
41
+ * written in the local-path form `./name`, which its path join resolves to
42
+ * the same file; every other name is written as is.
43
+ */
44
+ export declare function toFumadocsPageItem(name: string): string;
45
+ /**
46
+ * Write one strict `meta.json` per docs directory that has an index.md,
47
+ * derived from that index's Contents map, and report every page the
48
+ * resulting page tree leaves out.
49
+ *
50
+ * Existing files are compared by meaning (parsed and deep-equal), so a run
51
+ * with no doc changes writes nothing even after a formatter rewrites them.
52
+ */
53
+ export declare function syncFumadocsNavigation(options: {
54
+ docsRoot: string;
55
+ /** Compute and compare only; never write. */
56
+ check?: boolean;
57
+ }): Promise<SyncFumadocsNavigationResult>;
58
+ //# sourceMappingURL=fumadocs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fumadocs.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/fumadocs.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAyDD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxD;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoCvE;AA8BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAoLD;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAkExC"}
@@ -0,0 +1,326 @@
1
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { basename, extname, join, posix } from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import { getFrontmatterBlock, parseFrontmatterScalarFields, } from '../../shared/frontmatter.js';
5
+ import { fileExists } from '../../../fs/io.js';
6
+ import { parseIndexContents, resolveEntryTarget } from './contents.js';
7
+ const PAGE_EXTENSIONS = ['.md', '.mdx'];
8
+ const GROUP_FOLDER = /^\(.+\)$/;
9
+ // The `pages` item grammar of the fumadocs-core 16.10.2 page-tree builder
10
+ // (`resolveFolderItem`): exact rest entries, separators, links, and the
11
+ // exclude and extract prefixes are checked before an item is read as a path.
12
+ const FUMADOCS_REST_ITEMS = new Set(['...', 'z...a']);
13
+ const FUMADOCS_SEPARATOR = /^---(?:\[(?<icon>[^\]]+)])?(?<name>.+)---|^---$/;
14
+ const FUMADOCS_LINK = /^(?<external>external:)?(?:\[(?<icon>[^\]]+)])?\[(?<name>[^\]]+)]\((?<url>[^)]+)\)$/;
15
+ const FUMADOCS_PREFIXES = ['!', '...'];
16
+ const H1 = /^#\s+(.+?)\s*#*\s*$/;
17
+ const FENCE = /^\s*(```|~~~)/;
18
+ function parentDir(path) {
19
+ return posix.dirname(path);
20
+ }
21
+ async function inventoryDocs(docsRoot) {
22
+ const directories = [];
23
+ const pages = [];
24
+ async function walk(relativeDir) {
25
+ directories.push(relativeDir);
26
+ const entries = await readdir(join(docsRoot, relativeDir), {
27
+ withFileTypes: true,
28
+ });
29
+ for (const entry of entries) {
30
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
31
+ continue;
32
+ }
33
+ const entryPath = posix.join(relativeDir, entry.name);
34
+ if (entry.isDirectory()) {
35
+ await walk(entryPath);
36
+ }
37
+ else if (entry.isFile() &&
38
+ PAGE_EXTENSIONS.includes(extname(entry.name))) {
39
+ pages.push(entryPath);
40
+ }
41
+ }
42
+ }
43
+ await walk('.');
44
+ directories.sort();
45
+ pages.sort();
46
+ return { directories, pages };
47
+ }
48
+ /**
49
+ * Reduce a Markdown heading to the plain text a sidebar shows: images and
50
+ * links keep their text, code spans keep their content, and emphasis and
51
+ * strikethrough markers are dropped.
52
+ */
53
+ export function stripInlineMarkdown(text) {
54
+ return text
55
+ .replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
56
+ .replace(/(`+)(.+?)\1/g, '$2')
57
+ .replace(/(\*\*|__|~~)(.+?)\1/g, '$2')
58
+ .replace(/(^|[^\w*])[*_]([^*_]+?)[*_](?=[^\w*]|$)/g, '$1$2')
59
+ .replace(/\s+/g, ' ')
60
+ .trim();
61
+ }
62
+ /** Folder title: frontmatter `title`, else the first H1 outside code fences. */
63
+ export function resolveFolderTitle(markdown) {
64
+ const frontmatter = getFrontmatterBlock(markdown);
65
+ if (frontmatter !== null) {
66
+ const title = parseFrontmatterScalarFields(frontmatter, ['title']).values
67
+ .title;
68
+ if (title) {
69
+ return title;
70
+ }
71
+ }
72
+ const body = frontmatter === null
73
+ ? markdown
74
+ : markdown.slice(markdown.indexOf('\n---', 4) + 4);
75
+ let fence = null;
76
+ for (const line of body.split(/\r?\n/)) {
77
+ const fenceMatch = line.match(FENCE);
78
+ if (fenceMatch) {
79
+ const marker = fenceMatch[1];
80
+ if (fence === null) {
81
+ fence = marker;
82
+ }
83
+ else if (fence === marker) {
84
+ fence = null;
85
+ }
86
+ continue;
87
+ }
88
+ if (fence !== null) {
89
+ continue;
90
+ }
91
+ const heading = line.match(H1);
92
+ if (heading?.[1]) {
93
+ return stripInlineMarkdown(heading[1]) || undefined;
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ /**
99
+ * The URL Fumadocs assigns a page, following `getSlugs` in fumadocs-core's
100
+ * slugs plugin with the scaffolded `baseUrl: '/'`: group folders are dropped,
101
+ * segments are URI-encoded, and an `index` file maps to its folder.
102
+ */
103
+ function pageUrl(pagePath) {
104
+ const slugs = parentDir(pagePath)
105
+ .split('/')
106
+ .filter((segment) => segment.length > 0 && segment !== '.')
107
+ .filter((segment) => !GROUP_FOLDER.test(segment))
108
+ .map((segment) => encodeURI(segment));
109
+ const name = basename(pagePath, extname(pagePath));
110
+ if (name !== 'index') {
111
+ slugs.push(encodeURI(name));
112
+ }
113
+ return `/${slugs.join('/')}`;
114
+ }
115
+ /** Whether the Fumadocs page-tree builder reads `item` as a directive, not a path. */
116
+ function isFumadocsDirective(item) {
117
+ return (FUMADOCS_REST_ITEMS.has(item) ||
118
+ FUMADOCS_SEPARATOR.test(item) ||
119
+ FUMADOCS_LINK.test(item) ||
120
+ FUMADOCS_PREFIXES.some((prefix) => item.startsWith(prefix)));
121
+ }
122
+ /**
123
+ * The `pages` item for a page or folder in the same directory. A name the
124
+ * builder would read as a directive (`!hidden`, `z...a`, `---`, ...) is
125
+ * written in the local-path form `./name`, which its path join resolves to
126
+ * the same file; every other name is written as is.
127
+ */
128
+ export function toFumadocsPageItem(name) {
129
+ return isFumadocsDirective(name) ? `./${name}` : name;
130
+ }
131
+ function hrefFragment(href) {
132
+ const hashIndex = href.indexOf('#');
133
+ return hashIndex >= 0 ? href.slice(hashIndex) : '';
134
+ }
135
+ /**
136
+ * Build one folder's `pages` from its index.md Contents map.
137
+ *
138
+ * A page in this folder or an immediate subfolder is referenced by name. Any
139
+ * other target becomes a Fumadocs link entry, so no page is claimed by two
140
+ * folders. There is never a `"..."` rest entry: pages the Contents map does
141
+ * not list stay out of the tree and are reported instead.
142
+ */
143
+ /**
144
+ * Whether the fumadocs-core 16.10.2 page-tree builder supplies this folder's
145
+ * `index` page on its own, given the folder's effective metadata.
146
+ *
147
+ * `buildFolder` attaches `<folder>/index` as the folder index only when the
148
+ * folder is not a root folder (the docs root, or a preserved `root: true`)
149
+ * and no `pagesIndex` names a different page. Otherwise the page is shown
150
+ * only if `pages` lists it.
151
+ */
152
+ function hasImplicitIndex(relativeDir, existing) {
153
+ if (relativeDir === '.' || existing?.root === true) {
154
+ return false;
155
+ }
156
+ const pagesIndex = existing?.pagesIndex;
157
+ return pagesIndex === undefined || pagesIndex === 'index';
158
+ }
159
+ async function buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex) {
160
+ const indexPath = join(docsRoot, relativeDir, 'index.md');
161
+ const entries = parseIndexContents(markdown, indexPath);
162
+ // A folder the loader gives no implicit index (the docs root, a `root: true`
163
+ // folder) must list `index` to show its landing page. Elsewhere, listing
164
+ // `index` would demote the folder index to an ordinary child, so it is left
165
+ // implicit.
166
+ const pages = implicitIndex ? [] : ['index'];
167
+ for (const entry of entries) {
168
+ const target = await resolveEntryTarget(docsRoot, join(docsRoot, relativeDir), relativeDir, entry, PAGE_EXTENSIONS);
169
+ const targetDir = parentDir(target.path);
170
+ let item;
171
+ if (target.kind === 'section' && targetDir === relativeDir) {
172
+ continue;
173
+ }
174
+ else if (target.kind === 'section' &&
175
+ parentDir(targetDir) === relativeDir) {
176
+ item = toFumadocsPageItem(basename(targetDir));
177
+ }
178
+ else if (target.kind === 'page' && targetDir === relativeDir) {
179
+ item = toFumadocsPageItem(basename(target.path, extname(target.path)));
180
+ }
181
+ else {
182
+ item = `[${entry.title}](${pageUrl(target.path)}${hrefFragment(entry.href)})`;
183
+ }
184
+ if (!pages.includes(item)) {
185
+ pages.push(item);
186
+ }
187
+ }
188
+ return pages;
189
+ }
190
+ function isPlainObject(value) {
191
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
192
+ }
193
+ async function readExistingMeta(metaPath) {
194
+ if (!(await fileExists(metaPath))) {
195
+ return null;
196
+ }
197
+ try {
198
+ const parsed = JSON.parse(await readFile(metaPath, 'utf8'));
199
+ return isPlainObject(parsed) ? parsed : null;
200
+ }
201
+ catch {
202
+ // A generated file that no longer parses (for example after a merge
203
+ // conflict) is regenerated rather than preserved.
204
+ return null;
205
+ }
206
+ }
207
+ function mergeMeta(existing, meta) {
208
+ const { title: _title, pages: _pages, ...unowned } = existing ?? {};
209
+ return {
210
+ ...unowned,
211
+ ...(meta.title === undefined ? {} : { title: meta.title }),
212
+ pages: meta.pages,
213
+ };
214
+ }
215
+ function collectUnlisted(inventory, metas, implicitIndexDirs) {
216
+ const reachableDirs = new Set();
217
+ const reachablePages = new Set();
218
+ const pagesByFlattenPath = new Map(inventory.pages.map((page) => [
219
+ page.slice(0, page.length - extname(page).length),
220
+ page,
221
+ ]));
222
+ function visit(relativeDir) {
223
+ reachableDirs.add(relativeDir);
224
+ const meta = metas.get(relativeDir);
225
+ if (!meta) {
226
+ return;
227
+ }
228
+ // A landing page is reachable only when the loader attaches it as the
229
+ // folder index or `pages` lists it, never just because the file exists.
230
+ const index = pagesByFlattenPath.get(posix.join(relativeDir, 'index'));
231
+ if (index && implicitIndexDirs.has(relativeDir)) {
232
+ reachablePages.add(index);
233
+ }
234
+ for (const item of meta.pages) {
235
+ // Read items the way the loader does: a directive (including a link
236
+ // entry) names no local page, and `./name` joins to `name`.
237
+ if (isFumadocsDirective(item)) {
238
+ continue;
239
+ }
240
+ const itemPath = posix.join(relativeDir, item);
241
+ if (metas.has(itemPath) && !reachableDirs.has(itemPath)) {
242
+ visit(itemPath);
243
+ continue;
244
+ }
245
+ const page = pagesByFlattenPath.get(itemPath);
246
+ if (page) {
247
+ reachablePages.add(page);
248
+ }
249
+ }
250
+ }
251
+ visit('.');
252
+ const unlisted = new Set();
253
+ for (const page of inventory.pages) {
254
+ if (reachablePages.has(page)) {
255
+ continue;
256
+ }
257
+ let topmostHiddenDir = null;
258
+ for (let dir = parentDir(page); dir !== '.' && dir.length > 0; dir = parentDir(dir)) {
259
+ if (!reachableDirs.has(dir)) {
260
+ topmostHiddenDir = dir;
261
+ }
262
+ }
263
+ unlisted.add(topmostHiddenDir === null ? page : `${topmostHiddenDir}/`);
264
+ }
265
+ return [...unlisted].sort();
266
+ }
267
+ /**
268
+ * Write one strict `meta.json` per docs directory that has an index.md,
269
+ * derived from that index's Contents map, and report every page the
270
+ * resulting page tree leaves out.
271
+ *
272
+ * Existing files are compared by meaning (parsed and deep-equal), so a run
273
+ * with no doc changes writes nothing even after a formatter rewrites them.
274
+ */
275
+ export async function syncFumadocsNavigation(options) {
276
+ const { docsRoot } = options;
277
+ const inventory = await inventoryDocs(docsRoot);
278
+ const metas = new Map();
279
+ const existingMetas = new Map();
280
+ const implicitIndexDirs = new Set();
281
+ for (const relativeDir of inventory.directories) {
282
+ const indexPath = join(docsRoot, relativeDir, 'index.md');
283
+ if (!(await fileExists(indexPath))) {
284
+ continue;
285
+ }
286
+ // Index handling depends on the effective metadata, which keeps the
287
+ // author-owned `root` and `pagesIndex` keys of an existing meta.json.
288
+ const existing = await readExistingMeta(join(docsRoot, relativeDir, 'meta.json'));
289
+ existingMetas.set(relativeDir, existing);
290
+ const implicitIndex = hasImplicitIndex(relativeDir, existing);
291
+ if (implicitIndex) {
292
+ implicitIndexDirs.add(relativeDir);
293
+ }
294
+ const markdown = await readFile(indexPath, 'utf8');
295
+ const title = resolveFolderTitle(markdown);
296
+ metas.set(relativeDir, {
297
+ ...(title === undefined ? {} : { title }),
298
+ pages: await buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex),
299
+ });
300
+ }
301
+ if (!metas.has('.')) {
302
+ throw new Error(`Missing required index.md at ${join(docsRoot, 'index.md')}`);
303
+ }
304
+ const metaFiles = [];
305
+ for (const [relativeDir, meta] of metas) {
306
+ const relativePath = posix.join(relativeDir, 'meta.json');
307
+ const metaPath = join(docsRoot, relativePath);
308
+ const existing = existingMetas.get(relativeDir) ?? null;
309
+ const next = mergeMeta(existing, meta);
310
+ const changed = existing === null || !isDeepStrictEqual(existing, next);
311
+ if (changed && !options.check) {
312
+ await writeFile(metaPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
313
+ }
314
+ metaFiles.push({ path: relativePath, meta, changed });
315
+ }
316
+ metaFiles.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
317
+ const stale = metaFiles
318
+ .filter((file) => file.changed)
319
+ .map((file) => file.path);
320
+ return {
321
+ metaFiles,
322
+ written: options.check ? [] : stale,
323
+ stale,
324
+ unlisted: collectUnlisted(inventory, metas, implicitIndexDirs),
325
+ };
326
+ }
@@ -1,19 +1,49 @@
1
1
  import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
2
  import { Command } from 'commander';
3
3
  import { type DocsNavTree } from './contents.js';
4
+ import { type FumadocsMetaFile } from './fumadocs.js';
5
+ export type DocsNavFramework = 'fumadocs' | 'mkdocs';
4
6
  interface SyncDocsNavigationOptions {
5
7
  appRoot: string;
8
+ /**
9
+ * `documentation.tooling` from `.oat/config.json` when the app is the
10
+ * configured docs root. Used only when no framework marker file is found.
11
+ */
12
+ configuredTooling?: string | null;
13
+ /** Compute the navigation and compare it with the files; write nothing. */
14
+ check?: boolean;
6
15
  }
7
- interface SyncDocsNavigationResult {
16
+ interface MkDocsNavigationResult {
17
+ framework: 'mkdocs';
8
18
  appRoot: string;
9
19
  docsRoot: string;
10
20
  mkdocsPath: string;
11
21
  nav: DocsNavTree;
22
+ /** `['mkdocs.yml']` when its `nav:` block differed before this run. */
23
+ stale: string[];
12
24
  }
25
+ interface FumadocsNavigationResult {
26
+ framework: 'fumadocs';
27
+ appRoot: string;
28
+ docsRoot: string;
29
+ metaFiles: FumadocsMetaFile[];
30
+ written: string[];
31
+ stale: string[];
32
+ unlisted: string[];
33
+ }
34
+ type SyncDocsNavigationResult = MkDocsNavigationResult | FumadocsNavigationResult;
13
35
  interface DocsNavSyncDependencies {
14
36
  buildCommandContext: (options: GlobalOptions) => CommandContext;
37
+ resolveConfiguredTooling: (cwd: string, appRoot: string) => Promise<string | null>;
15
38
  syncDocsNavigation: (options: SyncDocsNavigationOptions) => Promise<SyncDocsNavigationResult>;
16
39
  }
40
+ /**
41
+ * Detect the docs framework from the app's own files: `mkdocs.yml` keeps the
42
+ * MkDocs path (checked first, so existing MkDocs apps behave as before) and a
43
+ * Fumadocs `source.config.*` selects `meta.json` output. Without either
44
+ * marker, a configured `documentation.tooling` decides.
45
+ */
46
+ export declare function detectDocsNavFramework(appRoot: string, configuredTooling?: string | null): Promise<DocsNavFramework>;
17
47
  export declare function syncDocsNavigation(options: SyncDocsNavigationOptions): Promise<SyncDocsNavigationResult>;
18
48
  export declare function createDocsNavSyncCommand(overrides?: Partial<DocsNavSyncDependencies>): Command;
19
49
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/sync.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAMhE,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,wBAAwB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAClB,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACxC;AAmCD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAmBnC;AAuCD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAoBT"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/sync.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAOrD,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,sBAAsB;IAC9B,SAAS,EAAE,QAAQ,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,UAAU,wBAAwB;IAChC,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,KAAK,wBAAwB,GACzB,sBAAsB,GACtB,wBAAwB,CAAC;AAE7B,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,wBAAwB,EAAE,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,kBAAkB,EAAE,CAClB,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACxC;AAsBD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAe3B;AAqDD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CA0CnC;AAmJD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CA4BT"}
@@ -2,11 +2,65 @@ import { readFile, writeFile } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { buildCommandContext, } from '../../../app/command-context.js';
4
4
  import { readGlobalOptions } from '../../shared/shared.utils.js';
5
+ import { readOatConfig } from '../../../config/oat-config.js';
6
+ import { fileExists } from '../../../fs/io.js';
7
+ import { resolveProjectRoot } from '../../../fs/paths.js';
5
8
  import { Command, Option } from 'commander';
6
9
  import YAML from 'yaml';
7
10
  import { buildDocsNavTree } from './contents.js';
11
+ import { syncFumadocsNavigation } from './fumadocs.js';
12
+ const MKDOCS_MARKERS = ['mkdocs.yml'];
13
+ const FUMADOCS_MARKERS = [
14
+ 'source.config.ts',
15
+ 'source.config.mts',
16
+ 'source.config.js',
17
+ 'source.config.mjs',
18
+ ];
19
+ async function hasAnyMarker(appRoot, markers) {
20
+ for (const marker of markers) {
21
+ if (await fileExists(join(appRoot, marker))) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ /**
28
+ * Detect the docs framework from the app's own files: `mkdocs.yml` keeps the
29
+ * MkDocs path (checked first, so existing MkDocs apps behave as before) and a
30
+ * Fumadocs `source.config.*` selects `meta.json` output. Without either
31
+ * marker, a configured `documentation.tooling` decides.
32
+ */
33
+ export async function detectDocsNavFramework(appRoot, configuredTooling) {
34
+ if (await hasAnyMarker(appRoot, MKDOCS_MARKERS)) {
35
+ return 'mkdocs';
36
+ }
37
+ if (await hasAnyMarker(appRoot, FUMADOCS_MARKERS)) {
38
+ return 'fumadocs';
39
+ }
40
+ const tooling = configuredTooling?.trim().toLowerCase();
41
+ if (tooling === 'mkdocs' || tooling === 'fumadocs') {
42
+ return tooling;
43
+ }
44
+ throw new Error(`Could not detect the docs framework in ${appRoot}: expected mkdocs.yml (MkDocs) ` +
45
+ 'or source.config.ts (Fumadocs).');
46
+ }
47
+ async function resolveConfiguredTooling(cwd, appRoot) {
48
+ try {
49
+ const repoRoot = await resolveProjectRoot(cwd);
50
+ const config = await readOatConfig(repoRoot);
51
+ const root = config.documentation?.root?.trim();
52
+ if (!root || resolve(repoRoot, root) !== appRoot) {
53
+ return null;
54
+ }
55
+ return config.documentation?.tooling?.trim() || null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
8
61
  const DEFAULT_DEPENDENCIES = {
9
62
  buildCommandContext,
63
+ resolveConfiguredTooling,
10
64
  syncDocsNavigation,
11
65
  };
12
66
  function replaceTopLevelYamlSection(source, key, replacement) {
@@ -30,27 +84,129 @@ function replaceTopLevelYamlSection(source, key, replacement) {
30
84
  ].join('\n');
31
85
  }
32
86
  export async function syncDocsNavigation(options) {
33
- const mkdocsPath = join(options.appRoot, 'mkdocs.yml');
34
87
  const docsRoot = join(options.appRoot, 'docs');
88
+ const framework = await detectDocsNavFramework(options.appRoot, options.configuredTooling);
89
+ if (framework === 'fumadocs') {
90
+ const result = await syncFumadocsNavigation({
91
+ docsRoot,
92
+ check: options.check,
93
+ });
94
+ return {
95
+ framework,
96
+ appRoot: options.appRoot,
97
+ docsRoot,
98
+ ...result,
99
+ };
100
+ }
101
+ const mkdocsPath = join(options.appRoot, 'mkdocs.yml');
35
102
  const nav = await buildDocsNavTree({ docsRoot });
36
103
  const mkdocsSource = await readFile(mkdocsPath, 'utf8');
37
104
  const navSection = YAML.stringify({ nav }).trimEnd();
38
105
  const updatedMkdocsSource = replaceTopLevelYamlSection(mkdocsSource, 'nav', navSection);
39
- await writeFile(mkdocsPath, `${updatedMkdocsSource.trimEnd()}\n`, 'utf8');
106
+ const nextMkdocsSource = `${updatedMkdocsSource.trimEnd()}\n`;
107
+ if (!options.check) {
108
+ await writeFile(mkdocsPath, nextMkdocsSource, 'utf8');
109
+ }
40
110
  return {
111
+ framework,
41
112
  appRoot: options.appRoot,
42
113
  docsRoot,
43
114
  mkdocsPath,
44
115
  nav,
116
+ stale: nextMkdocsSource === mkdocsSource ? [] : ['mkdocs.yml'],
45
117
  };
46
118
  }
119
+ function reportFumadocsResult(context, targetDir, result) {
120
+ if (context.json) {
121
+ context.logger.json({
122
+ status: 'ok',
123
+ framework: result.framework,
124
+ appRoot: result.appRoot,
125
+ docsRoot: result.docsRoot,
126
+ metaFiles: result.metaFiles,
127
+ written: result.written,
128
+ unlisted: result.unlisted,
129
+ });
130
+ return;
131
+ }
132
+ context.logger.info(`Synced docs navigation in ${targetDir}`);
133
+ context.logger.info(' Framework: Fumadocs (meta.json)');
134
+ context.logger.info(` Docs root: ${result.docsRoot}`);
135
+ if (result.written.length === 0) {
136
+ context.logger.info(' No meta.json changes');
137
+ }
138
+ else {
139
+ context.logger.info(` Wrote ${result.written.length} meta.json file(s):`);
140
+ for (const path of result.written) {
141
+ context.logger.info(` ${path}`);
142
+ }
143
+ }
144
+ if (result.unlisted.length > 0) {
145
+ context.logger.warn(` ${result.unlisted.length} path(s) are not listed in any index.md Contents map ` +
146
+ 'and stay out of the navigation:');
147
+ for (const path of result.unlisted) {
148
+ context.logger.warn(` ${path}`);
149
+ }
150
+ }
151
+ }
152
+ /**
153
+ * `--check`: report drift without writing. Fails (exit 1) when any generated
154
+ * navigation file would change or, for Fumadocs, any page or folder is
155
+ * unlisted, since strict navigation hides it from the sidebar.
156
+ */
157
+ function reportCheckResult(context, targetDir, result) {
158
+ const unlisted = result.framework === 'fumadocs' ? result.unlisted : [];
159
+ const current = result.stale.length === 0 && unlisted.length === 0;
160
+ if (context.json) {
161
+ context.logger.json({
162
+ status: current ? 'ok' : 'drift',
163
+ check: true,
164
+ framework: result.framework,
165
+ appRoot: result.appRoot,
166
+ docsRoot: result.docsRoot,
167
+ stale: result.stale,
168
+ unlisted,
169
+ });
170
+ return current;
171
+ }
172
+ if (current) {
173
+ context.logger.info(`Docs navigation is up to date in ${targetDir}`);
174
+ return current;
175
+ }
176
+ context.logger.error(`Docs navigation is out of date in ${targetDir}`);
177
+ if (result.stale.length > 0) {
178
+ context.logger.error(` ${result.stale.length} generated file(s) differ from the index.md Contents maps:`);
179
+ for (const path of result.stale) {
180
+ context.logger.error(` ${path}`);
181
+ }
182
+ }
183
+ if (unlisted.length > 0) {
184
+ context.logger.error(` ${unlisted.length} path(s) are not listed in any index.md Contents map ` +
185
+ 'and would be hidden from the sidebar:');
186
+ for (const path of unlisted) {
187
+ context.logger.error(` ${path}`);
188
+ }
189
+ }
190
+ context.logger.error(` List every page in its directory's ## Contents, then run \`oat docs nav sync --target-dir ${targetDir}\`.`);
191
+ return current;
192
+ }
47
193
  async function runDocsNavSyncCommand(context, options, dependencies) {
48
194
  try {
49
195
  const targetDir = options.targetDir ?? '.';
196
+ const appRoot = resolve(context.cwd, targetDir);
50
197
  const result = await dependencies.syncDocsNavigation({
51
- appRoot: resolve(context.cwd, targetDir),
198
+ appRoot,
199
+ configuredTooling: await dependencies.resolveConfiguredTooling(context.cwd, appRoot),
200
+ check: options.check === true,
52
201
  });
53
- if (context.json) {
202
+ if (options.check) {
203
+ process.exitCode = reportCheckResult(context, targetDir, result) ? 0 : 1;
204
+ return;
205
+ }
206
+ if (result.framework === 'fumadocs') {
207
+ reportFumadocsResult(context, targetDir, result);
208
+ }
209
+ else if (context.json) {
54
210
  context.logger.json({
55
211
  status: 'ok',
56
212
  appRoot: result.appRoot,
@@ -83,8 +239,9 @@ export function createDocsNavSyncCommand(overrides = {}) {
83
239
  ...overrides,
84
240
  };
85
241
  return new Command('sync')
86
- .description('Regenerate docs navigation from index.md contents')
87
- .addOption(new Option('--target-dir <path>', 'Docs app directory containing mkdocs.yml'))
242
+ .description('Regenerate docs navigation from index.md contents: mkdocs.yml nav (MkDocs) or strict meta.json files (Fumadocs)')
243
+ .addOption(new Option('--target-dir <path>', 'Docs app directory containing mkdocs.yml or source.config.ts'))
244
+ .addOption(new Option('--check', 'Write nothing; exit 1 if any navigation file is stale or any page is unlisted'))
88
245
  .action(async (options, command) => {
89
246
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
90
247
  await runDocsNavSyncCommand(context, options, dependencies);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBzC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0BvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BzC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2BvD"}
@@ -20,6 +20,7 @@ import { createReviewCommand } from './review/index.js';
20
20
  import { createStateCommand } from './state/index.js';
21
21
  import { createStatusCommand } from './status/index.js';
22
22
  import { createSyncCommand } from './sync/index.js';
23
+ import { createTemplateCommand } from './template/index.js';
23
24
  import { createToolsCommand } from './tools/index.js';
24
25
  export function registerCommands(program) {
25
26
  program.addCommand(createBacklogCommand());
@@ -42,6 +43,7 @@ export function registerCommands(program) {
42
43
  program.addCommand(createPjmCommand());
43
44
  program.addCommand(createProjectCommand());
44
45
  program.addCommand(createStateCommand());
46
+ program.addCommand(createTemplateCommand());
45
47
  program.addCommand(createToolsCommand());
46
48
  program.addCommand(createInternalCommand());
47
49
  // Commander's .addCommand() does not inherit help config from the parent.