@agentskit/doc-bridge 1.6.4 → 1.7.45
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 +249 -0
- package/CONTRIBUTING.md +6 -4
- package/action.yml +1 -1
- package/dist/cli/program.js +1139 -294
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +43 -5
- package/dist/config/index.js.map +1 -1
- package/dist/index-BUL0q7s8.d.ts +660 -0
- package/dist/index.d.ts +817 -2134
- package/dist/index.js +1154 -244
- package/dist/index.js.map +1 -1
- package/docs/PRD-enterprise-hardening.md +288 -0
- package/docs/RELEASE.md +22 -8
- package/docs/adr/0001-enterprise-verification-contract.md +35 -0
- package/docs/agent-corpus/INDEX.md +2 -2
- package/docs/agent-corpus/chat.md +2 -2
- package/docs/agent-corpus/cli.md +2 -2
- package/docs/agent-corpus/conformance.md +2 -2
- package/docs/agent-corpus/doc-bridge.md +1 -1
- package/docs/agent-corpus/doctor.md +2 -2
- package/docs/agent-corpus/gates.md +2 -2
- package/docs/agent-corpus/mcp.md +2 -2
- package/docs/agent-corpus/memory.md +2 -2
- package/docs/agent-corpus/query.md +2 -2
- package/docs/knowledge-engine-runbook.md +30 -2
- package/docs/spec/analyzer-plugin-v1.md +24 -0
- package/docs/spec/benchmark-v1.md +36 -0
- package/docs/spec/config-v1.md +156 -0
- package/docs/validation-cycle-plan.md +255 -0
- package/docs/verification-harness.md +37 -4
- package/mcpb/manifest.json +1 -1
- package/package.json +68 -70
- package/scripts/check-ecosystem-upstream.mjs +3 -2
- package/scripts/report-visual-check.mjs +64 -12
- package/scripts/verification-harness.mjs +216 -14
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +31 -7
- package/src/cli/demo.ts +2 -2
- package/src/cli/program.ts +59 -16
- package/src/config/index.ts +2 -0
- package/src/config/load-config.ts +7 -1
- package/src/config/schema.ts +60 -2
- package/src/conformance/documentation-standard-v1.ts +14 -8
- package/src/discovery/documentation.ts +90 -23
- package/src/discovery/repository.ts +147 -19
- package/src/doctor/run-doctor.ts +2 -15
- package/src/federation/llms.ts +72 -20
- package/src/fixes/proposals.ts +4 -3
- package/src/index-builder/human-adapters/fumadocs.ts +1 -1
- package/src/index-builder/watch-index.ts +1 -1
- package/src/index.ts +29 -0
- package/src/lib/bounded-text.ts +15 -10
- package/src/metrics/benchmark.ts +176 -0
- package/src/plugins/contract.ts +89 -0
- package/src/reconciliation/reconcile.ts +181 -5
- package/src/report/html.ts +318 -88
- package/src/rules/engine.ts +15 -2
- package/src/safety/repository.ts +1 -1
- package/src/schemas/knowledge.ts +21 -3
- package/src/validate.ts +7 -1
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +65 -9
- package/dist/index-DudNuwI5.d.ts +0 -2060
package/src/rules/engine.ts
CHANGED
|
@@ -176,5 +176,18 @@ export const evaluateRules = (
|
|
|
176
176
|
return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === 'error') ? 1 : 0 }
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
export const parseRuleId = (value: string): RuleId =>
|
|
180
|
-
|
|
179
|
+
export const parseRuleId = (value: string): RuleId => {
|
|
180
|
+
try {
|
|
181
|
+
return RuleIdSchema.parse(value)
|
|
182
|
+
} catch {
|
|
183
|
+
throw new Error(`Invalid enum value: ${value}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const parseRuleSeverity = (value: string): RuleSeverity => {
|
|
188
|
+
try {
|
|
189
|
+
return RuleSeveritySchema.parse(value)
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error(`Invalid enum value: ${value}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/safety/repository.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { isAbsolute, relative, resolve, sep } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { minimatch } from 'minimatch'
|
|
5
5
|
|
|
6
|
-
export const DEFAULT_SAFETY_EXCLUDES = ['**/.git/**', '**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**', '**/.doc-bridge/**', '**/.turbo/**', '**/.env', '**/.env.*', '**/*secret*', '**/*credential*', '**/*.pem', '**/*.key'] as const
|
|
6
|
+
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
7
|
|
|
8
8
|
export type SafeWalkOptions = {
|
|
9
9
|
readonly extensions?: readonly string[]
|
package/src/schemas/knowledge.ts
CHANGED
|
@@ -51,11 +51,12 @@ export const EvidenceSchema = z
|
|
|
51
51
|
})
|
|
52
52
|
export type Evidence = z.infer<typeof EvidenceSchema>
|
|
53
53
|
|
|
54
|
-
export const CoverageStatusSchema = z.enum(['complete', 'partial', 'not-analyzed'])
|
|
54
|
+
export const CoverageStatusSchema = z.enum(['complete', 'partial', 'not-analyzed', 'not-applicable'])
|
|
55
55
|
|
|
56
56
|
export const CoverageSchema = z
|
|
57
57
|
.object({
|
|
58
58
|
analyzer: boundedString(128),
|
|
59
|
+
analyzerVersion: boundedString(64).optional(),
|
|
59
60
|
scope: boundedString(512),
|
|
60
61
|
status: CoverageStatusSchema,
|
|
61
62
|
reason: z.string().max(1_024).optional(),
|
|
@@ -92,7 +93,7 @@ export const EntitySchema = z
|
|
|
92
93
|
aliases: z.array(boundedString(256)).max(32).optional(),
|
|
93
94
|
provenance: ProvenanceSchema,
|
|
94
95
|
evidence: z.array(EvidenceSchema).max(64),
|
|
95
|
-
metadata: z.record(z.unknown()).optional(),
|
|
96
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
96
97
|
})
|
|
97
98
|
.strict()
|
|
98
99
|
export type KnowledgeEntity = z.infer<typeof EntitySchema>
|
|
@@ -106,7 +107,7 @@ export const RelationSchema = z
|
|
|
106
107
|
discriminator: boundedString(256).optional(),
|
|
107
108
|
provenance: ProvenanceSchema,
|
|
108
109
|
evidence: z.array(EvidenceSchema).max(64),
|
|
109
|
-
metadata: z.record(z.unknown()).optional(),
|
|
110
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
110
111
|
})
|
|
111
112
|
.strict()
|
|
112
113
|
export type KnowledgeRelation = z.infer<typeof RelationSchema>
|
|
@@ -165,7 +166,24 @@ export const ReconciliationReportV1Schema = z
|
|
|
165
166
|
entityCount: z.number().int().nonnegative(),
|
|
166
167
|
relationCount: z.number().int().nonnegative(),
|
|
167
168
|
diagnosticCount: z.number().int().nonnegative(),
|
|
169
|
+
scope: z.enum(['file', 'module', 'package']).optional(),
|
|
168
170
|
requiredRelationKinds: z.array(boundedString(128)).max(128).optional(),
|
|
171
|
+
requiredRelationTargets: z.enum(['all', 'internal']).optional(),
|
|
172
|
+
diagnosticsByCode: z.record(z.string().max(128), z.number().int().nonnegative()).optional(),
|
|
173
|
+
diagnosticsByStatus: z.record(z.string().max(128), z.number().int().nonnegative()).optional(),
|
|
174
|
+
documentation: z.object({
|
|
175
|
+
documentCount: z.number().int().nonnegative(),
|
|
176
|
+
documentedDocumentCount: z.number().int().nonnegative(),
|
|
177
|
+
documentClassificationCounts: z.record(z.string().max(128), z.number().int().nonnegative()),
|
|
178
|
+
documentedDocumentClassificationCounts: z.record(z.string().max(128), z.number().int().nonnegative()),
|
|
179
|
+
packageCount: z.number().int().nonnegative(),
|
|
180
|
+
packageStatus: z.object({
|
|
181
|
+
fresh: z.number().int().nonnegative(),
|
|
182
|
+
stale: z.number().int().nonnegative(),
|
|
183
|
+
missing: z.number().int().nonnegative(),
|
|
184
|
+
unverified: z.number().int().nonnegative(),
|
|
185
|
+
}).strict(),
|
|
186
|
+
}).strict().optional(),
|
|
169
187
|
})
|
|
170
188
|
.strict(),
|
|
171
189
|
})
|
package/src/validate.ts
CHANGED
|
@@ -36,10 +36,16 @@ export type ParseResult<T> =
|
|
|
36
36
|
| { readonly ok: true; readonly value: T }
|
|
37
37
|
| { readonly ok: false; readonly issues: readonly ParseIssue[] }
|
|
38
38
|
|
|
39
|
+
const zodMessage = (issue: ZodError['issues'][number]): string => {
|
|
40
|
+
if (issue.code === 'invalid_type' && issue.message.endsWith('received undefined')) return 'Required'
|
|
41
|
+
if (issue.code === 'invalid_value' && 'values' in issue) return 'Invalid enum value'
|
|
42
|
+
return issue.message
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
const zodIssues = (error: ZodError): readonly ParseIssue[] =>
|
|
40
46
|
error.issues.map((issue) => ({
|
|
41
47
|
path: issue.path.join('.') || '(root)',
|
|
42
|
-
message: issue
|
|
48
|
+
message: zodMessage(issue),
|
|
43
49
|
}))
|
|
44
50
|
|
|
45
51
|
export const safeParseAgentHandoff = (input: unknown): ParseResult<AgentHandoffV1> => {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '1.
|
|
1
|
+
export const PACKAGE_VERSION = '1.7.45'
|
package/src/workflow/engine.ts
CHANGED
|
@@ -22,10 +22,13 @@ export type WorkflowOptions = {
|
|
|
22
22
|
readonly sourceRevision: string
|
|
23
23
|
readonly configurationHash: string
|
|
24
24
|
readonly toolVersion?: string
|
|
25
|
+
readonly pipelineVersion?: string
|
|
26
|
+
readonly analyzerVersions?: Readonly<Record<string, string>>
|
|
25
27
|
readonly runId?: string
|
|
26
28
|
readonly stage?: WorkflowStage | 'all'
|
|
27
29
|
readonly inputs?: Partial<Record<WorkflowStage, unknown>>
|
|
28
30
|
readonly handlers: Partial<Record<WorkflowStage, WorkflowStageHandler>>
|
|
31
|
+
readonly shouldCancel?: () => boolean
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export type WorkflowExecutionResult = {
|
|
@@ -67,6 +70,22 @@ const appendTransition = (stateDir: string, transition: WorkflowRunV1['transitio
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
const transition = (run: WorkflowRunV1, to: WorkflowState, reason?: string): WorkflowRunV1 => {
|
|
73
|
+
const allowed: Readonly<Record<WorkflowState, readonly WorkflowState[]>> = {
|
|
74
|
+
created: ['created', 'discovering', 'failed', 'cancelled'],
|
|
75
|
+
discovering: ['discovering', 'analyzed', 'failed', 'cancelled', 'stale'],
|
|
76
|
+
analyzed: ['analyzed', 'compared', 'failed', 'cancelled', 'stale'],
|
|
77
|
+
compared: ['compared', 'proposed', 'failed', 'cancelled', 'stale'],
|
|
78
|
+
'awaiting-agent': ['awaiting-agent', 'proposed', 'failed', 'cancelled', 'stale'],
|
|
79
|
+
proposed: ['proposed', 'validating', 'delivered', 'failed', 'cancelled', 'stale'],
|
|
80
|
+
'awaiting-approval': ['awaiting-approval', 'validating', 'failed', 'cancelled', 'stale'],
|
|
81
|
+
validating: ['validating', 'delivered', 'failed', 'cancelled', 'stale'],
|
|
82
|
+
delivered: ['delivered', 'stale', 'failed', 'cancelled'],
|
|
83
|
+
failed: ['failed', 'discovering', 'analyzed', 'compared', 'proposed', 'validating', 'delivered', 'cancelled'],
|
|
84
|
+
cancelled: ['cancelled', 'discovering', 'analyzed', 'compared', 'proposed', 'validating', 'delivered'],
|
|
85
|
+
stale: [],
|
|
86
|
+
superseded: [],
|
|
87
|
+
}
|
|
88
|
+
if (!allowed[run.state].includes(to)) throw new Error(`Illegal workflow transition ${run.state} -> ${to}.`)
|
|
70
89
|
const item = {
|
|
71
90
|
from: run.state,
|
|
72
91
|
to,
|
|
@@ -79,18 +98,30 @@ const transition = (run: WorkflowRunV1, to: WorkflowState, reason?: string): Wor
|
|
|
79
98
|
const runId = (): string => `${Date.now()}-${process.pid}`
|
|
80
99
|
|
|
81
100
|
const stageInputHash = (options: WorkflowOptions, stage: WorkflowStage, input: unknown): string =>
|
|
82
|
-
sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? '1.0.0' })
|
|
101
|
+
sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, pipelineVersion: options.pipelineVersion ?? '1.0.0', analyzerVersions: options.analyzerVersions ?? {}, toolVersion: options.toolVersion ?? '1.0.0' })
|
|
83
102
|
|
|
84
103
|
const stageArtifactPath = (stateDir: string, stage: WorkflowStage, inputHash: string): string => join(stateDir, 'artifacts', `${stage}-${inputHash}.json`)
|
|
85
104
|
|
|
86
105
|
const readArtifact = (path: string): PersistedArtifact => JSON.parse(readFileSync(path, 'utf8')) as PersistedArtifact
|
|
87
106
|
|
|
88
|
-
const
|
|
107
|
+
const readVerifiedArtifact = (path: string, stage: WorkflowStage, step: WorkflowStep): PersistedArtifact => {
|
|
108
|
+
const artifact = readArtifact(path)
|
|
109
|
+
if (artifact.type !== 'workflow-step-artifact' || artifact.stage !== stage || artifact.inputHash !== step.inputHash) throw new Error(`Invalid workflow artifact for stage "${stage}".`)
|
|
110
|
+
if (sha256NormalizedV1(artifact.value) !== artifact.outputHash || artifact.outputHash !== step.outputHash) throw new Error(`Workflow artifact hash mismatch for stage "${stage}".`)
|
|
111
|
+
return artifact
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const stepArtifactPath = (stateDir: string, step: WorkflowStep): string => {
|
|
115
|
+
const path = resolve(stateDir, step.artifactRefs?.[0] ?? '')
|
|
116
|
+
const pathRelativeToState = relative(stateDir, path)
|
|
117
|
+
if (pathRelativeToState.startsWith('..') || pathRelativeToState.startsWith('/')) throw new Error(`Workflow artifact escapes state directory for stage "${step.name}".`)
|
|
118
|
+
return path
|
|
119
|
+
}
|
|
89
120
|
|
|
90
121
|
const stepOutput = (stateDir: string, run: WorkflowRunV1, stage: WorkflowStage): unknown => {
|
|
91
122
|
const step = run.steps.find((item) => item.name === stage)
|
|
92
123
|
if (!step || step.status !== 'completed' || !step.artifactRefs?.[0]) return null
|
|
93
|
-
return
|
|
124
|
+
return readVerifiedArtifact(stepArtifactPath(stateDir, step), stage, step).value
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
const acquireLock = (stateDir: string): (() => void) => {
|
|
@@ -120,7 +151,7 @@ const loadManifest = (stateDir: string): WorkflowRunV1 | undefined => {
|
|
|
120
151
|
}
|
|
121
152
|
|
|
122
153
|
const baseRun = (options: WorkflowOptions, stateDir: string, supersedes?: string): WorkflowRunV1 => {
|
|
123
|
-
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? '1.0.0' })
|
|
154
|
+
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, pipelineVersion: options.pipelineVersion ?? '1.0.0', analyzerVersions: options.analyzerVersions ?? {}, toolVersion: options.toolVersion ?? '1.0.0' })
|
|
124
155
|
return WorkflowRunV1Schema.parse({
|
|
125
156
|
type: 'workflow-run',
|
|
126
157
|
schemaVersion: 1,
|
|
@@ -130,8 +161,8 @@ const baseRun = (options: WorkflowOptions, stateDir: string, supersedes?: string
|
|
|
130
161
|
sourceRevision: options.sourceRevision,
|
|
131
162
|
sourceRevisionKind: 'content',
|
|
132
163
|
configurationHash: options.configurationHash,
|
|
133
|
-
pipelineVersion: '1.0.0',
|
|
134
|
-
analyzerVersions: { workflow: options.toolVersion ?? '1.0.0' },
|
|
164
|
+
pipelineVersion: options.pipelineVersion ?? '1.0.0',
|
|
165
|
+
analyzerVersions: { ...(options.analyzerVersions ?? {}), workflow: options.toolVersion ?? '1.0.0' },
|
|
135
166
|
runId: options.runId ?? runId(),
|
|
136
167
|
state: 'created',
|
|
137
168
|
steps: WORKFLOW_STAGES.map((name) => ({ name, status: 'pending', inputHash })) as WorkflowStep[],
|
|
@@ -145,7 +176,8 @@ const withHash = (run: WorkflowRunV1): WorkflowRunV1 => WorkflowRunV1Schema.pars
|
|
|
145
176
|
const sameInputs = (run: WorkflowRunV1, options: WorkflowOptions): boolean =>
|
|
146
177
|
run.sourceRevision === options.sourceRevision &&
|
|
147
178
|
run.configurationHash === options.configurationHash &&
|
|
148
|
-
run.
|
|
179
|
+
run.pipelineVersion === (options.pipelineVersion ?? '1.0.0') &&
|
|
180
|
+
sha256NormalizedV1(run.analyzerVersions) === sha256NormalizedV1({ ...(options.analyzerVersions ?? {}), workflow: options.toolVersion ?? '1.0.0' })
|
|
149
181
|
|
|
150
182
|
const selectedStages = (stage: WorkflowOptions['stage']): readonly WorkflowStage[] =>
|
|
151
183
|
stage && stage !== 'all' ? [stage] : WORKFLOW_STAGES
|
|
@@ -174,7 +206,17 @@ export const runWorkflow = (options: WorkflowOptions): WorkflowExecutionResult =
|
|
|
174
206
|
if (!run) throw new Error('Workflow manifest was not initialized.')
|
|
175
207
|
const firstSelectedStage = selectedStages(options.stage)[0]
|
|
176
208
|
const previousStageIndex = firstSelectedStage ? WORKFLOW_STAGES.indexOf(firstSelectedStage) - 1 : -1
|
|
177
|
-
let previousOutput: unknown =
|
|
209
|
+
let previousOutput: unknown = null
|
|
210
|
+
if (previousStageIndex >= 0) {
|
|
211
|
+
try {
|
|
212
|
+
previousOutput = stepOutput(stateDir, run, WORKFLOW_STAGES[previousStageIndex]!)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
run = withHash(transition(run, 'failed', error instanceof Error ? error.message : String(error)))
|
|
215
|
+
appendTransition(stateDir, run.transitions[run.transitions.length - 1]!)
|
|
216
|
+
writeManifest(stateDir, run)
|
|
217
|
+
throw error
|
|
218
|
+
}
|
|
219
|
+
}
|
|
178
220
|
const reusedStages: WorkflowStage[] = []
|
|
179
221
|
for (const stage of selectedStages(options.stage)) {
|
|
180
222
|
const input = options.inputs?.[stage] ?? previousOutput
|
|
@@ -182,11 +224,25 @@ export const runWorkflow = (options: WorkflowOptions): WorkflowExecutionResult =
|
|
|
182
224
|
const existing = run.steps.find((step) => step.name === stage)
|
|
183
225
|
const artifactPath = existing?.artifactRefs?.[0] ? resolve(stateDir, existing.artifactRefs[0]) : stageArtifactPath(stateDir, stage, inputHash)
|
|
184
226
|
if (existing?.status === 'completed' && existing.inputHash === inputHash && existing.outputHash && existsSync(artifactPath)) {
|
|
185
|
-
|
|
227
|
+
try {
|
|
228
|
+
previousOutput = readVerifiedArtifact(artifactPath, stage, existing).value
|
|
229
|
+
} catch (error) {
|
|
230
|
+
run = withHash(transition(run, 'failed', error instanceof Error ? error.message : String(error)))
|
|
231
|
+
appendTransition(stateDir, run.transitions[run.transitions.length - 1]!)
|
|
232
|
+
writeManifest(stateDir, run)
|
|
233
|
+
throw error
|
|
234
|
+
}
|
|
186
235
|
reusedStages.push(stage)
|
|
187
236
|
continue
|
|
188
237
|
}
|
|
189
238
|
|
|
239
|
+
if (options.shouldCancel?.()) {
|
|
240
|
+
run = withHash(transition(run, 'cancelled', `Workflow cancellation requested before stage "${stage}".`))
|
|
241
|
+
appendTransition(stateDir, run.transitions[run.transitions.length - 1]!)
|
|
242
|
+
writeManifest(stateDir, run)
|
|
243
|
+
return { run, stateDir, reusedStages }
|
|
244
|
+
}
|
|
245
|
+
|
|
190
246
|
const handler = options.handlers[stage]
|
|
191
247
|
if (!handler) throw new Error(`No handler configured for workflow stage "${stage}".`)
|
|
192
248
|
run = withHash(transition(run, stageState[stage]))
|