@agentskit/doc-bridge 1.11.1 → 1.12.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/CHANGELOG.md +17 -1
- package/README.md +3 -2
- package/action.yml +1 -1
- package/dist/cli/program.js +582 -471
- package/dist/cli/program.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +500 -389
- package/dist/index.js.map +1 -1
- package/docs/DESIGN.md +71 -0
- package/docs/MARKETPLACE-ECOSYSTEM-PLAN.md +1 -1
- package/docs/PRD-enterprise-hardening.md +5 -5
- package/docs/PRD-knowledge-retrieval-and-enrichment.md +1 -1
- package/docs/knowledge-engine-runbook.md +1 -1
- package/docs/loop-workflow.md +15 -15
- package/docs/spec/config-v1.md +29 -7
- package/docs/spec/documentation-standard-v1.md +5 -0
- package/docs/spec/mcp-knowledge-tools-v1.md +1 -1
- package/docs/validation-cycle-plan.md +13 -13
- package/ecosystem-claims.json +17 -17
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +300 -147
- package/mcpb/manifest.json +1 -1
- package/package.json +2 -1
- package/skills/doc-bridge-handoff/SKILL.md +1 -1
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/conformance/ecosystem-contract.ts +10 -26
- package/src/discovery/repository.ts +7 -1
- package/src/findings/report.ts +1 -1
- package/src/fixes/proposals.ts +4 -2
- package/src/gates/run-gates.ts +1 -1
- package/src/index-builder/human-adapters/core.ts +10 -4
- package/src/lib/ignore-filter.ts +151 -0
- package/src/lib/walk.ts +10 -2
- package/src/memory/ingest.ts +1 -1
- package/src/report/html.ts +1 -1
- package/src/safety/repository.ts +9 -0
- package/src/version.ts +1 -1
- package/docs/DOGFOOD-ROUND2.md +0 -147
- package/docs/DOGFOOD-ROUND3.md +0 -79
- package/docs/DOGFOOD-V1.md +0 -89
- package/docs/DOGFOOD.md +0 -97
|
@@ -50,12 +50,9 @@ const ManifestSchema = z.object({
|
|
|
50
50
|
schemaVersion: z.literal(2),
|
|
51
51
|
parentBrand: z.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema }).passthrough(),
|
|
52
52
|
products: z.array(ProductSchema).min(1),
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.refine((value) => value.length === 4 || value.length === 7, {
|
|
57
|
-
message: 'must project either the legacy four products or the full seven-product catalog',
|
|
58
|
-
}),
|
|
53
|
+
// Deprecated compatibility shim: when present, every entry projects one products[] record.
|
|
54
|
+
// Membership is whatever the canonical manifest lists; no product is required by name.
|
|
55
|
+
properties: z.array(LegacyPropertySchema).optional(),
|
|
59
56
|
builder: z.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema, url: HttpsUrlSchema }).passthrough().optional(),
|
|
60
57
|
}).passthrough()
|
|
61
58
|
|
|
@@ -94,19 +91,6 @@ const ClaimsSchema = z.object({
|
|
|
94
91
|
products: z.array(ClaimProductSchema),
|
|
95
92
|
}).passthrough()
|
|
96
93
|
|
|
97
|
-
/** Historical four-product shim order. */
|
|
98
|
-
const LEGACY_FOUR_PRODUCT_IDS = ['agentskit', 'akos', 'playbook', 'registry'] as const
|
|
99
|
-
/** Full seven-product projection order (matches products[]). */
|
|
100
|
-
const FULL_SEVEN_PRODUCT_IDS = [
|
|
101
|
-
'agentskit',
|
|
102
|
-
'registry',
|
|
103
|
-
'agentskit-chat',
|
|
104
|
-
'playbook',
|
|
105
|
-
'doc-bridge',
|
|
106
|
-
'code-review',
|
|
107
|
-
'akos',
|
|
108
|
-
] as const
|
|
109
|
-
|
|
110
94
|
export const parseCanonicalEcosystemContract = (
|
|
111
95
|
manifestInput: unknown,
|
|
112
96
|
claimsInput: unknown,
|
|
@@ -137,14 +121,14 @@ export const parseCanonicalEcosystemContract = (
|
|
|
137
121
|
}
|
|
138
122
|
}
|
|
139
123
|
|
|
140
|
-
const propertyIds =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for (const [index, id] of propertyIds.entries()) {
|
|
144
|
-
const legacy = manifest.properties[index]
|
|
124
|
+
const propertyIds = new Set<string>()
|
|
125
|
+
for (const [index, legacy] of (manifest.properties ?? []).entries()) {
|
|
126
|
+
const id = legacy.id
|
|
145
127
|
const product = products.get(id)
|
|
146
|
-
if (
|
|
147
|
-
|
|
128
|
+
if (propertyIds.has(id)) throw new Error(`Legacy property ${id} is listed more than once.`)
|
|
129
|
+
propertyIds.add(id)
|
|
130
|
+
if (!product || !product.surfaces.home) {
|
|
131
|
+
throw new Error(`Legacy property ${index} (${id}) must project a manifest product with a home surface.`)
|
|
148
132
|
}
|
|
149
133
|
const expected = {
|
|
150
134
|
name: product.name,
|
|
@@ -203,7 +203,13 @@ const discoverPackages = (
|
|
|
203
203
|
const name = packageName(parsed.value, path)
|
|
204
204
|
const id = entityId('package', name ?? path)
|
|
205
205
|
const duplicate = packages.find((pkg) => pkg.id === id)
|
|
206
|
-
|
|
206
|
+
// Compare toPosix'd, not raw: the root package above is always registered with its raw, native-separator
|
|
207
|
+
// `absPath` (other code depends on that — e.g. `packageForModule`'s `startsWith` matching against
|
|
208
|
+
// `safeWalkFiles`' own native-style paths), while `expandWorkspaceGlobs` always returns toPosix'd absPaths.
|
|
209
|
+
// On Windows those are two different strings for the exact same directory whenever a workspace list
|
|
210
|
+
// includes "." explicitly (root resolves to itself both ways) — a false-positive collision on every such
|
|
211
|
+
// Windows checkout otherwise (reproduced live: harness's own `packages: [., apps/*]`).
|
|
212
|
+
if (duplicate && toPosix(duplicate.absPath) !== toPosix(absPath)) {
|
|
207
213
|
throw new Error(`Package identity collision for "${id}": "${duplicate.path}" and "${path}".`)
|
|
208
214
|
}
|
|
209
215
|
if (!duplicate) packages.push({ id, ...(name ? { name } : {}), path, absPath, manifestPath, manifest: parsed.value })
|
package/src/findings/report.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Evidence, FindingStatus, KnowledgeDiagnostic } from '../schemas/kn
|
|
|
4
4
|
* The canonical finding, as the rest of the ecosystem consumes it.
|
|
5
5
|
*
|
|
6
6
|
* `Finding` and `SEVERITY_ORDER` mirror `@agentskit/core/finding`, which is an optional peer:
|
|
7
|
-
* Code Review
|
|
7
|
+
* Code Review and dashboards read this shape, so a Doc Bridge diagnostic reported in it
|
|
8
8
|
* needs no parser of its own. A test imports the real package and asserts that what is emitted
|
|
9
9
|
* here is assignable to it and that the severities are drawn from the real order.
|
|
10
10
|
*
|
package/src/fixes/proposals.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { basename, dirname, extname, join, relative, resolve, sep } from 'node:p
|
|
|
3
3
|
|
|
4
4
|
import { contentHashForArtifactV1, sha256NormalizedV1 } from '../index-builder/content-hash.js'
|
|
5
5
|
import { FixProposalV1Schema, type FixProposalV1 } from '../schemas/knowledge.js'
|
|
6
|
+
import { createIgnoreFilter, type IgnoreFilter } from '../lib/ignore-filter.js'
|
|
6
7
|
import { containedPath } from '../safety/repository.js'
|
|
7
8
|
|
|
8
9
|
export type FixProposalOptions = {
|
|
@@ -55,10 +56,11 @@ const makeProposal = (root: string, options: FixProposalOptions, changes: readon
|
|
|
55
56
|
return FixProposalV1Schema.parse({ ...draft, contentHash: contentHashForArtifactV1(draft) })
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
const walkMarkdown = (root: string, directory = root): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
59
|
+
const walkMarkdown = (root: string, directory = root, ignored: IgnoreFilter = createIgnoreFilter(root)): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
59
60
|
if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') return []
|
|
60
61
|
const path = join(directory, entry.name)
|
|
61
|
-
if (entry.isDirectory()) return
|
|
62
|
+
if (ignored.isIgnored(path, entry.isDirectory())) return []
|
|
63
|
+
if (entry.isDirectory()) return walkMarkdown(root, path, ignored)
|
|
62
64
|
return entry.isFile() && ['.md', '.mdx'].includes(extname(entry.name).toLowerCase()) ? [relative(root, path).split(sep).join('/')] : []
|
|
63
65
|
})
|
|
64
66
|
|
package/src/gates/run-gates.ts
CHANGED
|
@@ -92,7 +92,7 @@ export const runGate = (
|
|
|
92
92
|
return {
|
|
93
93
|
id,
|
|
94
94
|
ok: false,
|
|
95
|
-
message: `${result.ignored.length} indexed path(s) are ignored by Git, so a clean checkout builds a different index.
|
|
95
|
+
message: `${result.ignored.length} indexed path(s) are ignored by Git, so a clean checkout builds a different index. Rebuild the index (scans skip ignored paths) or add them to safety.exclude.`,
|
|
96
96
|
expected: 'every indexed path is committed',
|
|
97
97
|
actual: sample.join(', '),
|
|
98
98
|
}
|
|
@@ -37,12 +37,15 @@ export const optionString = (
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export const parseFrontmatter = (raw: string): Record<string, string> => {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Windows-authored (or git autocrlf-checked-out) Markdown commonly uses CRLF;
|
|
41
|
+
// normalize before matching so frontmatter isn't silently dropped there.
|
|
42
|
+
const normalized = raw.includes('\r\n') ? raw.replace(/\r\n/g, '\n') : raw
|
|
43
|
+
if (!normalized.startsWith('---\n')) return {}
|
|
44
|
+
const end = normalized.indexOf('\n---', 4)
|
|
42
45
|
if (end === -1) return {}
|
|
43
46
|
|
|
44
47
|
const out: Record<string, string> = {}
|
|
45
|
-
for (const line of
|
|
48
|
+
for (const line of normalized.slice(4, end).split('\n')) {
|
|
46
49
|
const match = /^([A-Za-z0-9_-]+):\s*(.+?)\s*$/.exec(line)
|
|
47
50
|
if (match?.[1] && match[2]) out[match[1]] = match[2].replace(/^['"]|['"]$/g, '')
|
|
48
51
|
}
|
|
@@ -89,7 +92,10 @@ export const scanMarkdownDocs = (
|
|
|
89
92
|
const canonical = realpathSync.native(abs)
|
|
90
93
|
const fileRelative = relative(projectRoot, canonical)
|
|
91
94
|
if (isAbsolute(fileRelative) || fileRelative === '..' || fileRelative.startsWith(`..${sep}`)) continue
|
|
92
|
-
|
|
95
|
+
// Both sides must be toPosix'd before stripping the prefix: `abs` is native-separator
|
|
96
|
+
// (backslashes on Windows), so comparing it raw against a toPosix'd `absRoot` never
|
|
97
|
+
// matches there, silently leaving relToHumanRoot as the full absolute path.
|
|
98
|
+
const relToHumanRoot = toPosix(abs).replace(`${toPosix(absRoot)}/`, '')
|
|
93
99
|
const raw = readBoundedText(abs, budget)
|
|
94
100
|
if (options?.includeRelPath && !options.includeRelPath(relToHumanRoot, raw)) continue
|
|
95
101
|
out.push({
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files a repository scan may see.
|
|
3
|
+
*
|
|
4
|
+
* A committed index is only reproducible while it is a function of committed (or at least
|
|
5
|
+
* non-ignored) content. Build output — `.next/`, `.source/`, generated API docs, files that a dev
|
|
6
|
+
* server writes such as `next-env.d.ts` — exists on one machine and not on the next, so every walk
|
|
7
|
+
* that feeds the index honours the repository's ignore rules in addition to its own excludes.
|
|
8
|
+
*
|
|
9
|
+
* Inside a Git work tree the answer comes from Git itself (`git ls-files --cached --others
|
|
10
|
+
* --exclude-standard`), which applies nested `.gitignore` files, `.git/info/exclude`, and the
|
|
11
|
+
* user's global excludes exactly as `git status` does, and still lists tracked files that happen to
|
|
12
|
+
* match an ignore rule. Outside Git (a tarball, a CI cache, Git not installed) the `.gitignore`
|
|
13
|
+
* files on disk are applied with the same semantics, one directory at a time.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execFileSync } from 'node:child_process'
|
|
17
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
18
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
19
|
+
|
|
20
|
+
import ignore, { type Ignore } from 'ignore'
|
|
21
|
+
|
|
22
|
+
export type IgnoreFilterMode = 'git' | 'gitignore'
|
|
23
|
+
|
|
24
|
+
export type IgnoreFilter = {
|
|
25
|
+
/** `git` when Git answered for this root, `gitignore` for the on-disk fallback. */
|
|
26
|
+
readonly mode: IgnoreFilterMode
|
|
27
|
+
/** True when `absolutePath` (a file, or a directory when `isDirectory`) must not be scanned. */
|
|
28
|
+
readonly isIgnored: (absolutePath: string, isDirectory: boolean) => boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const toPosix = (value: string): string => value.split(sep).join('/')
|
|
32
|
+
|
|
33
|
+
const canonical = (path: string): string => {
|
|
34
|
+
try {
|
|
35
|
+
return realpathSync.native(resolve(path))
|
|
36
|
+
} catch {
|
|
37
|
+
return resolve(path)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Path of `candidate` below `root` in POSIX form, or undefined when it is outside `root`. */
|
|
42
|
+
const below = (root: string, candidate: string): string | undefined => {
|
|
43
|
+
const rel = relative(root, candidate)
|
|
44
|
+
if (rel === '') return ''
|
|
45
|
+
if (isAbsolute(rel) || rel === '..' || rel.startsWith(`..${sep}`)) return undefined
|
|
46
|
+
return toPosix(rel)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const gitVisibleFiles = (root: string): readonly string[] | undefined => {
|
|
50
|
+
try {
|
|
51
|
+
const inside = execFileSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
52
|
+
cwd: root,
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
55
|
+
}).trim()
|
|
56
|
+
if (inside !== 'true') return undefined
|
|
57
|
+
const output = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '.'], {
|
|
58
|
+
cwd: root,
|
|
59
|
+
encoding: 'utf8',
|
|
60
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
61
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
62
|
+
})
|
|
63
|
+
// Paths are relative to `cwd`. A nested repository shows up as `name/`; its files stay hidden.
|
|
64
|
+
return output.split('\0').filter(Boolean)
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const gitFilter = (root: string, canonicalRoot: string, visible: readonly string[]): IgnoreFilter => {
|
|
71
|
+
const files = new Set(visible.filter((path) => !path.endsWith('/')))
|
|
72
|
+
const directories = new Set<string>([''])
|
|
73
|
+
for (const file of visible) {
|
|
74
|
+
let parent = file
|
|
75
|
+
for (;;) {
|
|
76
|
+
const slash = parent.lastIndexOf('/')
|
|
77
|
+
if (slash === -1) break
|
|
78
|
+
parent = parent.slice(0, slash)
|
|
79
|
+
if (directories.has(parent)) break
|
|
80
|
+
directories.add(parent)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
mode: 'git',
|
|
85
|
+
isIgnored: (absolutePath, isDirectory) => {
|
|
86
|
+
const rel = below(root, resolve(absolutePath)) ?? below(canonicalRoot, canonical(absolutePath))
|
|
87
|
+
if (rel === undefined) return false
|
|
88
|
+
return isDirectory ? !directories.has(rel) : !files.has(rel)
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const gitignoreFilter = (root: string): IgnoreFilter => {
|
|
94
|
+
const rules = new Map<string, Ignore | undefined>()
|
|
95
|
+
const rulesFor = (directory: string): Ignore | undefined => {
|
|
96
|
+
if (rules.has(directory)) return rules.get(directory)
|
|
97
|
+
const file = join(directory, '.gitignore')
|
|
98
|
+
let matcher: Ignore | undefined
|
|
99
|
+
if (existsSync(file)) {
|
|
100
|
+
try {
|
|
101
|
+
matcher = ignore().add(readFileSync(file, 'utf8'))
|
|
102
|
+
} catch {
|
|
103
|
+
matcher = undefined
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
rules.set(directory, matcher)
|
|
107
|
+
return matcher
|
|
108
|
+
}
|
|
109
|
+
const ignoredCache = new Map<string, boolean>()
|
|
110
|
+
const isIgnored = (absolutePath: string, isDirectory: boolean): boolean => {
|
|
111
|
+
const target = resolve(absolutePath)
|
|
112
|
+
const rel = below(root, target)
|
|
113
|
+
if (rel === undefined || rel === '') return false
|
|
114
|
+
const key = `${isDirectory ? 'd' : 'f'}:${rel}`
|
|
115
|
+
const cached = ignoredCache.get(key)
|
|
116
|
+
if (cached !== undefined) return cached
|
|
117
|
+
// A path is ignored when an ancestor directory is ignored, or when the nearest applicable rule
|
|
118
|
+
// in any `.gitignore` between the root and its parent matches it (deeper files win).
|
|
119
|
+
const parent = dirname(target)
|
|
120
|
+
let result = parent !== root && below(root, parent) !== undefined ? isIgnored(parent, true) : false
|
|
121
|
+
if (!result) {
|
|
122
|
+
let decided: boolean | undefined
|
|
123
|
+
let directory = root
|
|
124
|
+
const segments = below(root, parent)?.split('/').filter(Boolean) ?? []
|
|
125
|
+
for (let index = 0; index <= segments.length; index += 1) {
|
|
126
|
+
if (index > 0) directory = join(directory, segments[index - 1] as string)
|
|
127
|
+
const matcher = rulesFor(directory)
|
|
128
|
+
if (!matcher) continue
|
|
129
|
+
const local = toPosix(relative(directory, target)) + (isDirectory ? '/' : '')
|
|
130
|
+
const verdict = matcher.test(local)
|
|
131
|
+
if (verdict.ignored) decided = true
|
|
132
|
+
else if (verdict.unignored) decided = false
|
|
133
|
+
}
|
|
134
|
+
result = decided ?? false
|
|
135
|
+
}
|
|
136
|
+
ignoredCache.set(key, result)
|
|
137
|
+
return result
|
|
138
|
+
}
|
|
139
|
+
return { mode: 'gitignore', isIgnored }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build the filter for one scan root. Call once per walk: the Git listing reflects the working
|
|
144
|
+
* tree at that moment, so a long-lived watcher rebuilds it on every rescan.
|
|
145
|
+
*/
|
|
146
|
+
export const createIgnoreFilter = (root: string): IgnoreFilter => {
|
|
147
|
+
const base = resolve(root)
|
|
148
|
+
const canonicalRoot = canonical(root)
|
|
149
|
+
const visible = gitVisibleFiles(canonicalRoot)
|
|
150
|
+
return visible ? gitFilter(base, canonicalRoot, visible) : gitignoreFilter(base)
|
|
151
|
+
}
|
package/src/lib/walk.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lstatSync, readdirSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
|
+
import { createIgnoreFilter } from './ignore-filter.js'
|
|
4
5
|
import { toPosix } from './paths.js'
|
|
5
6
|
|
|
6
7
|
const DEFAULT_SKIP = new Set(['node_modules', '.git', 'dist', 'coverage', '.doc-bridge'])
|
|
@@ -12,12 +13,19 @@ export const walkFiles = (
|
|
|
12
13
|
readonly extensions?: readonly string[]
|
|
13
14
|
readonly skipDirs?: ReadonlySet<string>
|
|
14
15
|
readonly maxFiles?: number
|
|
16
|
+
/**
|
|
17
|
+
* Skip what the repository ignores (`.gitignore`, `.git/info/exclude`, global excludes) on top
|
|
18
|
+
* of `skipDirs`. On by default; only walks of non-repository input, such as an agent memory
|
|
19
|
+
* directory, turn it off.
|
|
20
|
+
*/
|
|
21
|
+
readonly respectIgnore?: boolean
|
|
15
22
|
},
|
|
16
23
|
): string[] => {
|
|
17
24
|
const extensions = opts?.extensions ?? ['.md']
|
|
18
25
|
const skip = opts?.skipDirs ?? DEFAULT_SKIP
|
|
19
26
|
const out: string[] = []
|
|
20
27
|
const visited = new Set<string>()
|
|
28
|
+
const ignored = opts?.respectIgnore === false ? undefined : createIgnoreFilter(root)
|
|
21
29
|
|
|
22
30
|
const visit = (dir: string) => {
|
|
23
31
|
let canonicalDir: string
|
|
@@ -45,12 +53,12 @@ export const walkFiles = (
|
|
|
45
53
|
}
|
|
46
54
|
if (st.isSymbolicLink()) continue
|
|
47
55
|
if (st.isDirectory()) {
|
|
48
|
-
if (skip.has(name)) continue
|
|
56
|
+
if (skip.has(name) || ignored?.isIgnored(abs, true)) continue
|
|
49
57
|
visit(abs)
|
|
50
58
|
continue
|
|
51
59
|
}
|
|
52
60
|
if (!st.isFile()) continue
|
|
53
|
-
if (extensions.some((ext) => name.endsWith(ext))) {
|
|
61
|
+
if (extensions.some((ext) => name.endsWith(ext)) && !ignored?.isIgnored(abs, false)) {
|
|
54
62
|
if (out.length >= (opts?.maxFiles ?? DEFAULT_MAX_FILES)) {
|
|
55
63
|
throw new Error(`Documentation corpus exceeds the ${opts?.maxFiles ?? DEFAULT_MAX_FILES} file limit.`)
|
|
56
64
|
}
|
package/src/memory/ingest.ts
CHANGED
|
@@ -20,7 +20,7 @@ const ingestMarkdownDir = (
|
|
|
20
20
|
): MemoryCandidateV1[] => {
|
|
21
21
|
if (!existsSync(dir)) return []
|
|
22
22
|
|
|
23
|
-
return walkFiles(dir, { extensions: ['.md', '.mdc'] }).map((abs) => {
|
|
23
|
+
return walkFiles(dir, { extensions: ['.md', '.mdc'], respectIgnore: false }).map((abs) => {
|
|
24
24
|
const rel = relativePath(root, abs)
|
|
25
25
|
const raw = readFileSync(abs, 'utf8')
|
|
26
26
|
const id = slugFromPath(rel)
|
package/src/report/html.ts
CHANGED
|
@@ -424,7 +424,7 @@ const findingMatches=(finding)=>{const query=state.query.toLowerCase();return(!q
|
|
|
424
424
|
const findingScope=(finding)=>data.view?.diagnosticGroup?.[finding.id]?.[0]||(()=>{const ids=[...(finding.entityIds||[])];(finding.relationIds||[]).forEach((id)=>{const relation=relationById.get(id);if(relation)ids.push(relation.from,relation.to)});return ids.map(groupFor).sort()[0]||"repository"})();
|
|
425
425
|
const findingGroups=(findings)=>{const groups=new Map();findings.forEach((finding)=>{const scope=findingScope(finding),key=[scope,finding.code,finding.status,finding.severity].join("|"),group=groups.get(key)||{key,scope,code:finding.code,status:finding.status,severity:finding.severity,findings:[]};group.findings.push(finding);groups.set(key,group)});return[...groups.values()].sort((left,right)=>right.findings.length-left.findings.length||left.code.localeCompare(right.code)||left.key.localeCompare(right.key))};
|
|
426
426
|
const renderFinding=(finding)=>"<article class=\"finding\" id=\""+esc("diagnostic-"+finding.id.replace(/[^A-Za-z0-9_-]+/g,"-"))+"\"><div class=\"finding-head\"><h3>"+esc(finding.code)+"</h3><span><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> <span class=\"tag\">"+esc(finding.status)+"</span></span></div><p>"+esc(finding.message)+"</p>"+(finding.evidence.length?"<ul>"+finding.evidence.slice(0,4).map((item)=>"<li>"+esc(item.path+(item.lineStart?":"+item.lineStart:"")+(item.context?" — "+item.context:""))+"</li>").join("")+"</ul>":"")+(finding.remediation?"<p><strong>Next check:</strong> "+esc(finding.remediation)+"</p>":"")+"</article>";
|
|
427
|
-
const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const itemLabel=state.lens==="evidence"?"evidence checks":"findings",groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+" "+itemLabel+" · "+groups.length+" groups";document.querySelector("#findings").innerHTML=findings.length?"<div class=\"finding-groups\">"+groups.map((group)=>"<article class=\"finding-group\"><div class=\"finding-head\"><h3>"+esc(group.code)+"</h3><span><span class=\"tag "+esc(group.severity)+"\">"+esc(group.severity)+"</span> <span class=\"tag\">"+esc(group.status)+"</span></span></div><p><strong>"+group.findings.length+"</strong> "+itemLabel+" · "+esc(groupById.get(group.scope)?.name||group.scope)+"</p><p>"+esc(group.findings[0].message)+"</p><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(group.key)+"\" aria-expanded=\""+String(selected?.key===group.key)+"\">Inspect group</button></article>").join("")+"</div>"+(selected?"<div class=\"finding-detail\"><div class=\"finding-head\"><h3>"+esc(selected.code)+" · "+esc(groupById.get(selected.scope)?.name||selected.scope)+"</h3><span class=\"subtle\">"+selected.findings.length+" "+itemLabel+"</span></div>"+selected.findings.slice(start,start+pageSize).map(renderFinding).join("")+"<div class=\"finding-actions\"><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page-1)+"\" "+(page===0?"disabled":"")+">Previous</button><span class=\"subtle\">Showing "+(start+1)+"–"+Math.min(start+pageSize,selected.findings.length)+" of "+selected.findings.length+" · page "+(page+1)+"/"+pageCount+"</span><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page+1)+"\" "+(page+1>=pageCount?"disabled":"")+">Next</button></div></div>":""):"<p class=\"empty\">No "+itemLabel+" match the current lens and filters.</p>"};
|
|
427
|
+
const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const itemLabel=state.lens==="evidence"?"evidence checks":"findings",groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+" "+itemLabel+" · "+groups.length+" groups";document.querySelector("#findings").innerHTML=findings.length?"<div class=\"finding-groups\">"+groups.map((group)=>"<article class=\"finding-group\"><div class=\"finding-head\"><h3>"+esc(group.code)+"</h3><span><span class=\"tag "+esc(group.severity)+"\">"+esc(group.severity)+"</span> <span class=\"tag\">"+esc(group.status)+"</span></span></div><p><strong>"+group.findings.length+"</strong> "+itemLabel+" · "+esc(groupById.get(group.scope)?.name||group.scope)+"</p><p>"+esc(group.findings[0].message)+"</p><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(group.key)+"\" aria-expanded=\""+String(selected?.key===group.key)+"\">Inspect group</button></article>").join("")+"</div>"+(selected?"<div class=\"finding-detail\"><div class=\"finding-head\"><h3>"+esc(selected.code)+" · "+esc(groupById.get(selected.scope)?.name||selected.scope)+"</h3><span class=\"subtle\">"+selected.findings.length+" "+itemLabel+"</span></div>"+selected.findings.slice(start,start+pageSize).map(renderFinding).join("")+"<div class=\"finding-actions\"><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page - 1)+"\" "+(page===0?"disabled":"")+">Previous</button><span class=\"subtle\">Showing "+(start+1)+"–"+Math.min(start+pageSize,selected.findings.length)+" of "+selected.findings.length+" · page "+(page+1)+"/"+pageCount+"</span><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page+1)+"\" "+(page+1>=pageCount?"disabled":"")+">Next</button></div></div>":""):"<p class=\"empty\">No "+itemLabel+" match the current lens and filters.</p>"};
|
|
428
428
|
const renderCoverage=()=>{document.querySelector("#coverage-list").innerHTML=(data.coverage||[]).map((entry)=>{const width=entry.status==="complete"?100:entry.status==="partial"?55:12;return"<div class=\"coverage-row\"><div><b>"+esc(entry.analyzer)+"</b><br><span class=\"subtle\">"+esc(entry.scope)+"</span></div><div class=\"bar\"><i class=\""+(entry.status==="complete"?"":entry.status==="partial"?"partial":"none")+"\" style=\"width:"+width+"%\"></i></div><div>"+esc(entry.status)+"</div></div>"}).join("")||"<p class=\"empty\">No coverage metadata.</p>"};
|
|
429
429
|
const deferFindings=()=>{if(findingsLoaded())return;const actionable=data.actionableCount??data.diagnosticCount??0,confirmed=data.confirmedCount??0;document.querySelector("#finding-count").textContent=actionable+" actionable findings · "+confirmed+" confirmed checks available on demand";document.querySelector("#findings").innerHTML="<div class=\"empty\"><p>Findings stay out of the first paint so large repositories remain responsive. Confirmed checks are evidence, not issues.</p><button id=\"load-findings\" class=\"tab\" type=\"button\">Load findings</button></div>"};
|
|
430
430
|
const scopePrompt=()=>{if((state.level==="module"||state.level==="file")&&!state.selected){document.querySelector("#map-note").textContent="Select an app or package to inspect this level.";document.querySelector("#graph").innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>"}};
|
package/src/safety/repository.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { isAbsolute, relative, resolve, sep } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { minimatch } from 'minimatch'
|
|
5
5
|
|
|
6
|
+
import { createIgnoreFilter } from '../lib/ignore-filter.js'
|
|
7
|
+
|
|
6
8
|
export const DEFAULT_SAFETY_EXCLUDES = ['**/.git/**', '**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**', '**/.doc-bridge/**', '**/.next/**', '**/out/**', '**/.turbo/**', '**/.svelte-kit/**', '**/.mcpb-build/**', '**/.mcpb-output/**', '**/.env', '**/.env.*', '**/*secret*', '**/*credential*', '**/*.pem', '**/*.key'] as const
|
|
7
9
|
|
|
8
10
|
export type SafeWalkOptions = {
|
|
@@ -12,6 +14,11 @@ export type SafeWalkOptions = {
|
|
|
12
14
|
readonly maxBytes?: number
|
|
13
15
|
readonly maxTimeMs?: number
|
|
14
16
|
readonly maxMemoryMb?: number
|
|
17
|
+
/**
|
|
18
|
+
* Skip what the repository ignores (Git ignore rules, or `.gitignore` files outside Git) in
|
|
19
|
+
* addition to `exclude`, so build output never reaches the index. Defaults to true.
|
|
20
|
+
*/
|
|
21
|
+
readonly respectIgnore?: boolean
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
export type SafeWalkResult = {
|
|
@@ -43,6 +50,7 @@ export const safeWalkFiles = (root: string, options: SafeWalkOptions = {}): Safe
|
|
|
43
50
|
let reason: string | undefined
|
|
44
51
|
const started = Date.now()
|
|
45
52
|
const matchesExclude = (path: string): boolean => excludes.some((pattern) => minimatch(path, pattern, { dot: true }))
|
|
53
|
+
const ignored = options.respectIgnore === false ? undefined : createIgnoreFilter(projectRoot)
|
|
46
54
|
const visit = (directory: string): void => {
|
|
47
55
|
if (reason) return
|
|
48
56
|
if (options.maxTimeMs !== undefined && Date.now() - started >= options.maxTimeMs) { reason = `Repository scan exceeded the ${options.maxTimeMs} ms time limit.`; return }
|
|
@@ -56,6 +64,7 @@ export const safeWalkFiles = (root: string, options: SafeWalkOptions = {}): Safe
|
|
|
56
64
|
let stats
|
|
57
65
|
try { stats = lstatSync(absolute) } catch { continue }
|
|
58
66
|
if (stats.isSymbolicLink()) continue
|
|
67
|
+
if (ignored?.isIgnored(absolute, stats.isDirectory())) continue
|
|
59
68
|
if (stats.isDirectory()) { visit(absolute); if (reason) return; continue }
|
|
60
69
|
if (!stats.isFile() || (extensions.length > 0 && !extensions.some((extension) => name.endsWith(extension)))) continue
|
|
61
70
|
if (files.length >= (options.maxFiles ?? 10_000)) { reason = `Repository scan exceeded the ${options.maxFiles ?? 10_000} file limit.`; return }
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.
|
|
1
|
+
export const PACKAGE_VERSION = '1.12.0'
|
package/docs/DOGFOOD-ROUND2.md
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Dogfood round 2
|
|
3
|
-
description: Validation evidence from the second published Doc Bridge alpha.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Dogfood round 2 — published `@agentskit/doc-bridge@0.1.0-alpha.2`
|
|
7
|
-
|
|
8
|
-
**Date:** 2026-07-09
|
|
9
|
-
**Install:** npm registry tag `alpha` → `0.1.0-alpha.2`
|
|
10
|
-
**Consumers:** agentskit · agentskit-os · agents-playbook · agentskit-registry
|
|
11
|
-
|
|
12
|
-
## Scoreboard
|
|
13
|
-
|
|
14
|
-
| Repo | Install | Version | Knowledge | Handoffs | Gate | Sample handoff quality |
|
|
15
|
-
|------|---------|---------|-----------|----------|------|------------------------|
|
|
16
|
-
| **agentskit** | `pnpm add -Dw @0.1.0-alpha.2` ✅ | 0.1.0-alpha.2 | 25 | 24 | ✅ freshness + 23 humanDoc | Excellent: pnpm filter checks + humanDoc `/docs/reference/packages/core` |
|
|
17
|
-
| **agentskit-os** | pnpm store conflict; used **`npx`** ✅ | 0.1.0-alpha.2 | 170 | 92 | ✅ freshness + 9 humanDoc | Good act path; weak human bridge (9/92) |
|
|
18
|
-
| **agents-playbook** | `pnpm add -D` ✅ | 0.1.0-alpha.2 | 127 | 81 | ✅ freshness + okf-type | Pattern ownership + `check:okf-type` ✅ |
|
|
19
|
-
| **agentskit-registry** | needs `--legacy-peer-deps` ⚠️ | 0.1.0-alpha.2 | 36 | 36 | ✅ freshness | Full agent ownership + humanDoc ✅ |
|
|
20
|
-
|
|
21
|
-
### npm dist-tags (observed)
|
|
22
|
-
|
|
23
|
-
| Tag | Version |
|
|
24
|
-
|-----|---------|
|
|
25
|
-
| `latest` | `0.1.0-alpha.1` |
|
|
26
|
-
| `alpha` | `0.1.0-alpha.2` |
|
|
27
|
-
|
|
28
|
-
**Recommendation:** keep prereleases only on `alpha` until stable; document `npm i -D @agentskit/doc-bridge@alpha`.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## What works well (validated)
|
|
33
|
-
|
|
34
|
-
1. **Published binary works** — `ak-docs 0.1.0-alpha.2` from registry / npx.
|
|
35
|
-
2. **Monorepo handoffs** — `editRoots` + `pnpm --filter @agentskit/<pkg> test|lint` on agentskit and AKOS.
|
|
36
|
-
3. **Playbook preset** — gates green without crushing large OKF corpus.
|
|
37
|
-
4. **Registry ownership** — all 36 agents resolve; checks use `npm run validate`.
|
|
38
|
-
5. **Fumadocs humanDoc** on agentskit points at **reference** docs, not nested for-agents.
|
|
39
|
-
6. **Pattern checks** — playbook patterns emit `pnpm run check:okf-type`.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Issues found (prioritized)
|
|
44
|
-
|
|
45
|
-
### P0 — correctness / ranking (agents will take wrong action)
|
|
46
|
-
|
|
47
|
-
| ID | Issue | Evidence | Suggested fix |
|
|
48
|
-
|----|--------|----------|----------------|
|
|
49
|
-
| **R2-1** | **Search/ask ranking ignores exact package id** | `search core --agent` bestMatch = **angular** (score 8) tied with **core** (score 8) because summary contains `@agentskit/core` | Boost exact `id` / path segment matches; prefer ownership id === term before body mentions |
|
|
50
|
-
| **R2-2** | **`ask` suggests wrong handoff** | “where do I change the **core** package?” → best match **angular**, next `query ownership angular` | Same ranking fix; optional: prefer ownership over knowledge for “package/module” language |
|
|
51
|
-
| **R2-3** | **Purpose/notes truncated mid-sentence** | core notes: `"Stable TypeScript contracts (Adapter, Tool, Memory, Retriever, Skill,"` | Raise firstParagraph budget; strip trailing incomplete commas; prefer frontmatter `purpose` full line |
|
|
52
|
-
|
|
53
|
-
### P1 — install / peer DX
|
|
54
|
-
|
|
55
|
-
| ID | Issue | Evidence | Suggested fix |
|
|
56
|
-
|----|--------|----------|----------------|
|
|
57
|
-
| **R2-4** | **Optional peers still break npm install** | registry: `ERESOLVE` with optional peers vs `@agentskit/adapters@0.12.x` | Peer ranges more permissive (`>=0.12` / `>=1.0`); document `--legacy-peer-deps`; ensure optional peers never hard-fail Layer 0 |
|
|
58
|
-
| **R2-5** | **Peer `@agentskit/core@^1.10.0` vs monorepo 1.9.0** | pnpm warning on agentskit install | Align peer to `^1.9.0 \|\| ^1.10.0` or `>=1.9.0 <2` for dogfood until monorepo bumps |
|
|
59
|
-
| **R2-6** | **`latest` still points at alpha.1** | `npm view` latest=alpha.1, alpha=alpha.2 | Publish hygiene: don’t put prereleases on `latest`, or retag after publish |
|
|
60
|
-
| **R2-7** | **AKOS cannot `pnpm add` (store v10 vs v11)** | `ERR_PNPM_UNEXPECTED_STORE` | Docs: use npx / align store; not a doc-bridge bug but dogfood friction |
|
|
61
|
-
|
|
62
|
-
### P2 — search / retrieval quality
|
|
63
|
-
|
|
64
|
-
| ID | Issue | Evidence | Suggested fix |
|
|
65
|
-
|----|--------|----------|----------------|
|
|
66
|
-
| **R2-8** | **Search miss on common English** | agentskit `search documentation` → **0 matches** | Index more body text / titles; lower threshold; BM25 over full file not only description |
|
|
67
|
-
| **R2-9** | **Playbook `search handoff` → 0** | term may only appear deep in body | Same as R2-8; chunk full markdown into search corpus |
|
|
68
|
-
| **R2-10** | **Federation hard-fail noise** | `retrieve` → `Failed to fetch https://registry.agentskit.io/llms.txt: 404` | Soft-fail federation sources; warn once; don’t fail command |
|
|
69
|
-
| **R2-11** | **Text search UX** | tab-separated dense lines | Columnar/TTY formatting (`type id path summary`) |
|
|
70
|
-
|
|
71
|
-
### P3 — bridge coverage & product polish
|
|
72
|
-
|
|
73
|
-
| ID | Issue | Evidence | Suggested fix |
|
|
74
|
-
|----|--------|----------|----------------|
|
|
75
|
-
| **R2-12** | **AKOS humanDoc still sparse** | 9/92 handoffs with humanDoc | Map for-agents frontmatter `humanDoc`; product Fumadocs catalog; optional plain-markdown `docs/errors` etc. |
|
|
76
|
-
| **R2-13** | **Duplicate knowledge+ownership rows in search** | same path appears as ownership + knowledge | Deduplicate by path; show ownership preferred |
|
|
77
|
-
| **R2-14** | **Default checks for patterns without okf script** | N/A playbook ok | Fine; document convention |
|
|
78
|
-
| **R2-15** | **MCP not re-smoked this round** | — | Keep in CI smoke; optional round 3 |
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Sample outputs (good)
|
|
83
|
-
|
|
84
|
-
### agentskit — `query package core --agent`
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"startHere": "apps/docs-next/content/docs/for-agents/core.mdx",
|
|
89
|
-
"editRoots": ["packages/core"],
|
|
90
|
-
"checks": [
|
|
91
|
-
"pnpm --filter @agentskit/core test",
|
|
92
|
-
"pnpm --filter @agentskit/core lint"
|
|
93
|
-
],
|
|
94
|
-
"humanDoc": "/docs/reference/packages/core"
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### playbook — pattern handoff
|
|
99
|
-
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"startHere": "content/docs/pillars/ai-collaboration/open-knowledge-format-pattern.md",
|
|
103
|
-
"checks": ["pnpm run check:okf-type"],
|
|
104
|
-
"humanDoc": "/docs/pillars/ai-collaboration/open-knowledge-format-pattern"
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### registry — `docs-chat`
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"startHere": "registry/docs-chat/README.md",
|
|
113
|
-
"editRoots": ["registry/docs-chat"],
|
|
114
|
-
"checks": ["npm run validate", "npm test"],
|
|
115
|
-
"humanDoc": "/agents/docs-chat"
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Recommended next alpha (0.1.0-alpha.3) scope
|
|
122
|
-
|
|
123
|
-
1. **Ranking:** exact id / basename boost; package-intent heuristics for `ask`
|
|
124
|
-
2. **Search corpus:** full-text over body (not only description); dedupe paths
|
|
125
|
-
3. **Descriptions:** full purpose from frontmatter or first complete paragraph
|
|
126
|
-
4. **Peers:** widen optional peer ranges; Layer 0 install never requires AgentsKit packages
|
|
127
|
-
5. **Federation:** soft-fail missing `llms.txt` sources
|
|
128
|
-
6. **Publish:** keep `latest` free of broken prereleases; doc install as `@alpha`
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Consumer follow-ups (not doc-bridge code)
|
|
133
|
-
|
|
134
|
-
| Repo | Action |
|
|
135
|
-
|------|--------|
|
|
136
|
-
| agentskit | Bump `@agentskit/core` to 1.10+ or live with peer warning |
|
|
137
|
-
| agentskit-os | Align pnpm store / commit lock with published dep; add CI `docs:bridge:gate` |
|
|
138
|
-
| agents-playbook | Optional CI workflow for gate |
|
|
139
|
-
| agentskit-registry | Publish `public/llms.txt` at site root (404 today); use `--legacy-peer-deps` until peer ranges loosen |
|
|
140
|
-
| all | After alpha.3: switch dep to `@agentskit/doc-bridge@alpha` only |
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Conclusion
|
|
145
|
-
|
|
146
|
-
**Round 2 proves Layer 0 is useful on real monorepos and OKF/registry corpora with the published package.**
|
|
147
|
-
The biggest remaining gap for “agents do the right thing” is **discovery ranking (R2-1/R2-2)** and **search body coverage (R2-8/R2-9)** — not indexing or handoff structure.
|
package/docs/DOGFOOD-ROUND3.md
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Dogfood round 3
|
|
3
|
-
description: Validation evidence from the third published Doc Bridge alpha.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Dogfood round 3 — `@agentskit/doc-bridge@0.1.0-alpha.3`
|
|
7
|
-
|
|
8
|
-
**Date:** 2026-07-09
|
|
9
|
-
**npm:** `alpha` → `0.1.0-alpha.3` · `latest` still `0.1.0-alpha.1`
|
|
10
|
-
|
|
11
|
-
## Scoreboard
|
|
12
|
-
|
|
13
|
-
| Repo | Install | Ver | K | Handoffs | Gate | Ranking | Ask | Retrieve |
|
|
14
|
-
|------|---------|-----|---|----------|------|---------|-----|----------|
|
|
15
|
-
| **agentskit** | `pnpm add -Dw` ✅ | 0.1.0-alpha.3 | 25 | 24 | ✅ 24 humanDoc | `core` #1 (683) ✅ | ownership core ✅ | ✅ soft, 8 chunks |
|
|
16
|
-
| **agentskit-os** | `npx` (store issue) ✅ | 0.1.0-alpha.3 | 170 | 92 | ✅ | `os-core` #1 (689) ✅ | ownership os-core ✅ | ✅ 8 chunks |
|
|
17
|
-
| **playbook** | `pnpm add -D` ✅ | 0.1.0-alpha.3 | 127 | 81 | ✅ okf | OKF pattern #1 ✅ | pattern ownership ✅ | ✅ |
|
|
18
|
-
| **registry** | `--legacy-peer-deps` ⚠️ | 0.1.0-alpha.3 | 36 | 36 | ✅ | `docs-chat` #1 (593) ✅ | docs-chat ✅ | ✅ |
|
|
19
|
-
|
|
20
|
-
## Round-2 issues — validation
|
|
21
|
-
|
|
22
|
-
| Item (R2) | Status | Evidence |
|
|
23
|
-
|-----------|--------|----------|
|
|
24
|
-
| Exact id ranking | **Fixed** | agentskit `search core` → best=`core` not angular |
|
|
25
|
-
| ask wrong package | **Fixed** | “change the core package?” → ownership core |
|
|
26
|
-
| Notes truncated | **Fixed** | full purpose sentences on core / os-core |
|
|
27
|
-
| Full-text search | **Improved** | `search documentation` → 20 matches (was 0) |
|
|
28
|
-
| Federation 404 hard-fail | **Fixed** | retrieve exit 0, no error, local chunks only |
|
|
29
|
-
| Peer install friction | **Improved** | no peer warning on agentskit; registry still needs legacy-peer-deps |
|
|
30
|
-
| Text UX | **Fixed** | multi-line `[ownership] id score=` format |
|
|
31
|
-
| Path dedupe | **OK** | top lists are unique ownership rows |
|
|
32
|
-
| AKOS humanDoc sparse | **Still open** | os-core handoff has no `humanDoc` (product docs thin) |
|
|
33
|
-
| pnpm store AKOS | **Still open** | environment; npx works |
|
|
34
|
-
| `latest` on old alpha | **Still open** | publish hygiene |
|
|
35
|
-
|
|
36
|
-
## Sample outputs (alpha.3)
|
|
37
|
-
|
|
38
|
-
### agentskit — ranking
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
{ "term": "core", "best": "core", "top": ["core:683", "angular:27", "ink:27"] }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### agentskit — handoff
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"startHere": "apps/docs-next/content/docs/for-agents/core.mdx",
|
|
49
|
-
"editRoots": ["packages/core"],
|
|
50
|
-
"checks": ["pnpm --filter @agentskit/core test", "pnpm --filter @agentskit/core lint"],
|
|
51
|
-
"humanDoc": "/docs/reference/packages/core",
|
|
52
|
-
"notes": ["Stable TypeScript contracts (Adapter, Tool, Memory, Retriever, Skill, Runtime) + `createChatController` + primitives. Target: <10 KB gzipped, zero runtime deps."]
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### agentskit-os — ranking
|
|
57
|
-
|
|
58
|
-
```json
|
|
59
|
-
{ "best": "os-core", "top": ["os-core:689", "command-palette:36", "os-blob:36"] }
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### registry — ranking
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
{ "best": "docs-chat", "top": ["docs-chat:593", "agency-brief-generator:15"] }
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Remaining improvements (lower urgency)
|
|
69
|
-
|
|
70
|
-
1. **AKOS humanDoc coverage** — enrich for-agents frontmatter `humanDoc` or map product Fumadocs more densely (content work more than code).
|
|
71
|
-
2. **npm install without legacy-peer-deps** on registry — may still need peerOptional tuning vs npm ERESOLVE; document install flags.
|
|
72
|
-
3. **HTML entities in notes** — `<10 KB` from source MD; optional decode on index.
|
|
73
|
-
4. **AKOS lockfile install path** — fix local pnpm store / commit published dep version.
|
|
74
|
-
5. **CI wire-up** — `docs:bridge:gate` on each consumer after merge of dogfood branches.
|
|
75
|
-
6. **Publish tags** — keep `latest` free of prereleases or retag intentionally.
|
|
76
|
-
|
|
77
|
-
## Verdict
|
|
78
|
-
|
|
79
|
-
**alpha.3 is a successful dogfood pass.** P0/P1 product issues from round 2 are validated fixed on published package across monorepo + playbook + registry. Remaining items are polish, content, or consumer-side install/CI.
|