@agentskit/doc-bridge 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +7 -0
- package/README.md +81 -15
- package/SECURITY.md +1 -1
- package/action.yml +2 -2
- package/dist/cli/program.js +1248 -316
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +338 -13
- package/dist/config/index.js.map +1 -1
- package/dist/{index-CPUJbTbg.d.ts → index-DGI9TBLE.d.ts} +906 -11
- package/dist/index.d.ts +65 -11
- package/dist/index.js +1084 -171
- package/dist/index.js.map +1 -1
- package/docs/RELEASE.md +19 -21
- package/docs/getting-started.md +27 -2
- package/docs/landing/assets/doc-bridge-hero.webp +0 -0
- package/docs/landing/assets/doc-bridge-surfaces.webp +0 -0
- package/docs/landing/assets/doc-bridge-two-way.webp +0 -0
- package/docs/landing/index.html +70 -10
- package/docs/playbook/doc-bridge-pattern.md +2 -2
- package/docs/recipes/index-pipeline.md +2 -2
- package/docs/schemas/memory-candidate-v1.md +10 -1
- package/docs/spec/cli.md +1 -0
- package/docs/spec/config-v1.md +51 -6
- package/docs/spec/documentation-standard-v1.md +131 -0
- package/ecosystem-claims.json +187 -0
- package/ecosystem-upstream.json +9 -0
- package/ecosystem.json +231 -0
- package/package.json +14 -2
- package/scripts/check-ecosystem-upstream.mjs +50 -0
- package/src/cli/program.ts +43 -9
- package/src/config/index.ts +7 -1
- package/src/config/load-config.ts +4 -14
- package/src/config/schema.ts +91 -0
- package/src/conformance/documentation-standard-v1.ts +502 -0
- package/src/conformance/ecosystem-contract.ts +175 -0
- package/src/gates/run-gates.ts +33 -4
- package/src/index-builder/human-adapters/core.ts +12 -5
- package/src/index-builder/human-adapters/docusaurus.ts +29 -44
- package/src/index-builder/human-adapters/index.ts +15 -3
- package/src/index-builder/scan-corpus.ts +6 -6
- package/src/index.ts +17 -0
- package/src/lib/bounded-text.ts +25 -0
- package/src/lib/paths.ts +20 -2
- package/src/lib/static-js-literal.ts +261 -0
- package/src/lib/walk.ts +23 -4
- package/src/version.ts +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const stripComments = (source: string): string => {
|
|
2
|
+
let out = ''
|
|
3
|
+
let index = 0
|
|
4
|
+
let quote: string | undefined
|
|
5
|
+
while (index < source.length) {
|
|
6
|
+
const char = source[index] ?? ''
|
|
7
|
+
const next = source[index + 1] ?? ''
|
|
8
|
+
if (quote) {
|
|
9
|
+
out += char
|
|
10
|
+
if (char === '\\') {
|
|
11
|
+
out += next
|
|
12
|
+
index += 2
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
if (char === quote) quote = undefined
|
|
16
|
+
index += 1
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
20
|
+
quote = char
|
|
21
|
+
out += char
|
|
22
|
+
index += 1
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
if (char === '/' && next === '/') {
|
|
26
|
+
while (index < source.length && source[index] !== '\n') index += 1
|
|
27
|
+
out += '\n'
|
|
28
|
+
index += 1
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
if (char === '/' && next === '*') {
|
|
32
|
+
index += 2
|
|
33
|
+
while (index < source.length && !(source[index] === '*' && source[index + 1] === '/')) index += 1
|
|
34
|
+
index += 2
|
|
35
|
+
out += ' '
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
out += char
|
|
39
|
+
index += 1
|
|
40
|
+
}
|
|
41
|
+
if (quote) throw new Error('Unterminated string in static JavaScript literal.')
|
|
42
|
+
return out
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class LiteralParser {
|
|
46
|
+
private index = 0
|
|
47
|
+
|
|
48
|
+
constructor(private readonly source: string) {}
|
|
49
|
+
|
|
50
|
+
parseAt(index: number): unknown {
|
|
51
|
+
this.index = index
|
|
52
|
+
const value = this.value()
|
|
53
|
+
this.space()
|
|
54
|
+
return value
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private space(): void {
|
|
58
|
+
while (/\s/.test(this.source[this.index] ?? '')) this.index += 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private value(): unknown {
|
|
62
|
+
this.space()
|
|
63
|
+
const char = this.source[this.index]
|
|
64
|
+
if (char === '{') return this.object()
|
|
65
|
+
if (char === '[') return this.array()
|
|
66
|
+
if (char === '"' || char === "'") return this.string()
|
|
67
|
+
const word = this.word()
|
|
68
|
+
if (word === 'true') return true
|
|
69
|
+
if (word === 'false') return false
|
|
70
|
+
if (word === 'null') return null
|
|
71
|
+
if (/^-?\d+(?:\.\d+)?$/.test(word)) return Number(word)
|
|
72
|
+
throw new Error(`Unsupported dynamic sidebar expression near "${word || char || 'EOF'}".`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private object(): Record<string, unknown> {
|
|
76
|
+
const result: Record<string, unknown> = {}
|
|
77
|
+
this.index += 1
|
|
78
|
+
this.space()
|
|
79
|
+
while (this.source[this.index] !== '}') {
|
|
80
|
+
const key = this.source[this.index] === '"' || this.source[this.index] === "'" ? this.string() : this.word()
|
|
81
|
+
if (!key) throw new Error('Static sidebar object key is required.')
|
|
82
|
+
this.space()
|
|
83
|
+
if (this.source[this.index] !== ':') throw new Error(`Static sidebar key "${key}" requires a literal value.`)
|
|
84
|
+
this.index += 1
|
|
85
|
+
result[key] = this.value()
|
|
86
|
+
this.space()
|
|
87
|
+
if (this.source[this.index] === ',') {
|
|
88
|
+
this.index += 1
|
|
89
|
+
this.space()
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
if (this.source[this.index] !== '}') throw new Error('Expected comma or closing brace in static sidebar.')
|
|
93
|
+
}
|
|
94
|
+
this.index += 1
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private array(): unknown[] {
|
|
99
|
+
const result: unknown[] = []
|
|
100
|
+
this.index += 1
|
|
101
|
+
this.space()
|
|
102
|
+
while (this.source[this.index] !== ']') {
|
|
103
|
+
result.push(this.value())
|
|
104
|
+
this.space()
|
|
105
|
+
if (this.source[this.index] === ',') {
|
|
106
|
+
this.index += 1
|
|
107
|
+
this.space()
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (this.source[this.index] !== ']') throw new Error('Expected comma or closing bracket in static sidebar.')
|
|
111
|
+
}
|
|
112
|
+
this.index += 1
|
|
113
|
+
return result
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private string(): string {
|
|
117
|
+
const quote = this.source[this.index] ?? ''
|
|
118
|
+
this.index += 1
|
|
119
|
+
let result = ''
|
|
120
|
+
while (this.index < this.source.length) {
|
|
121
|
+
const char = this.source[this.index] ?? ''
|
|
122
|
+
if (char === quote) {
|
|
123
|
+
this.index += 1
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
if (char === '\\') {
|
|
127
|
+
const escaped = this.source[this.index + 1]
|
|
128
|
+
if (escaped === undefined) break
|
|
129
|
+
const escapes: Record<string, string> = { n: '\n', r: '\r', t: '\t' }
|
|
130
|
+
result += escapes[escaped] ?? escaped
|
|
131
|
+
this.index += 2
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
result += char
|
|
135
|
+
this.index += 1
|
|
136
|
+
}
|
|
137
|
+
throw new Error('Unterminated string in static sidebar.')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private word(): string {
|
|
141
|
+
this.space()
|
|
142
|
+
const start = this.index
|
|
143
|
+
while (/[A-Za-z0-9_$.-]/.test(this.source[this.index] ?? '')) this.index += 1
|
|
144
|
+
return this.source.slice(start, this.index)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const maskStrings = (source: string): string => {
|
|
149
|
+
const chars = [...source]
|
|
150
|
+
let quote: string | undefined
|
|
151
|
+
let previousToken: string | undefined
|
|
152
|
+
const regexPrefixKeywords = new Set([
|
|
153
|
+
'await', 'case', 'delete', 'do', 'else', 'in', 'instanceof', 'new',
|
|
154
|
+
'of', 'return', 'throw', 'typeof', 'void', 'yield',
|
|
155
|
+
])
|
|
156
|
+
const canStartRegex = (): boolean => previousToken === undefined
|
|
157
|
+
|| '=(:,[!&|?{};'.includes(previousToken)
|
|
158
|
+
|| previousToken === '=>'
|
|
159
|
+
|| regexPrefixKeywords.has(previousToken)
|
|
160
|
+
for (let index = 0; index < chars.length; index += 1) {
|
|
161
|
+
const char = chars[index] ?? ''
|
|
162
|
+
if (quote) {
|
|
163
|
+
chars[index] = ' '
|
|
164
|
+
if (char === '\\') {
|
|
165
|
+
if (index + 1 < chars.length) chars[index + 1] = ' '
|
|
166
|
+
index += 1
|
|
167
|
+
} else if (char === quote) {
|
|
168
|
+
quote = undefined
|
|
169
|
+
previousToken = 'literal'
|
|
170
|
+
}
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
174
|
+
quote = char
|
|
175
|
+
chars[index] = ' '
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
if (char === '/' && canStartRegex()) {
|
|
179
|
+
chars[index] = ' '
|
|
180
|
+
let inClass = false
|
|
181
|
+
for (index += 1; index < chars.length; index += 1) {
|
|
182
|
+
const regexChar = chars[index] ?? ''
|
|
183
|
+
chars[index] = ' '
|
|
184
|
+
if (regexChar === '\\') {
|
|
185
|
+
if (index + 1 < chars.length) chars[index + 1] = ' '
|
|
186
|
+
index += 1
|
|
187
|
+
} else if (regexChar === '[') {
|
|
188
|
+
inClass = true
|
|
189
|
+
} else if (regexChar === ']') {
|
|
190
|
+
inClass = false
|
|
191
|
+
} else if (regexChar === '/' && !inClass) {
|
|
192
|
+
while (/[A-Za-z]/.test(chars[index + 1] ?? '')) {
|
|
193
|
+
index += 1
|
|
194
|
+
chars[index] = ' '
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
previousToken = 'literal'
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
203
|
+
const start = index
|
|
204
|
+
while (/[A-Za-z0-9_$]/.test(chars[index + 1] ?? '')) index += 1
|
|
205
|
+
previousToken = chars.slice(start, index + 1).join('')
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
if (char === '=' && chars[index + 1] === '>') {
|
|
209
|
+
previousToken = '=>'
|
|
210
|
+
index += 1
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
if (!/\s/.test(char)) previousToken = char
|
|
214
|
+
}
|
|
215
|
+
return chars.join('')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const nonSpace = (source: string, start: number): number => {
|
|
219
|
+
let index = start
|
|
220
|
+
while (/\s/.test(source[index] ?? '')) index += 1
|
|
221
|
+
return index
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const escapedRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
225
|
+
|
|
226
|
+
export const parseStaticJsObject = (source: string): unknown => {
|
|
227
|
+
const clean = stripComments(source)
|
|
228
|
+
const mask = maskStrings(clean)
|
|
229
|
+
const parseBinding = (identifier: string): unknown => {
|
|
230
|
+
const declaration = new RegExp(`\\b(?:const|let|var)\\s+${escapedRegex(identifier)}(?:\\s*:[^=;]+)?\\s*=`).exec(mask)
|
|
231
|
+
if (!declaration) throw new Error(`Exported binding "${identifier}" must reference a static object declaration.`)
|
|
232
|
+
const start = nonSpace(mask, declaration.index + declaration[0].length)
|
|
233
|
+
if (mask[start] !== '{') throw new Error(`Exported binding "${identifier}" must be a static object literal.`)
|
|
234
|
+
return new LiteralParser(clean).parseAt(start)
|
|
235
|
+
}
|
|
236
|
+
const commonJs = /\bmodule\.exports\s*=/.exec(mask)
|
|
237
|
+
if (commonJs) {
|
|
238
|
+
const start = nonSpace(mask, commonJs.index + commonJs[0].length)
|
|
239
|
+
if (mask[start] === '{') return new LiteralParser(clean).parseAt(start)
|
|
240
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(mask.slice(start))?.[0]
|
|
241
|
+
if (identifier) return parseBinding(identifier)
|
|
242
|
+
throw new Error('module.exports must be assigned a static object literal or binding.')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const exported = /\bexport\s+default\b/.exec(mask)
|
|
246
|
+
if (exported) {
|
|
247
|
+
let start = nonSpace(mask, exported.index + exported[0].length)
|
|
248
|
+
if (mask.slice(start).startsWith('defineConfig')) {
|
|
249
|
+
start = nonSpace(mask, start + 'defineConfig'.length)
|
|
250
|
+
if (mask[start] !== '(') throw new Error('defineConfig must be called with a static object literal.')
|
|
251
|
+
start = nonSpace(mask, start + 1)
|
|
252
|
+
if (mask[start] !== '{') throw new Error('defineConfig must be called with a static object literal.')
|
|
253
|
+
return new LiteralParser(clean).parseAt(start)
|
|
254
|
+
}
|
|
255
|
+
if (mask[start] === '{') return new LiteralParser(clean).parseAt(start)
|
|
256
|
+
|
|
257
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(mask.slice(start))?.[0]
|
|
258
|
+
if (identifier) return parseBinding(identifier)
|
|
259
|
+
}
|
|
260
|
+
throw new Error('Docusaurus sidebar must export a static object or assign one to a const.')
|
|
261
|
+
}
|
package/src/lib/walk.ts
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
|
-
import { readdirSync,
|
|
1
|
+
import { lstatSync, readdirSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { toPosix } from './paths.js'
|
|
5
5
|
|
|
6
6
|
const DEFAULT_SKIP = new Set(['node_modules', '.git', 'dist', 'coverage', '.doc-bridge'])
|
|
7
|
+
const DEFAULT_MAX_FILES = 10_000
|
|
7
8
|
|
|
8
9
|
export const walkFiles = (
|
|
9
10
|
root: string,
|
|
10
|
-
opts?: {
|
|
11
|
+
opts?: {
|
|
12
|
+
readonly extensions?: readonly string[]
|
|
13
|
+
readonly skipDirs?: ReadonlySet<string>
|
|
14
|
+
readonly maxFiles?: number
|
|
15
|
+
},
|
|
11
16
|
): string[] => {
|
|
12
17
|
const extensions = opts?.extensions ?? ['.md']
|
|
13
18
|
const skip = opts?.skipDirs ?? DEFAULT_SKIP
|
|
14
19
|
const out: string[] = []
|
|
20
|
+
const visited = new Set<string>()
|
|
15
21
|
|
|
16
22
|
const visit = (dir: string) => {
|
|
23
|
+
let canonicalDir: string
|
|
24
|
+
try {
|
|
25
|
+
canonicalDir = realpathSync.native(dir)
|
|
26
|
+
} catch {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
if (visited.has(canonicalDir)) return
|
|
30
|
+
visited.add(canonicalDir)
|
|
31
|
+
|
|
17
32
|
let entries: string[] = []
|
|
18
33
|
try {
|
|
19
34
|
entries = readdirSync(dir)
|
|
@@ -24,10 +39,11 @@ export const walkFiles = (
|
|
|
24
39
|
const abs = join(dir, name)
|
|
25
40
|
let st
|
|
26
41
|
try {
|
|
27
|
-
st =
|
|
42
|
+
st = lstatSync(abs)
|
|
28
43
|
} catch {
|
|
29
44
|
continue
|
|
30
45
|
}
|
|
46
|
+
if (st.isSymbolicLink()) continue
|
|
31
47
|
if (st.isDirectory()) {
|
|
32
48
|
if (skip.has(name)) continue
|
|
33
49
|
visit(abs)
|
|
@@ -35,6 +51,9 @@ export const walkFiles = (
|
|
|
35
51
|
}
|
|
36
52
|
if (!st.isFile()) continue
|
|
37
53
|
if (extensions.some((ext) => name.endsWith(ext))) {
|
|
54
|
+
if (out.length >= (opts?.maxFiles ?? DEFAULT_MAX_FILES)) {
|
|
55
|
+
throw new Error(`Documentation corpus exceeds the ${opts?.maxFiles ?? DEFAULT_MAX_FILES} file limit.`)
|
|
56
|
+
}
|
|
38
57
|
out.push(toPosix(abs))
|
|
39
58
|
}
|
|
40
59
|
}
|
|
@@ -42,4 +61,4 @@ export const walkFiles = (
|
|
|
42
61
|
|
|
43
62
|
visit(root)
|
|
44
63
|
return out.sort()
|
|
45
|
-
}
|
|
64
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.
|
|
1
|
+
export const PACKAGE_VERSION = '1.1.1'
|