@open-agent-toolkit/cli 0.2.54 → 0.2.56
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +12 -2
- package/assets/docs/docs-tooling/commands.md +36 -5
- package/assets/docs/reference/oat-directory-structure.md +56 -4
- package/assets/docs/workflows/skills/recon.md +28 -12
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-dispatch-subagents/SKILL.md +4 -105
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +0 -226
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/recon/SKILL.md +76 -38
- package/assets/skills/recon/references/packet-contract.md +138 -105
- package/assets/skills/recon/scripts/lib/contracts.mjs +181 -674
- package/assets/skills/recon/scripts/lib/validated-run.mjs +10 -16
- package/assets/skills/recon/scripts/render-packet.mjs +7 -20
- package/assets/skills/recon/scripts/validate-packet.mjs +177 -344
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +51 -4
- package/dist/commands/docs/index-generate/generator.d.ts +43 -1
- package/dist/commands/docs/index-generate/generator.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/generator.js +169 -3
- package/dist/commands/docs/index-generate/index.d.ts +8 -3
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +340 -24
- package/dist/commands/docs/init/scaffold.d.ts +11 -1
- package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
- package/dist/commands/docs/init/scaffold.js +11 -2
- package/dist/config/oat-config.d.ts +14 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +61 -0
- package/dist/fs/assets.d.ts +33 -4
- package/dist/fs/assets.d.ts.map +1 -1
- package/dist/fs/assets.js +127 -12
- package/package.json +2 -2
|
@@ -10,6 +10,7 @@ interface ConfigCommandDependencies {
|
|
|
10
10
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
11
11
|
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
12
12
|
readOatConfigForDefaultScopeRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
13
|
+
readOatConfigForDocumentationExcludesRepair: (repoRoot: string) => Promise<OatConfig>;
|
|
13
14
|
writeOatConfig: (repoRoot: string, config: OatConfig) => Promise<void>;
|
|
14
15
|
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
15
16
|
writeOatLocalConfig: (repoRoot: string, config: OatLocalConfig) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAmBzC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAmBzC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAiBhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2GpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kCAAkC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,2CAA2C,EAAE,CAC3C,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,SAAS,CAAC,CAAC;IACxB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAunFD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAsKT"}
|
|
@@ -9,7 +9,7 @@ import { compileDispatchCeilingPreset } from '../../config/dispatch-ceiling-pres
|
|
|
9
9
|
import { normalizeDispatchMatrix, validateDispatchRouteTarget, walkDispatchMatrix, } from '../../config/dispatch-matrix.js';
|
|
10
10
|
import { formatDispatchNotices, terminalReviewerNoticesForMatrix, } from '../../config/dispatch-notices.js';
|
|
11
11
|
import { dispatchPolicyModeDescription, dispatchPolicyPolicyDescription, managedDispatchPolicyValueList, } from '../../config/dispatch-policy-options.js';
|
|
12
|
-
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
12
|
+
import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatConfigForDefaultScopeRepair, readOatConfigForDocumentationExcludesRepair, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
13
13
|
import { resolveEffectiveConfig, } from '../../config/resolve.js';
|
|
14
14
|
import { resolveAssetsRoot } from '../../fs/assets.js';
|
|
15
15
|
import { resolveProjectRoot } from '../../fs/paths.js';
|
|
@@ -38,6 +38,7 @@ const KEY_ORDER = [
|
|
|
38
38
|
'documentation.root',
|
|
39
39
|
'documentation.tooling',
|
|
40
40
|
'documentation.config',
|
|
41
|
+
'documentation.excludes',
|
|
41
42
|
'documentation.requireForProjectCompletion',
|
|
42
43
|
'explainers.defaults.style',
|
|
43
44
|
'explainers.defaults.palette',
|
|
@@ -177,6 +178,17 @@ const CONFIG_CATALOG = [
|
|
|
177
178
|
owningCommand: 'oat config set documentation.config <value>',
|
|
178
179
|
description: 'Repository-relative path to the primary documentation tool config file.',
|
|
179
180
|
},
|
|
181
|
+
{
|
|
182
|
+
key: 'documentation.excludes',
|
|
183
|
+
group: 'Shared Repo (.oat/config.json)',
|
|
184
|
+
file: '.oat/config.json',
|
|
185
|
+
scope: 'shared repo',
|
|
186
|
+
type: 'string[]',
|
|
187
|
+
defaultValue: 'unset',
|
|
188
|
+
mutability: 'read/write',
|
|
189
|
+
owningCommand: 'oat config set documentation.excludes <glob[,glob...]>',
|
|
190
|
+
description: 'Comma-separated globs, relative to the docs directory, excluded from `oat docs generate-index`. Repeated `--exclude` flags extend this list; an empty value clears the key.',
|
|
191
|
+
},
|
|
180
192
|
{
|
|
181
193
|
key: 'documentation.requireForProjectCompletion',
|
|
182
194
|
group: 'Shared Repo (.oat/config.json)',
|
|
@@ -854,6 +866,7 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
854
866
|
resolveProjectRoot,
|
|
855
867
|
readOatConfig,
|
|
856
868
|
readOatConfigForDefaultScopeRepair,
|
|
869
|
+
readOatConfigForDocumentationExcludesRepair,
|
|
857
870
|
writeOatConfig,
|
|
858
871
|
readOatLocalConfig,
|
|
859
872
|
writeOatLocalConfig,
|
|
@@ -916,6 +929,20 @@ function normalizeSharedRoot(value) {
|
|
|
916
929
|
}
|
|
917
930
|
return trimmed.replace(/\/+$/, '');
|
|
918
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Split the one array-valued config key's comma-separated grammar.
|
|
934
|
+
*
|
|
935
|
+
* Entries are trimmed and de-duplicated in order; blank entries are dropped, so
|
|
936
|
+
* an empty (or all-blank) value yields an empty list, which the caller treats
|
|
937
|
+
* as "clear the key" rather than as an error.
|
|
938
|
+
*/
|
|
939
|
+
function parseDocumentationExcludes(rawValue) {
|
|
940
|
+
const parsed = rawValue
|
|
941
|
+
.split(',')
|
|
942
|
+
.map((entry) => entry.trim())
|
|
943
|
+
.filter((entry) => entry.length > 0);
|
|
944
|
+
return [...new Set(parsed)];
|
|
945
|
+
}
|
|
919
946
|
function parseExplainerValue(key, rawValue, surface) {
|
|
920
947
|
const value = rawValue.trim();
|
|
921
948
|
if (!value) {
|
|
@@ -1633,7 +1660,12 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1633
1660
|
});
|
|
1634
1661
|
return { key, value: defaultScope, source: 'shared' };
|
|
1635
1662
|
}
|
|
1636
|
-
|
|
1663
|
+
// `documentation.excludes` is the one shared key whose own validation error
|
|
1664
|
+
// names `oat config set` as the repair, so it reads leniently: a strict read
|
|
1665
|
+
// would reject the very value the operator is trying to replace.
|
|
1666
|
+
const config = key === 'documentation.excludes'
|
|
1667
|
+
? await dependencies.readOatConfigForDocumentationExcludesRepair(repoRoot)
|
|
1668
|
+
: await dependencies.readOatConfig(repoRoot);
|
|
1637
1669
|
if (key.startsWith('documentation.')) {
|
|
1638
1670
|
const doc = { ...config.documentation };
|
|
1639
1671
|
if (key === 'documentation.root') {
|
|
@@ -1645,6 +1677,17 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1645
1677
|
else if (key === 'documentation.config') {
|
|
1646
1678
|
doc.config = normalizeSharedRoot(rawValue);
|
|
1647
1679
|
}
|
|
1680
|
+
else if (key === 'documentation.excludes') {
|
|
1681
|
+
const excludes = parseDocumentationExcludes(rawValue);
|
|
1682
|
+
if (excludes.length === 0) {
|
|
1683
|
+
// Unlike the scalar shared keys, an empty value is the documented way
|
|
1684
|
+
// to clear this list rather than an error.
|
|
1685
|
+
delete doc.excludes;
|
|
1686
|
+
}
|
|
1687
|
+
else {
|
|
1688
|
+
doc.excludes = excludes;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1648
1691
|
else if (key === 'documentation.requireForProjectCompletion') {
|
|
1649
1692
|
doc.requireForProjectCompletion =
|
|
1650
1693
|
rawValue.trim().toLowerCase() === 'true';
|
|
@@ -1655,7 +1698,9 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1655
1698
|
});
|
|
1656
1699
|
const resultValue = key === 'documentation.requireForProjectCompletion'
|
|
1657
1700
|
? String(doc.requireForProjectCompletion ?? false)
|
|
1658
|
-
:
|
|
1701
|
+
: key === 'documentation.excludes'
|
|
1702
|
+
? (doc.excludes ?? null)
|
|
1703
|
+
: (doc[key.replace('documentation.', '')] ?? null);
|
|
1659
1704
|
return {
|
|
1660
1705
|
key,
|
|
1661
1706
|
value: resultValue,
|
|
@@ -1990,7 +2035,9 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
1990
2035
|
});
|
|
1991
2036
|
}
|
|
1992
2037
|
else {
|
|
1993
|
-
|
|
2038
|
+
// `formatResolvedValue` renders an array-valued key the same way `get`
|
|
2039
|
+
// does, rather than leaning on implicit `Array.prototype.toString`.
|
|
2040
|
+
context.logger.info(`${result.key}=${formatResolvedValue(result.value) ?? ''}`);
|
|
1994
2041
|
}
|
|
1995
2042
|
process.exitCode = 0;
|
|
1996
2043
|
}
|
|
@@ -4,6 +4,48 @@ export interface IndexEntry {
|
|
|
4
4
|
path: string;
|
|
5
5
|
children?: IndexEntry[];
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export interface GenerateIndexOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Glob patterns whose matches are left out of the index. See
|
|
10
|
+
* {@link createExclusionMatcher} for the grammar; an empty or omitted list
|
|
11
|
+
* indexes everything, byte-identically to the pre-exclusion behavior.
|
|
12
|
+
*/
|
|
13
|
+
excludes?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A compiled exclusion list.
|
|
17
|
+
*
|
|
18
|
+
* Files and directories are asked separately so a trailing-slash pattern can
|
|
19
|
+
* mean "directories only" while every other pattern applies to both.
|
|
20
|
+
*/
|
|
21
|
+
export interface ExclusionMatcher {
|
|
22
|
+
excludesFile: (relativePath: string) => boolean;
|
|
23
|
+
excludesDirectory: (relativePath: string) => boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compile exclusion globs matched against **docs-root-relative POSIX paths**
|
|
27
|
+
* (`api/auth.md`, `api/nested`), never against absolute or CWD-relative ones.
|
|
28
|
+
*
|
|
29
|
+
* Grammar, defined once and documented in
|
|
30
|
+
* `apps/oat-docs/docs/reference/oat-directory-structure.md`:
|
|
31
|
+
*
|
|
32
|
+
* - `*` matches any run of characters inside a single path segment.
|
|
33
|
+
* - `**` spans `/` only as a whole segment, where it covers zero or more
|
|
34
|
+
* segments: `**\/CLAUDE.md` matches `CLAUDE.md` and `api/CLAUDE.md`. Inside a
|
|
35
|
+
* segment (`a**b`) it is an ordinary single-segment wildcard.
|
|
36
|
+
* - Patterns are anchored at both ends against the whole relative path, so a
|
|
37
|
+
* bare `CLAUDE.md` matches only the root-level file. Use `**\/CLAUDE.md` for
|
|
38
|
+
* every depth.
|
|
39
|
+
* - A trailing `/` restricts a pattern to directories: `drafts/` prunes the
|
|
40
|
+
* `drafts` directory and everything beneath it, and never matches a file.
|
|
41
|
+
* Without it, a pattern that matches a directory path prunes it too.
|
|
42
|
+
* - Matching is case-sensitive, `/` is the separator on every platform, and
|
|
43
|
+
* `*` is the only metacharacter — `.`, `+`, and friends are literal.
|
|
44
|
+
*
|
|
45
|
+
* Entries are trimmed; a leading `./` or `/` is stripped so both spellings
|
|
46
|
+
* anchor at the docs root. Blank entries are ignored.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createExclusionMatcher(patterns: readonly string[]): ExclusionMatcher;
|
|
49
|
+
export declare function generateIndex(docsDir: string, options?: GenerateIndexOptions): Promise<IndexEntry[]>;
|
|
8
50
|
export declare function renderIndex(entries: IndexEntry[]): string;
|
|
9
51
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/generator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,iBAAiB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD;AAmHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,gBAAgB,CAwClB;AAqID,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,UAAU,EAAE,CAAC,CAMvB;AAmBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAMzD"}
|
|
@@ -1,5 +1,155 @@
|
|
|
1
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
const NO_EXCLUSIONS = {
|
|
4
|
+
excludesFile: () => false,
|
|
5
|
+
excludesDirectory: () => false,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Match one path segment against one pattern segment, where `*` is the only
|
|
9
|
+
* metacharacter and never crosses `/`.
|
|
10
|
+
*
|
|
11
|
+
* This is the classic greedy two-pointer wildcard match, deliberately not a
|
|
12
|
+
* compiled regular expression: a pattern like `*a*a*a...` translates to
|
|
13
|
+
* nested-quantifier alternatives that backtrack exponentially, and exclusion
|
|
14
|
+
* patterns come from config and flags where a typo should not hang generation.
|
|
15
|
+
* Restarting from the last `*` instead bounds the work at O(pattern x text).
|
|
16
|
+
*/
|
|
17
|
+
function matchSegment(pattern, text) {
|
|
18
|
+
let patternIndex = 0;
|
|
19
|
+
let textIndex = 0;
|
|
20
|
+
let starPatternIndex = -1;
|
|
21
|
+
let starTextIndex = 0;
|
|
22
|
+
while (textIndex < text.length) {
|
|
23
|
+
if (pattern[patternIndex] === '*') {
|
|
24
|
+
// Collapse a run of stars: within a segment they are all equivalent.
|
|
25
|
+
while (pattern[patternIndex] === '*') {
|
|
26
|
+
patternIndex += 1;
|
|
27
|
+
}
|
|
28
|
+
if (patternIndex === pattern.length) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
starPatternIndex = patternIndex;
|
|
32
|
+
starTextIndex = textIndex;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (patternIndex < pattern.length &&
|
|
36
|
+
pattern[patternIndex] === text[textIndex]) {
|
|
37
|
+
patternIndex += 1;
|
|
38
|
+
textIndex += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (starPatternIndex === -1) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Give the last star one more character and retry from just after it.
|
|
45
|
+
starTextIndex += 1;
|
|
46
|
+
patternIndex = starPatternIndex;
|
|
47
|
+
textIndex = starTextIndex;
|
|
48
|
+
}
|
|
49
|
+
while (pattern[patternIndex] === '*') {
|
|
50
|
+
patternIndex += 1;
|
|
51
|
+
}
|
|
52
|
+
return patternIndex === pattern.length;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Match a docs-root-relative path against a pattern, segment by segment, where
|
|
56
|
+
* a whole-segment `**` spans zero or more path segments.
|
|
57
|
+
*
|
|
58
|
+
* Same greedy-restart shape as {@link matchSegment}, one level up, so a pattern
|
|
59
|
+
* full of `**` segments stays bounded too.
|
|
60
|
+
*/
|
|
61
|
+
function matchPath(patternSegments, pathSegments) {
|
|
62
|
+
let patternIndex = 0;
|
|
63
|
+
let pathIndex = 0;
|
|
64
|
+
let starPatternIndex = -1;
|
|
65
|
+
let starPathIndex = 0;
|
|
66
|
+
while (pathIndex < pathSegments.length) {
|
|
67
|
+
if (patternSegments[patternIndex] === '**') {
|
|
68
|
+
while (patternSegments[patternIndex] === '**') {
|
|
69
|
+
patternIndex += 1;
|
|
70
|
+
}
|
|
71
|
+
if (patternIndex === patternSegments.length) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
starPatternIndex = patternIndex;
|
|
75
|
+
starPathIndex = pathIndex;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (patternIndex < patternSegments.length &&
|
|
79
|
+
matchSegment(patternSegments[patternIndex], pathSegments[pathIndex])) {
|
|
80
|
+
patternIndex += 1;
|
|
81
|
+
pathIndex += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (starPatternIndex === -1) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
starPathIndex += 1;
|
|
88
|
+
patternIndex = starPatternIndex;
|
|
89
|
+
pathIndex = starPathIndex;
|
|
90
|
+
}
|
|
91
|
+
while (patternSegments[patternIndex] === '**') {
|
|
92
|
+
patternIndex += 1;
|
|
93
|
+
}
|
|
94
|
+
return patternIndex === patternSegments.length;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Compile exclusion globs matched against **docs-root-relative POSIX paths**
|
|
98
|
+
* (`api/auth.md`, `api/nested`), never against absolute or CWD-relative ones.
|
|
99
|
+
*
|
|
100
|
+
* Grammar, defined once and documented in
|
|
101
|
+
* `apps/oat-docs/docs/reference/oat-directory-structure.md`:
|
|
102
|
+
*
|
|
103
|
+
* - `*` matches any run of characters inside a single path segment.
|
|
104
|
+
* - `**` spans `/` only as a whole segment, where it covers zero or more
|
|
105
|
+
* segments: `**\/CLAUDE.md` matches `CLAUDE.md` and `api/CLAUDE.md`. Inside a
|
|
106
|
+
* segment (`a**b`) it is an ordinary single-segment wildcard.
|
|
107
|
+
* - Patterns are anchored at both ends against the whole relative path, so a
|
|
108
|
+
* bare `CLAUDE.md` matches only the root-level file. Use `**\/CLAUDE.md` for
|
|
109
|
+
* every depth.
|
|
110
|
+
* - A trailing `/` restricts a pattern to directories: `drafts/` prunes the
|
|
111
|
+
* `drafts` directory and everything beneath it, and never matches a file.
|
|
112
|
+
* Without it, a pattern that matches a directory path prunes it too.
|
|
113
|
+
* - Matching is case-sensitive, `/` is the separator on every platform, and
|
|
114
|
+
* `*` is the only metacharacter — `.`, `+`, and friends are literal.
|
|
115
|
+
*
|
|
116
|
+
* Entries are trimmed; a leading `./` or `/` is stripped so both spellings
|
|
117
|
+
* anchor at the docs root. Blank entries are ignored.
|
|
118
|
+
*/
|
|
119
|
+
export function createExclusionMatcher(patterns) {
|
|
120
|
+
const filePatterns = [];
|
|
121
|
+
const directoryPatterns = [];
|
|
122
|
+
for (const rawPattern of patterns) {
|
|
123
|
+
const trimmedPattern = rawPattern.trim();
|
|
124
|
+
if (!trimmedPattern) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const directoryOnly = trimmedPattern.endsWith('/');
|
|
128
|
+
const normalized = trimmedPattern
|
|
129
|
+
.replace(/\/+$/, '')
|
|
130
|
+
.replace(/^\.\//, '')
|
|
131
|
+
.replace(/^\/+/, '');
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const segments = normalized.split('/');
|
|
136
|
+
directoryPatterns.push(segments);
|
|
137
|
+
if (!directoryOnly) {
|
|
138
|
+
filePatterns.push(segments);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (directoryPatterns.length === 0) {
|
|
142
|
+
return NO_EXCLUSIONS;
|
|
143
|
+
}
|
|
144
|
+
const matches = (compiled, relativePath) => {
|
|
145
|
+
const pathSegments = relativePath.split('/');
|
|
146
|
+
return compiled.some((segments) => matchPath(segments, pathSegments));
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
excludesFile: (relativePath) => matches(filePatterns, relativePath),
|
|
150
|
+
excludesDirectory: (relativePath) => matches(directoryPatterns, relativePath),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
3
153
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
|
|
4
154
|
const HEADING_RE = /^#\s+(.+)$/m;
|
|
5
155
|
function fileNameToTitle(fileName) {
|
|
@@ -60,15 +210,25 @@ function prefixEntryPath(entry, prefix) {
|
|
|
60
210
|
children: entry.children?.map((child) => prefixEntryPath(child, prefix)),
|
|
61
211
|
};
|
|
62
212
|
}
|
|
63
|
-
|
|
213
|
+
/** Docs-root-relative POSIX path used for matching, never for reading files. */
|
|
214
|
+
function relativeKey(base, name) {
|
|
215
|
+
return base ? `${base}/${name}` : name;
|
|
216
|
+
}
|
|
217
|
+
async function collectEntries(docsDir, relativeBase, matcher) {
|
|
64
218
|
const entries = [];
|
|
65
219
|
const subdirs = [];
|
|
66
220
|
const dirEntries = await readdir(docsDir, { withFileTypes: true });
|
|
67
221
|
for (const entry of dirEntries) {
|
|
222
|
+
const relativePath = relativeKey(relativeBase, entry.name);
|
|
68
223
|
if (entry.isDirectory()) {
|
|
69
|
-
|
|
224
|
+
if (!matcher.excludesDirectory(relativePath)) {
|
|
225
|
+
subdirs.push(entry.name);
|
|
226
|
+
}
|
|
70
227
|
}
|
|
71
228
|
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
229
|
+
if (matcher.excludesFile(relativePath)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
72
232
|
const filePath = join(docsDir, entry.name);
|
|
73
233
|
const content = await readFile(filePath, 'utf8');
|
|
74
234
|
const { title, description } = resolveTitle(content, entry.name);
|
|
@@ -80,7 +240,10 @@ export async function generateIndex(docsDir) {
|
|
|
80
240
|
}
|
|
81
241
|
}
|
|
82
242
|
for (const subdir of subdirs.sort()) {
|
|
83
|
-
const children = await
|
|
243
|
+
const children = await collectEntries(join(docsDir, subdir), relativeKey(relativeBase, subdir), matcher);
|
|
244
|
+
// A directory with no surviving children emits no heading. This already
|
|
245
|
+
// pruned genuinely empty directories; exclusion simply empties more of
|
|
246
|
+
// them, and it composes upward through the recursion.
|
|
84
247
|
if (children.length > 0) {
|
|
85
248
|
entries.push({
|
|
86
249
|
title: fileNameToTitle(subdir),
|
|
@@ -91,6 +254,9 @@ export async function generateIndex(docsDir) {
|
|
|
91
254
|
}
|
|
92
255
|
return sortEntries(entries);
|
|
93
256
|
}
|
|
257
|
+
export async function generateIndex(docsDir, options = {}) {
|
|
258
|
+
return collectEntries(docsDir, '', createExclusionMatcher(options.excludes ?? []));
|
|
259
|
+
}
|
|
94
260
|
function renderEntries(entries, indent) {
|
|
95
261
|
const lines = [];
|
|
96
262
|
const prefix = ' '.repeat(indent);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
|
+
import { type OatConfig } from '../../../config/oat-config.js';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
export declare const GENERATED_INDEX_WARNING = "<!-- AUTOGENERATED by `oat docs generate-index`. Do not hand-edit; changes are clobbered on every `predev` / `prebuild`. -->";
|
|
4
5
|
interface IndexGenerateFileDependencies {
|
|
5
|
-
generateIndex: (docsDir: string) => Promise<import('./generator.js').IndexEntry[]>;
|
|
6
|
+
generateIndex: (docsDir: string, options: import('./generator.js').GenerateIndexOptions) => Promise<import('./generator.js').IndexEntry[]>;
|
|
6
7
|
renderIndex: (entries: import('./generator.js').IndexEntry[]) => string;
|
|
7
8
|
writeFile: (path: string, content: string, encoding: BufferEncoding) => Promise<void>;
|
|
8
|
-
readOatConfig: (repoRoot: string) => Promise<
|
|
9
|
-
writeOatConfig: (repoRoot: string, config:
|
|
9
|
+
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
10
|
+
writeOatConfig: (repoRoot: string, config: OatConfig) => Promise<void>;
|
|
10
11
|
resolveRepoRoot: (cwd: string) => Promise<string>;
|
|
12
|
+
dirExists: (path: string) => Promise<boolean>;
|
|
13
|
+
readFileIfPresent: (path: string) => Promise<string | null>;
|
|
14
|
+
realpath: (path: string) => Promise<string>;
|
|
15
|
+
readLinkIfSymlink: (path: string) => Promise<string | null>;
|
|
11
16
|
}
|
|
12
17
|
interface IndexGenerateDependencies {
|
|
13
18
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/index-generate/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAI5C,eAAO,MAAM,uBAAuB,iIAC4F,CAAC;AAuBjI,UAAU,6BAA6B;IACrC,aAAa,EAAE,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,aAAa,EAAE,oBAAoB,KAChD,OAAO,CAAC,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC,CAAC;IACjD,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,KAAK,MAAM,CAAC;IACrE,SAAS,EAAE,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,QAAQ,EAAE,6BAA6B,CAAC;CACzC;AAggBD,wBAAgB,8BAA8B,CAC5C,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAgCT"}
|