@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/CONTRIBUTING.md +6 -4
  3. package/action.yml +1 -1
  4. package/dist/cli/program.js +1139 -294
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/config/index.d.ts +1 -1
  7. package/dist/config/index.js +43 -5
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/index-BUL0q7s8.d.ts +660 -0
  10. package/dist/index.d.ts +817 -2134
  11. package/dist/index.js +1154 -244
  12. package/dist/index.js.map +1 -1
  13. package/docs/PRD-enterprise-hardening.md +288 -0
  14. package/docs/RELEASE.md +22 -8
  15. package/docs/adr/0001-enterprise-verification-contract.md +35 -0
  16. package/docs/agent-corpus/INDEX.md +2 -2
  17. package/docs/agent-corpus/chat.md +2 -2
  18. package/docs/agent-corpus/cli.md +2 -2
  19. package/docs/agent-corpus/conformance.md +2 -2
  20. package/docs/agent-corpus/doc-bridge.md +1 -1
  21. package/docs/agent-corpus/doctor.md +2 -2
  22. package/docs/agent-corpus/gates.md +2 -2
  23. package/docs/agent-corpus/mcp.md +2 -2
  24. package/docs/agent-corpus/memory.md +2 -2
  25. package/docs/agent-corpus/query.md +2 -2
  26. package/docs/knowledge-engine-runbook.md +30 -2
  27. package/docs/spec/analyzer-plugin-v1.md +24 -0
  28. package/docs/spec/benchmark-v1.md +36 -0
  29. package/docs/spec/config-v1.md +156 -0
  30. package/docs/validation-cycle-plan.md +255 -0
  31. package/docs/verification-harness.md +37 -4
  32. package/mcpb/manifest.json +1 -1
  33. package/package.json +68 -70
  34. package/scripts/check-ecosystem-upstream.mjs +3 -2
  35. package/scripts/report-visual-check.mjs +64 -12
  36. package/scripts/verification-harness.mjs +216 -14
  37. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  38. package/src/agents/registry-adapter.ts +31 -7
  39. package/src/cli/demo.ts +2 -2
  40. package/src/cli/program.ts +59 -16
  41. package/src/config/index.ts +2 -0
  42. package/src/config/load-config.ts +7 -1
  43. package/src/config/schema.ts +60 -2
  44. package/src/conformance/documentation-standard-v1.ts +14 -8
  45. package/src/discovery/documentation.ts +90 -23
  46. package/src/discovery/repository.ts +147 -19
  47. package/src/doctor/run-doctor.ts +2 -15
  48. package/src/federation/llms.ts +72 -20
  49. package/src/fixes/proposals.ts +4 -3
  50. package/src/index-builder/human-adapters/fumadocs.ts +1 -1
  51. package/src/index-builder/watch-index.ts +1 -1
  52. package/src/index.ts +29 -0
  53. package/src/lib/bounded-text.ts +15 -10
  54. package/src/metrics/benchmark.ts +176 -0
  55. package/src/plugins/contract.ts +89 -0
  56. package/src/reconciliation/reconcile.ts +181 -5
  57. package/src/report/html.ts +318 -88
  58. package/src/rules/engine.ts +15 -2
  59. package/src/safety/repository.ts +1 -1
  60. package/src/schemas/knowledge.ts +21 -3
  61. package/src/validate.ts +7 -1
  62. package/src/version.ts +1 -1
  63. package/src/workflow/engine.ts +65 -9
  64. package/dist/index-DudNuwI5.d.ts +0 -2060
@@ -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 => RuleIdSchema.parse(value)
180
- export const parseRuleSeverity = (value: string): RuleSeverity => RuleSeveritySchema.parse(value)
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
+ }
@@ -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[]
@@ -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.message,
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.6.4'
1
+ export const PACKAGE_VERSION = '1.7.45'
@@ -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 stepArtifactPath = (stateDir: string, step: WorkflowStep): string => resolve(stateDir, step.artifactRefs?.[0] ?? '')
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 readArtifact(stepArtifactPath(stateDir, step)).value
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.analyzerVersions.workflow === (options.toolVersion ?? '1.0.0')
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 = previousStageIndex >= 0 ? stepOutput(stateDir, run, WORKFLOW_STAGES[previousStageIndex]!) : null
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
- previousOutput = readArtifact(artifactPath).value
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]))