@agentskit/doc-bridge 1.7.45 → 1.10.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 +488 -0
- package/CONTRIBUTING.md +23 -0
- package/README.md +87 -40
- package/SECURITY.md +7 -0
- package/action.yml +1 -1
- package/bin/ak-docs.js +2 -2
- package/bin/ak-verify.js +13 -7
- package/dist/cli/program.d.ts +3 -1
- package/dist/cli/program.js +15871 -6218
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +95 -6
- package/dist/config/index.js.map +1 -1
- package/dist/{index-BUL0q7s8.d.ts → index-7wYGbllW.d.ts} +134 -1
- package/dist/index.d.ts +9880 -1062
- package/dist/index.js +16143 -6127
- package/dist/index.js.map +1 -1
- package/docs/MARKETPLACE.md +1 -1
- package/docs/PRD-documentation-efficiency-study.md +406 -0
- package/docs/PRD-knowledge-retrieval-and-enrichment.md +466 -0
- package/docs/adr/0002-documentation-audit-boundary.md +22 -0
- package/docs/adr/0003-study-protocol-and-historical-evidence.md +40 -0
- package/docs/adr/0004-controlled-study-runner.md +25 -0
- package/docs/adr/0005-documentation-quality-and-criticality.md +20 -0
- package/docs/adr/0006-registry-semantic-grounding.md +20 -0
- package/docs/adr/0007-longitudinal-study-metrics.md +21 -0
- package/docs/adr/0008-study-verification-boundary.md +21 -0
- package/docs/adr/0009-study-provider-cli-adapter.md +25 -0
- package/docs/agent-corpus/INDEX.md +12 -1
- package/docs/agent-corpus/OVERVIEW.md +25 -0
- package/docs/agent-corpus/chat.md +5 -1
- package/docs/agent-corpus/cli.md +16 -0
- package/docs/agent-corpus/conformance.md +12 -0
- package/docs/agent-corpus/doc-bridge.md +47 -0
- package/docs/agent-corpus/doctor.md +8 -0
- package/docs/agent-corpus/gates.md +4 -0
- package/docs/agent-corpus/mcp.md +13 -0
- package/docs/agent-corpus/memory.md +4 -0
- package/docs/agent-corpus/query.md +33 -0
- package/docs/bench/README.md +122 -0
- package/docs/bench/retrieval-baseline-v1.json +28 -0
- package/docs/bench/retrieval-suite-v1.json +1033 -0
- package/docs/chat-and-rag.md +3 -2
- package/docs/for-agents.md +9 -1
- package/docs/getting-started.md +4 -11
- package/docs/guides/gate-ci.md +11 -1
- package/docs/guides/install-and-run.md +9 -65
- package/docs/index.md +22 -1
- package/docs/knowledge-engine-runbook.md +38 -3
- package/docs/landing/assets/context-payload-reduction.svg +21 -0
- package/docs/landing/assets/controlled-ab-comparison.svg +30 -0
- package/docs/landing/index.html +119 -5
- package/docs/loop-workflow.md +117 -0
- package/docs/mcp.md +6 -1
- package/docs/parity/public-claims-v1.json +145 -0
- package/docs/playbook/doc-bridge-pattern.md +1 -1
- package/docs/query.md +90 -2
- package/docs/recipes/index-pipeline.md +1 -1
- package/docs/schemas/agent-handoff-v1.md +15 -0
- package/docs/schemas/doc-bridge-index-v1.md +65 -0
- package/docs/spec/benchmark-v1.md +33 -1
- package/docs/spec/cli.md +30 -10
- package/docs/spec/config-v1.md +157 -8
- package/docs/spec/documentation-audit-v1.md +61 -0
- package/docs/spec/enrichment-overlay-v1.md +241 -0
- package/docs/spec/graph-signals-v1.md +92 -0
- package/docs/spec/incremental-scan-v1.md +102 -0
- package/docs/spec/markdown-analyzer-v1.md +73 -0
- package/docs/spec/mcp-knowledge-tools-v1.md +147 -0
- package/docs/spec/measured-enrichment-v1.md +229 -0
- package/docs/spec/public-parity-v1.md +119 -0
- package/docs/spec/registry-agents.md +6 -0
- package/docs/spec/render-v1.md +122 -0
- package/docs/spec/retrieval-index-v1.md +164 -0
- package/docs/spec/study-metrics-v1.md +58 -0
- package/docs/spec/study-protocol-v1.md +46 -0
- package/docs/spec/study-provider-cli-v1.md +116 -0
- package/docs/spec/study-runner-v1.md +35 -0
- package/docs/spec/study-task-suite-v1.md +41 -0
- package/docs/spec/study-verification-v1.md +40 -0
- package/docs/study/README.md +84 -0
- package/docs/study/ab-adjudicated-cost-analysis-v1.md +29 -0
- package/docs/study/ab-adjudicated-cost-plan-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-plan-v2-v1.json +33 -0
- package/docs/study/ab-adjudicated-cost-result-v1.json +80 -0
- package/docs/study/ab-baseline-analysis-v1.md +21 -0
- package/docs/study/ab-baseline-plan-v1.json +33 -0
- package/docs/study/ab-baseline-recovery-plan-v1.json +33 -0
- package/docs/study/ab-baseline-result-v1.json +79 -0
- package/docs/study/documentation-audit-round-2026-08-31.json +183 -0
- package/docs/study/historical-evidence-v1.json +252 -0
- package/docs/study/observation-ledger-v1.json +30632 -0
- package/docs/study/phase3-task-coverage-v1.json +34 -0
- package/docs/study/phase4-public-pilot-ledger-v1.json +1344 -0
- package/docs/study/phase4-public-pilot-result-v1.json +52 -0
- package/docs/study/phase4-public-pilot-run-plan-v1.json +26 -0
- package/docs/study/phase4-public-pilot-task-suite-v1.json +71 -0
- package/docs/study/pilot-round-2026-08-31.json +46 -0
- package/docs/study/protocol-v1.json +90 -0
- package/docs/study/publication-gate-v1.md +45 -0
- package/docs/study/quality-scorecard-cycle-plan.md +545 -0
- package/docs/study/quality-scorecard-v1.json +38 -0
- package/docs/study/round-1-adjudicated-smoke-v1.json +30642 -0
- package/docs/study/round-1-instrumentation-plan-v1.md +39 -0
- package/docs/study/round-2-expanded-adjudication-v1.json +91 -0
- package/docs/study/round-2-expanded-validation-v1.md +58 -0
- package/docs/study/round-3-evidence-contract-v1.json +75 -0
- package/docs/study/round-3-evidence-contract-v1.md +57 -0
- package/docs/study/round-4-confirmation-v1.json +75 -0
- package/docs/study/round-4-confirmation-v1.md +55 -0
- package/docs/study/run-plan-v1.json +33 -0
- package/docs/study/semantic-adjudication-cycle-8.md +20 -0
- package/docs/study/task-suite-v1.json +96 -0
- package/docs/study/token-efficiency-plan-v1.md +337 -0
- package/docs/study/token-efficiency-protocol-v2.json +62 -0
- package/docs/study/verification-binding-v1.json +27 -0
- package/docs/validation-cycle-plan.md +14 -0
- package/docs/verification-harness.md +11 -6
- package/ecosystem-claims.json +2 -2
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +4 -4
- package/mcpb/manifest.json +9 -1
- package/package.json +34 -15
- package/scripts/check-ecosystem-upstream.mjs +34 -6
- package/skills/doc-bridge-handoff/fixtures/synthetic-repo/docs/for-agents/packages/payments.md +7 -0
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +192 -24
- package/src/audit/documentation.ts +513 -0
- package/src/bench/baseline.ts +198 -0
- package/src/bench/overlay-delta.ts +139 -0
- package/src/bench/retrieval.ts +319 -0
- package/src/budget/compile.ts +91 -0
- package/src/budget/sections.ts +70 -0
- package/src/cli/program.ts +684 -74
- package/src/cli/usage.ts +71 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +4 -0
- package/src/config/schema.ts +126 -2
- package/src/conformance/documentation-standard-v1.ts +8 -6
- package/src/discovery/areas.ts +197 -0
- package/src/discovery/documentation.ts +211 -5
- package/src/discovery/identity.ts +24 -0
- package/src/discovery/incremental.ts +314 -0
- package/src/discovery/inputs.ts +110 -0
- package/src/discovery/markdown.ts +481 -0
- package/src/discovery/repository.ts +485 -101
- package/src/doctor/run-doctor.ts +244 -12
- package/src/enrich/approvals.ts +190 -0
- package/src/enrich/cache.ts +93 -0
- package/src/enrich/context-pack.ts +272 -0
- package/src/enrich/overlay.ts +255 -0
- package/src/enrich/review.ts +106 -0
- package/src/enrich/stage.ts +374 -0
- package/src/enrich/stats.ts +100 -0
- package/src/enrich/validate.ts +410 -0
- package/src/federation/llms.ts +2 -4
- package/src/findings/report.ts +103 -0
- package/src/graph/build.ts +356 -0
- package/src/graph/memory.ts +208 -0
- package/src/index-builder/build-handoffs.ts +22 -11
- package/src/index-builder/build-index.ts +132 -3
- package/src/index-builder/llms-txt.ts +48 -8
- package/src/index-builder/project-corpus.ts +111 -0
- package/src/index.ts +630 -2
- package/src/lib/fuzzy-match.ts +235 -0
- package/src/mcp/knowledge.ts +554 -0
- package/src/mcp/server.ts +113 -18
- package/src/metrics/benchmark.ts +21 -0
- package/src/parity/check.ts +309 -0
- package/src/parity/claims.ts +259 -0
- package/src/parity/resolve.ts +160 -0
- package/src/query/handoff.ts +326 -0
- package/src/query/load-index.ts +53 -1
- package/src/query/query.ts +92 -59
- package/src/query/search.ts +289 -92
- package/src/query/text.ts +155 -0
- package/src/reconciliation/reconcile.ts +101 -10
- package/src/render/data.ts +356 -0
- package/src/render/engine.ts +398 -0
- package/src/render/generated.ts +77 -0
- package/src/render/render.ts +209 -0
- package/src/render/template-source.ts +52 -0
- package/src/render/templates.ts +289 -0
- package/src/report/html.ts +2 -2
- package/src/retrieval/bm25.ts +161 -0
- package/src/retrieval/project.ts +495 -0
- package/src/retrieval/rank.ts +383 -0
- package/src/retrieval/weights.ts +39 -0
- package/src/retriever/doc-bridge-retriever.ts +100 -15
- package/src/rules/engine.ts +30 -10
- package/src/schemas/agent-handoff.ts +56 -0
- package/src/schemas/budget.ts +37 -0
- package/src/schemas/doc-bridge-index.ts +53 -2
- package/src/schemas/enrichment.ts +369 -0
- package/src/schemas/json-schemas.ts +39 -2
- package/src/schemas/knowledge.ts +14 -1
- package/src/schemas/retrieval-index.ts +152 -0
- package/src/shims/graphology.d.ts +91 -0
- package/src/study/adjudication.ts +196 -0
- package/src/study/execution.ts +350 -0
- package/src/study/expectations.ts +219 -0
- package/src/study/metrics.ts +467 -0
- package/src/study/protocol.ts +271 -0
- package/src/study/provider-cli.ts +115 -0
- package/src/study/provider-telemetry.ts +47 -0
- package/src/study/quality-scorecard.ts +164 -0
- package/src/study/runner.ts +461 -0
- package/src/study/task-suite.ts +321 -0
- package/src/study/verification.ts +134 -0
- package/src/validate.ts +1 -4
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +36 -11
- package/scripts/verification-harness.mjs +0 -482
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { contentHashForArtifactV1, sha256NormalizedV1 } from '../index-builder/content-hash.js'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const STUDY_PROTOCOL_SCHEMA_VERSION = 1 as const
|
|
5
|
+
export const STUDY_PROTOCOL_CONTENT_HASH_ALGO = 'sha256-normalized-v1' as const
|
|
6
|
+
|
|
7
|
+
const hash = z.string().regex(/^[a-f0-9]{64}$/)
|
|
8
|
+
const identifier = z.string().regex(/^[a-z][a-z0-9-]{0,127}$/)
|
|
9
|
+
const reference = z.string().regex(/^[a-z0-9][a-z0-9._:-]{0,255}$/)
|
|
10
|
+
const modelReference = z.string().regex(/^[a-z0-9][a-z0-9._:/-]{0,255}$/)
|
|
11
|
+
export const isSafeStudyText = (value: string): boolean => !/(?:^|[\\/])(?:Users|private|tmp|home)(?:[\\/])|(?:api[_-]?key|token|password|secret)\s*[:=]|-----BEGIN|https?:\/\//i.test(value)
|
|
12
|
+
const safeText = z.string().min(1).max(2_048).refine(
|
|
13
|
+
isSafeStudyText,
|
|
14
|
+
'Public study text cannot contain paths, URLs, credentials, or secret material',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const uniqueIds = (values: readonly string[], label: string): void => {
|
|
18
|
+
if (new Set(values).size !== values.length) throw new Error(`Duplicate ${label} id.`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ModelSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
id: identifier,
|
|
24
|
+
role: z.enum(['low-cost', 'reference']),
|
|
25
|
+
status: z.enum(['planned', 'pinned']),
|
|
26
|
+
provider: identifier.optional(),
|
|
27
|
+
model: modelReference.optional(),
|
|
28
|
+
version: reference.optional(),
|
|
29
|
+
promptContractHash: hash.optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict()
|
|
32
|
+
.superRefine((value, context) => {
|
|
33
|
+
if (value.status === 'pinned' && (!value.provider || !value.model || !value.version)) {
|
|
34
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: 'Pinned models require provider, model, and version.' })
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const RepositorySchema = z
|
|
39
|
+
.object({
|
|
40
|
+
id: identifier,
|
|
41
|
+
visibility: z.enum(['public', 'private', 'anonymized']),
|
|
42
|
+
role: z.enum(['product', 'consumer']),
|
|
43
|
+
})
|
|
44
|
+
.strict()
|
|
45
|
+
|
|
46
|
+
const ScenarioSchema = z.object({
|
|
47
|
+
id: identifier,
|
|
48
|
+
label: safeText,
|
|
49
|
+
source: z.enum(['repository-only', 'deterministic-doc-bridge', 'doc-bridge-registry-agent']),
|
|
50
|
+
modelIds: z.array(identifier).min(1).max(16),
|
|
51
|
+
registryAgent: z.enum(['none', 'configured']).optional(),
|
|
52
|
+
requiresHumanApproval: z.boolean(),
|
|
53
|
+
}).strict()
|
|
54
|
+
|
|
55
|
+
const MetricSchema = z.object({
|
|
56
|
+
id: identifier,
|
|
57
|
+
family: z.enum(['discovery', 'task', 'documentation', 'operations', 'cost']),
|
|
58
|
+
unit: z.enum(['count', 'milliseconds', 'tokens', 'bytes', 'ratio', 'percent', 'currency']),
|
|
59
|
+
source: z.enum(['provider', 'runner', 'adjudicator', 'doc-bridge', 'human']),
|
|
60
|
+
required: z.boolean(),
|
|
61
|
+
description: safeText,
|
|
62
|
+
}).strict()
|
|
63
|
+
|
|
64
|
+
const OutcomeSchema = z.object({
|
|
65
|
+
id: identifier,
|
|
66
|
+
statement: safeText,
|
|
67
|
+
checks: z.array(z.object({ id: identifier, command: safeText }).strict()).max(16).default([]),
|
|
68
|
+
notApplicableReason: safeText.optional(),
|
|
69
|
+
}).strict().superRefine((value, context) => {
|
|
70
|
+
if (!value.checks.length && !value.notApplicableReason) {
|
|
71
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ['checks'], message: 'An outcome requires an executable check or an explicit not-applicable reason.' })
|
|
72
|
+
}
|
|
73
|
+
if (value.checks.length && value.notApplicableReason) {
|
|
74
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ['notApplicableReason'], message: 'An outcome cannot have checks and a not-applicable reason.' })
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const BudgetSchema = z.object({
|
|
79
|
+
maxTokensPerTask: z.number().int().positive(),
|
|
80
|
+
maxRuntimeMsPerTask: z.number().int().positive(),
|
|
81
|
+
maxRuns: z.number().int().positive(),
|
|
82
|
+
maxNetworkRequests: z.number().int().nonnegative(),
|
|
83
|
+
}).strict()
|
|
84
|
+
|
|
85
|
+
const PrivacySchema = z.object({
|
|
86
|
+
mode: z.literal('anonymized'),
|
|
87
|
+
forbiddenFields: z.array(z.enum(['repository-content', 'paths', 'prompts', 'credentials', 'private-identifiers', 'raw-agent-responses'])).min(1).max(16),
|
|
88
|
+
publicationRequiresHumanReview: z.literal(true),
|
|
89
|
+
}).strict()
|
|
90
|
+
|
|
91
|
+
const StoppingSchema = z.object({
|
|
92
|
+
minControlledRounds: z.number().int().positive(),
|
|
93
|
+
consecutiveNoMaterialImprovementRounds: z.number().int().positive(),
|
|
94
|
+
targets: z.array(z.object({
|
|
95
|
+
metricId: identifier,
|
|
96
|
+
direction: z.enum(['decrease', 'increase', 'no-regression']),
|
|
97
|
+
threshold: z.number().finite(),
|
|
98
|
+
}).strict()).max(32),
|
|
99
|
+
}).strict()
|
|
100
|
+
|
|
101
|
+
const StudyProtocolPayloadSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
type: z.literal('study-protocol'),
|
|
104
|
+
schemaVersion: z.literal(STUDY_PROTOCOL_SCHEMA_VERSION),
|
|
105
|
+
protocolVersion: reference,
|
|
106
|
+
title: safeText,
|
|
107
|
+
evidenceClasses: z.array(z.enum(['historical', 'controlled'])).min(1).max(2),
|
|
108
|
+
repositories: z.array(RepositorySchema).min(1).max(64),
|
|
109
|
+
taskCategories: z.array(z.enum(['discovery', 'architecture', 'documentation', 'implementation'])).min(1).max(4),
|
|
110
|
+
models: z.array(ModelSchema).min(2).max(16),
|
|
111
|
+
scenarios: z.array(ScenarioSchema).min(3).max(8),
|
|
112
|
+
metrics: z.array(MetricSchema).min(1).max(128),
|
|
113
|
+
outcomes: z.array(OutcomeSchema).min(1).max(64),
|
|
114
|
+
budget: BudgetSchema,
|
|
115
|
+
privacy: PrivacySchema,
|
|
116
|
+
stopping: StoppingSchema,
|
|
117
|
+
})
|
|
118
|
+
.strict()
|
|
119
|
+
|
|
120
|
+
export const StudyProtocolV1Schema = StudyProtocolPayloadSchema.extend({
|
|
121
|
+
contentHash: hash,
|
|
122
|
+
contentHashAlgo: z.literal(STUDY_PROTOCOL_CONTENT_HASH_ALGO),
|
|
123
|
+
}).strict()
|
|
124
|
+
|
|
125
|
+
export type StudyProtocolV1 = z.infer<typeof StudyProtocolV1Schema>
|
|
126
|
+
|
|
127
|
+
const HistoricalMetricStatusSchema = z.enum(['missing', 'not-analyzed', 'blocked', 'not-applicable'])
|
|
128
|
+
const HistoricalRecordStatusSchema = z.enum(['validated', 'partially-validated', 'not-analyzed', 'blocked', 'not-applicable'])
|
|
129
|
+
|
|
130
|
+
const HistoricalEvidenceRecordPayloadSchema = z.object({
|
|
131
|
+
id: identifier,
|
|
132
|
+
evidenceClass: z.literal('historical'),
|
|
133
|
+
observedAt: z.string().datetime(),
|
|
134
|
+
subject: z.object({
|
|
135
|
+
kind: z.enum(['consumer', 'aggregate']),
|
|
136
|
+
id: identifier,
|
|
137
|
+
}).strict(),
|
|
138
|
+
source: z.object({
|
|
139
|
+
kind: z.enum(['validation-plan', 'verification-run', 'benchmark', 'study-artifact', 'issue', 'pull-request']),
|
|
140
|
+
reference,
|
|
141
|
+
}).strict(),
|
|
142
|
+
docBridgeVersion: reference.optional(),
|
|
143
|
+
sourceRevisionHash: hash.optional(),
|
|
144
|
+
snapshotHash: hash.optional(),
|
|
145
|
+
reportHash: hash.optional(),
|
|
146
|
+
workflowRunId: reference.optional(),
|
|
147
|
+
verificationRunId: reference.optional(),
|
|
148
|
+
status: HistoricalRecordStatusSchema,
|
|
149
|
+
metrics: z.record(identifier, z.number().finite()).default({}),
|
|
150
|
+
missingMetrics: z.array(z.object({
|
|
151
|
+
metricId: identifier,
|
|
152
|
+
status: HistoricalMetricStatusSchema,
|
|
153
|
+
reason: safeText,
|
|
154
|
+
}).strict()).default([]),
|
|
155
|
+
limitations: z.array(safeText).min(1).max(32),
|
|
156
|
+
}).strict().superRefine((value, context) => {
|
|
157
|
+
if (!Object.keys(value.metrics).length && !value.missingMetrics.length) {
|
|
158
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ['metrics'], message: 'A historical record must contain metrics or explicit missing metrics.' })
|
|
159
|
+
}
|
|
160
|
+
uniqueIds(value.missingMetrics.map((metric) => metric.metricId), 'missing metric')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
export const HistoricalEvidenceRecordV1Schema = HistoricalEvidenceRecordPayloadSchema
|
|
164
|
+
export type HistoricalEvidenceRecordV1 = z.infer<typeof HistoricalEvidenceRecordV1Schema>
|
|
165
|
+
|
|
166
|
+
const HistoricalEvidenceRegistryPayloadSchema = z.object({
|
|
167
|
+
type: z.literal('historical-evidence-registry'),
|
|
168
|
+
schemaVersion: z.literal(STUDY_PROTOCOL_SCHEMA_VERSION),
|
|
169
|
+
registryVersion: reference,
|
|
170
|
+
records: z.array(HistoricalEvidenceRecordV1Schema).max(10_000),
|
|
171
|
+
}).strict()
|
|
172
|
+
|
|
173
|
+
export const HistoricalEvidenceRegistryV1Schema = HistoricalEvidenceRegistryPayloadSchema.extend({
|
|
174
|
+
contentHash: hash,
|
|
175
|
+
contentHashAlgo: z.literal(STUDY_PROTOCOL_CONTENT_HASH_ALGO),
|
|
176
|
+
}).strict()
|
|
177
|
+
|
|
178
|
+
export type HistoricalEvidenceRegistryV1 = z.infer<typeof HistoricalEvidenceRegistryV1Schema>
|
|
179
|
+
|
|
180
|
+
const assertProtocolReferences = (protocol: StudyProtocolV1): void => {
|
|
181
|
+
uniqueIds(protocol.repositories.map((item) => item.id), 'repository')
|
|
182
|
+
uniqueIds(protocol.models.map((item) => item.id), 'model')
|
|
183
|
+
uniqueIds(protocol.scenarios.map((item) => item.id), 'scenario')
|
|
184
|
+
uniqueIds(protocol.metrics.map((item) => item.id), 'metric')
|
|
185
|
+
uniqueIds(protocol.outcomes.map((item) => item.id), 'outcome')
|
|
186
|
+
uniqueIds(protocol.outcomes.flatMap((item) => item.checks.map((check) => check.id)), 'outcome check')
|
|
187
|
+
const modelIds = new Set(protocol.models.map((item) => item.id))
|
|
188
|
+
const metricIds = new Set(protocol.metrics.map((item) => item.id))
|
|
189
|
+
for (const scenario of protocol.scenarios) {
|
|
190
|
+
for (const modelId of scenario.modelIds) if (!modelIds.has(modelId)) throw new Error(`Scenario ${scenario.id} references unknown model ${modelId}.`)
|
|
191
|
+
if (scenario.source === 'doc-bridge-registry-agent' && scenario.registryAgent !== 'configured') throw new Error(`Scenario ${scenario.id} requires a configured Registry agent.`)
|
|
192
|
+
if (scenario.source !== 'doc-bridge-registry-agent' && scenario.registryAgent === 'configured') throw new Error(`Scenario ${scenario.id} cannot configure a Registry agent.`)
|
|
193
|
+
}
|
|
194
|
+
for (const target of protocol.stopping.targets) if (!metricIds.has(target.metricId)) throw new Error(`Stopping target references unknown metric ${target.metricId}.`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const assertHash = <T extends { readonly contentHash: string }>(artifact: T): T => {
|
|
198
|
+
const expected = contentHashForArtifactV1(artifact)
|
|
199
|
+
if (expected !== artifact.contentHash) throw new Error(`Invalid content hash: expected ${expected}.`)
|
|
200
|
+
return artifact
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const createStudyProtocol = (input: unknown): StudyProtocolV1 => {
|
|
204
|
+
const payload = StudyProtocolPayloadSchema.parse(input)
|
|
205
|
+
const hashable = { ...payload, contentHashAlgo: STUDY_PROTOCOL_CONTENT_HASH_ALGO }
|
|
206
|
+
const protocol = StudyProtocolV1Schema.parse({
|
|
207
|
+
...hashable,
|
|
208
|
+
contentHash: sha256NormalizedV1(hashable),
|
|
209
|
+
})
|
|
210
|
+
assertProtocolReferences(protocol)
|
|
211
|
+
return protocol
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const parseStudyProtocol = (input: unknown): StudyProtocolV1 => {
|
|
215
|
+
const protocol = StudyProtocolV1Schema.parse(input)
|
|
216
|
+
assertHash(protocol)
|
|
217
|
+
assertProtocolReferences(protocol)
|
|
218
|
+
return protocol
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const createHistoricalEvidenceRegistry = (input: unknown): HistoricalEvidenceRegistryV1 => {
|
|
222
|
+
const payload = HistoricalEvidenceRegistryPayloadSchema.parse(input)
|
|
223
|
+
const hashable = { ...payload, contentHashAlgo: STUDY_PROTOCOL_CONTENT_HASH_ALGO }
|
|
224
|
+
return HistoricalEvidenceRegistryV1Schema.parse({
|
|
225
|
+
...hashable,
|
|
226
|
+
contentHash: sha256NormalizedV1(hashable),
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const parseHistoricalEvidenceRegistry = (input: unknown): HistoricalEvidenceRegistryV1 => {
|
|
231
|
+
const registry = HistoricalEvidenceRegistryV1Schema.parse(input)
|
|
232
|
+
return assertHash(registry)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export const validateHistoricalEvidenceRegistry = (
|
|
236
|
+
registry: HistoricalEvidenceRegistryV1,
|
|
237
|
+
protocol: StudyProtocolV1,
|
|
238
|
+
): void => {
|
|
239
|
+
const repositoryIds = new Set(protocol.repositories.map((item) => item.id))
|
|
240
|
+
uniqueIds(registry.records.map((record) => record.id), 'historical record')
|
|
241
|
+
for (const record of registry.records) {
|
|
242
|
+
if (record.subject.kind === 'consumer' && !repositoryIds.has(record.subject.id)) throw new Error(`Historical record ${record.id} references unknown consumer ${record.subject.id}.`)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const formatStudyProtocolText = (protocol: StudyProtocolV1): readonly string[] => [
|
|
247
|
+
`Protocol: ${protocol.protocolVersion}`,
|
|
248
|
+
`Repositories: ${protocol.repositories.length}`,
|
|
249
|
+
`Models: ${protocol.models.length} (${protocol.models.filter((model) => model.status === 'pinned').length} pinned)`,
|
|
250
|
+
`Scenarios: ${protocol.scenarios.length}`,
|
|
251
|
+
`Task categories: ${protocol.taskCategories.join(', ')}`,
|
|
252
|
+
`Metrics: ${protocol.metrics.length}`,
|
|
253
|
+
`Outcomes mapped: ${protocol.outcomes.length}`,
|
|
254
|
+
`Budget: ${protocol.budget.maxTokensPerTask} tokens/task, ${protocol.budget.maxRuntimeMsPerTask} ms/task, ${protocol.budget.maxRuns} runs`,
|
|
255
|
+
`Privacy: ${protocol.privacy.mode}; human publication review: required`,
|
|
256
|
+
`Content hash: ${protocol.contentHash}`,
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
export const formatHistoricalEvidenceText = (
|
|
260
|
+
registry: HistoricalEvidenceRegistryV1,
|
|
261
|
+
): readonly string[] => {
|
|
262
|
+
const statuses = registry.records.reduce<Record<string, number>>((counts, record) => ({ ...counts, [record.status]: (counts[record.status] ?? 0) + 1 }), {})
|
|
263
|
+
const missing = registry.records.reduce((count, record) => count + record.missingMetrics.length, 0)
|
|
264
|
+
return [
|
|
265
|
+
`Registry: ${registry.registryVersion}`,
|
|
266
|
+
`Records: ${registry.records.length}`,
|
|
267
|
+
`Missing measurements: ${missing}`,
|
|
268
|
+
`Statuses: ${Object.entries(statuses).sort(([a], [b]) => a.localeCompare(b)).map(([status, count]) => `${status}=${count}`).join(', ') || 'none'}`,
|
|
269
|
+
`Content hash: ${registry.contentHash}`,
|
|
270
|
+
]
|
|
271
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, statSync } from 'node:fs'
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import { contentHashForArtifactV1, sha256NormalizedV1 } from '../index-builder/content-hash.js'
|
|
6
|
+
|
|
7
|
+
export const STUDY_PROVIDER_CLI_SCHEMA_VERSION = 1 as const
|
|
8
|
+
export const STUDY_PROVIDER_CLI_CONTENT_HASH_ALGO = 'sha256-normalized-v1' as const
|
|
9
|
+
|
|
10
|
+
const hash = z.string().regex(/^[a-f0-9]{64}$/)
|
|
11
|
+
const identifier = z.string().regex(/^[a-z][a-z0-9-]{0,127}$/)
|
|
12
|
+
const reference = z.string().regex(/^[a-z0-9][a-z0-9._:-]{0,255}$/)
|
|
13
|
+
const command = z.string().min(1).max(512).refine((value) => !/[\u0000\r\n]/.test(value), 'Command cannot contain control characters.')
|
|
14
|
+
const argument = z.string().max(2_048).refine((value) => !/[\u0000\r\n]/.test(value), 'Arguments cannot contain control characters.')
|
|
15
|
+
const environmentName = z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/)
|
|
16
|
+
const pricing = z.object({
|
|
17
|
+
currency: z.literal('USD'),
|
|
18
|
+
inputPerMillionUsd: z.number().finite().nonnegative(),
|
|
19
|
+
cachedInputPerMillionUsd: z.number().finite().nonnegative(),
|
|
20
|
+
outputPerMillionUsd: z.number().finite().nonnegative(),
|
|
21
|
+
}).strict()
|
|
22
|
+
|
|
23
|
+
const commandConfig = z.object({
|
|
24
|
+
command,
|
|
25
|
+
args: z.array(argument).max(64).default([]),
|
|
26
|
+
envAllowlist: z.array(environmentName).max(64).default([]),
|
|
27
|
+
providerNetwork: z.boolean(),
|
|
28
|
+
maxInputBytes: z.number().int().positive().max(8_000_000),
|
|
29
|
+
maxOutputBytes: z.number().int().positive().max(256_000),
|
|
30
|
+
pricing: pricing.optional(),
|
|
31
|
+
}).strict()
|
|
32
|
+
|
|
33
|
+
const adjudicatorConfig = z.object({
|
|
34
|
+
id: identifier,
|
|
35
|
+
modelId: identifier,
|
|
36
|
+
...commandConfig.shape,
|
|
37
|
+
}).strict()
|
|
38
|
+
|
|
39
|
+
const ProviderCliPayloadSchema = z.object({
|
|
40
|
+
type: z.literal('study-provider-cli-config'),
|
|
41
|
+
schemaVersion: z.literal(STUDY_PROVIDER_CLI_SCHEMA_VERSION),
|
|
42
|
+
configVersion: reference,
|
|
43
|
+
providers: z.array(z.object({
|
|
44
|
+
modelId: identifier,
|
|
45
|
+
scenarioIds: z.array(z.enum(['repository-only', 'deterministic-doc-bridge', 'registry-assisted'])).min(1).max(3),
|
|
46
|
+
...commandConfig.shape,
|
|
47
|
+
}).strict()).min(1).max(16),
|
|
48
|
+
adjudicator: adjudicatorConfig.optional(),
|
|
49
|
+
}).strict()
|
|
50
|
+
|
|
51
|
+
export const StudyProviderCliConfigV1Schema = ProviderCliPayloadSchema.extend({
|
|
52
|
+
contentHash: hash,
|
|
53
|
+
contentHashAlgo: z.literal(STUDY_PROVIDER_CLI_CONTENT_HASH_ALGO),
|
|
54
|
+
}).strict()
|
|
55
|
+
|
|
56
|
+
export type StudyProviderCliConfigV1 = z.infer<typeof StudyProviderCliConfigV1Schema>
|
|
57
|
+
export type StudyProviderCli = StudyProviderCliConfigV1['providers'][number]
|
|
58
|
+
export type StudyProviderCostPricing = z.infer<typeof pricing>
|
|
59
|
+
export type StudyAdjudicatorCli = z.infer<typeof adjudicatorConfig>
|
|
60
|
+
|
|
61
|
+
export const createStudyProviderCliConfig = (input: unknown): StudyProviderCliConfigV1 => {
|
|
62
|
+
const payload = ProviderCliPayloadSchema.parse(input)
|
|
63
|
+
const hashable = { ...payload, contentHashAlgo: STUDY_PROVIDER_CLI_CONTENT_HASH_ALGO }
|
|
64
|
+
return StudyProviderCliConfigV1Schema.parse({ ...hashable, contentHash: sha256NormalizedV1(hashable) })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const parseStudyProviderCliConfig = (input: unknown): StudyProviderCliConfigV1 => {
|
|
68
|
+
const config = StudyProviderCliConfigV1Schema.parse(input)
|
|
69
|
+
if (contentHashForArtifactV1(config) !== config.contentHash) throw new Error('Invalid study provider CLI config content hash.')
|
|
70
|
+
const duplicateKeys = new Set<string>()
|
|
71
|
+
for (const provider of config.providers) {
|
|
72
|
+
for (const scenarioId of provider.scenarioIds) {
|
|
73
|
+
const key = `${provider.modelId}:${scenarioId}`
|
|
74
|
+
if (duplicateKeys.has(key)) throw new Error(`Duplicate study provider mapping for ${key}.`)
|
|
75
|
+
duplicateKeys.add(key)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return config
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const providerForStudyExecution = (
|
|
82
|
+
config: StudyProviderCliConfigV1,
|
|
83
|
+
modelId: string,
|
|
84
|
+
scenarioId: StudyProviderCli['scenarioIds'][number],
|
|
85
|
+
): StudyProviderCli => {
|
|
86
|
+
const provider = config.providers.find((item) => item.modelId === modelId && item.scenarioIds.includes(scenarioId))
|
|
87
|
+
if (!provider) throw new Error(`No provider CLI is configured for model ${modelId} and scenario ${scenarioId}.`)
|
|
88
|
+
return provider
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const calculateStudyCostUsd = (pricing: StudyProviderCostPricing | undefined, usage: { readonly inputTokens?: number; readonly cachedInputTokens?: number; readonly outputTokens?: number }): number | undefined => {
|
|
92
|
+
if (!pricing || usage.inputTokens === undefined || usage.outputTokens === undefined) return undefined
|
|
93
|
+
const cachedInputTokens = Math.min(usage.cachedInputTokens ?? 0, usage.inputTokens)
|
|
94
|
+
const uncachedInputTokens = usage.inputTokens - cachedInputTokens
|
|
95
|
+
return Number(((uncachedInputTokens * pricing.inputPerMillionUsd + cachedInputTokens * pricing.cachedInputPerMillionUsd + usage.outputTokens * pricing.outputPerMillionUsd) / 1_000_000).toFixed(8))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const validateStudyProviderCommand = (provider: Pick<StudyProviderCli, 'command'>, cwd: string): void => {
|
|
99
|
+
if (isAbsolute(provider.command) || provider.command.includes('/')) {
|
|
100
|
+
const resolvedPath = isAbsolute(provider.command) ? provider.command : resolve(cwd, provider.command)
|
|
101
|
+
if (!existsSync(resolvedPath) || !statSync(resolvedPath).isFile()) throw new Error(`Provider CLI command is not executable at ${resolvedPath}.`)
|
|
102
|
+
try { accessSync(resolvedPath, constants.X_OK) } catch { throw new Error(`Provider CLI command is not executable at ${resolvedPath}.`) }
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
const pathEntries = (process.env.PATH ?? '').split(':').filter(Boolean)
|
|
106
|
+
if (pathEntries.some((entry) => { try { accessSync(resolve(entry, provider.command), constants.X_OK); return true } catch { return false } })) return
|
|
107
|
+
throw new Error(`Provider CLI command ${provider.command} was not found on PATH.`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const formatStudyProviderCliText = (config: StudyProviderCliConfigV1): readonly string[] => [
|
|
111
|
+
`Provider CLI config: ${config.configVersion}`,
|
|
112
|
+
`Mappings: ${config.providers.length}`,
|
|
113
|
+
`Hosted providers: ${config.providers.filter((provider) => provider.providerNetwork).length}`,
|
|
114
|
+
`Content hash: ${config.contentHash}`,
|
|
115
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
|
|
3
|
+
const TOOL_TYPES = new Set([
|
|
4
|
+
'command_execution',
|
|
5
|
+
'mcp_tool_call',
|
|
6
|
+
'web_search_call',
|
|
7
|
+
'file_search_call',
|
|
8
|
+
'computer_call',
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
const byteLength = (value: unknown): number => {
|
|
12
|
+
if (value === undefined || value === null) return 0
|
|
13
|
+
const encoded = typeof value === 'string' ? value : JSON.stringify(value)
|
|
14
|
+
return encoded === undefined ? 0 : Buffer.byteLength(encoded, 'utf8')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const firstPresent = (value: Record<string, unknown>, keys: readonly string[]): unknown => {
|
|
18
|
+
for (const key of keys) {
|
|
19
|
+
if (value[key] !== undefined && value[key] !== null) return value[key]
|
|
20
|
+
}
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ProviderToolTelemetry = {
|
|
25
|
+
readonly observedToolEventCount: number
|
|
26
|
+
readonly observedToolInputBytes: number
|
|
27
|
+
readonly observedToolOutputBytes: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const measureProviderToolTelemetry = (events: readonly unknown[]): ProviderToolTelemetry => {
|
|
31
|
+
let observedToolEventCount = 0
|
|
32
|
+
let observedToolInputBytes = 0
|
|
33
|
+
let observedToolOutputBytes = 0
|
|
34
|
+
|
|
35
|
+
for (const event of events) {
|
|
36
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) continue
|
|
37
|
+
const record = event as { readonly type?: unknown; readonly item?: unknown }
|
|
38
|
+
if (record.type !== 'item.completed' || !record.item || typeof record.item !== 'object' || Array.isArray(record.item)) continue
|
|
39
|
+
const item = record.item as Record<string, unknown>
|
|
40
|
+
if (typeof item.type !== 'string' || !TOOL_TYPES.has(item.type)) continue
|
|
41
|
+
observedToolEventCount += 1
|
|
42
|
+
observedToolInputBytes += byteLength(firstPresent(item, ['command', 'arguments', 'input', 'params']))
|
|
43
|
+
observedToolOutputBytes += byteLength(firstPresent(item, ['aggregated_output', 'output', 'result', 'content']))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { observedToolEventCount, observedToolInputBytes, observedToolOutputBytes }
|
|
47
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type ScorecardStatus = 'pass' | 'partial' | 'not-analyzed' | 'fail'
|
|
2
|
+
|
|
3
|
+
export type ScorecardCriterion = {
|
|
4
|
+
readonly id: string
|
|
5
|
+
readonly dimension: string
|
|
6
|
+
readonly status: ScorecardStatus
|
|
7
|
+
readonly observed: number | null
|
|
8
|
+
readonly threshold: number | null
|
|
9
|
+
readonly unit: string
|
|
10
|
+
readonly required: boolean
|
|
11
|
+
readonly evidence: readonly string[]
|
|
12
|
+
readonly reason: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ScorecardDimension = {
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly status: ScorecardStatus
|
|
18
|
+
readonly criteria: readonly string[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type QualityScorecard = {
|
|
22
|
+
readonly type: 'doc-bridge-quality-scorecard'
|
|
23
|
+
readonly schemaVersion: 1
|
|
24
|
+
readonly decision: 'ready' | 'not-ready'
|
|
25
|
+
readonly criteria: readonly ScorecardCriterion[]
|
|
26
|
+
readonly dimensions: readonly ScorecardDimension[]
|
|
27
|
+
readonly summary: {
|
|
28
|
+
readonly pass: number
|
|
29
|
+
readonly partial: number
|
|
30
|
+
readonly notAnalyzed: number
|
|
31
|
+
readonly fail: number
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type QualityScorecardInput = {
|
|
36
|
+
readonly semanticFixture: {
|
|
37
|
+
readonly precision: number
|
|
38
|
+
readonly recall: number
|
|
39
|
+
readonly evidenceRatio: number
|
|
40
|
+
}
|
|
41
|
+
readonly semanticTask: {
|
|
42
|
+
readonly repositoryOnlySuccessRate: number
|
|
43
|
+
readonly docBridgeSuccessRate: number
|
|
44
|
+
}
|
|
45
|
+
readonly acceptanceInstrumentation: {
|
|
46
|
+
readonly observations: number
|
|
47
|
+
readonly complete: number
|
|
48
|
+
}
|
|
49
|
+
readonly tokenPilot: {
|
|
50
|
+
readonly reductionPct: number
|
|
51
|
+
readonly replicates: number
|
|
52
|
+
}
|
|
53
|
+
readonly cost: {
|
|
54
|
+
readonly observedUsd: number | null
|
|
55
|
+
}
|
|
56
|
+
readonly generalization: {
|
|
57
|
+
readonly populations: number
|
|
58
|
+
readonly models: number
|
|
59
|
+
readonly replicates: number
|
|
60
|
+
}
|
|
61
|
+
readonly documentation: {
|
|
62
|
+
readonly titleRate: number
|
|
63
|
+
readonly requiredSectionsRate: number
|
|
64
|
+
readonly examplesRate: number
|
|
65
|
+
readonly contradictions: number
|
|
66
|
+
readonly stale: number
|
|
67
|
+
readonly structureGaps: number
|
|
68
|
+
readonly exactDuplicateGroups: number
|
|
69
|
+
readonly criticalMetadataCoverage: number
|
|
70
|
+
readonly semanticNotAnalyzed: number
|
|
71
|
+
readonly semanticReviewed: number
|
|
72
|
+
readonly semanticCorpus: number
|
|
73
|
+
readonly semanticContradictionCandidates: number
|
|
74
|
+
readonly semanticRedundancyCandidates: number
|
|
75
|
+
readonly semanticMissingCandidates: number
|
|
76
|
+
readonly semanticClaimCandidates: number
|
|
77
|
+
}
|
|
78
|
+
readonly evidence: {
|
|
79
|
+
readonly contentHashes: number
|
|
80
|
+
readonly requiredHashes: number
|
|
81
|
+
}
|
|
82
|
+
readonly latency: {
|
|
83
|
+
readonly reductionPct: number
|
|
84
|
+
}
|
|
85
|
+
readonly thresholds: {
|
|
86
|
+
readonly fixturePrecision: number
|
|
87
|
+
readonly fixtureRecall: number
|
|
88
|
+
readonly fixtureEvidenceRatio: number
|
|
89
|
+
readonly taskSuccessRate: number
|
|
90
|
+
readonly tokenReductionPct: number
|
|
91
|
+
readonly tokenReplicates: number
|
|
92
|
+
readonly populationCount: number
|
|
93
|
+
readonly modelCount: number
|
|
94
|
+
readonly generalizationReplicates: number
|
|
95
|
+
readonly documentationRate: number
|
|
96
|
+
readonly examplesRate: number
|
|
97
|
+
readonly criticalMetadataCoverage: number
|
|
98
|
+
readonly semanticNotAnalyzedMaximum: number
|
|
99
|
+
readonly semanticReviewCoverage: number
|
|
100
|
+
readonly acceptanceInstrumentationCoverage: number
|
|
101
|
+
readonly latencyReductionPct: number
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const criterion = (
|
|
106
|
+
id: string,
|
|
107
|
+
dimension: string,
|
|
108
|
+
status: ScorecardStatus,
|
|
109
|
+
observed: number | null,
|
|
110
|
+
threshold: number | null,
|
|
111
|
+
unit: string,
|
|
112
|
+
required: boolean,
|
|
113
|
+
evidence: readonly string[],
|
|
114
|
+
reason: string,
|
|
115
|
+
): ScorecardCriterion => ({ id, dimension, status, observed, threshold, unit, required, evidence, reason })
|
|
116
|
+
|
|
117
|
+
const dimensionStatus = (criteria: readonly ScorecardCriterion[]): ScorecardStatus => {
|
|
118
|
+
if (criteria.some((item) => item.status === 'fail')) return 'fail'
|
|
119
|
+
if (criteria.some((item) => item.status === 'not-analyzed')) return 'not-analyzed'
|
|
120
|
+
if (criteria.some((item) => item.status === 'partial')) return 'partial'
|
|
121
|
+
return 'pass'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const evaluateQualityScorecard = (input: QualityScorecardInput): QualityScorecard => {
|
|
125
|
+
const t = input.thresholds
|
|
126
|
+
const criteria = [
|
|
127
|
+
criterion('semantic-fixture-classification', 'semantic-correctness', input.semanticFixture.precision >= t.fixturePrecision && input.semanticFixture.recall >= t.fixtureRecall && input.semanticFixture.evidenceRatio >= t.fixtureEvidenceRatio ? 'pass' : 'fail', input.semanticFixture.precision, t.fixturePrecision, 'precision', true, ['tests/semantic-benchmark.test.ts'], 'Known structured reconciliation cases meet the fixture threshold.'),
|
|
128
|
+
criterion('semantic-task-correctness', 'semantic-correctness', Math.min(input.semanticTask.repositoryOnlySuccessRate, input.semanticTask.docBridgeSuccessRate) >= t.taskSuccessRate ? 'pass' : 'fail', input.semanticTask.docBridgeSuccessRate, t.taskSuccessRate, 'success-rate', true, ['docs/study/ab-adjudicated-cost-result-v1.json'], 'Independent task adjudication must demonstrate the configured success threshold in both arms.'),
|
|
129
|
+
criterion('acceptance-instrumentation', 'semantic-correctness', input.acceptanceInstrumentation.observations > 0 && input.acceptanceInstrumentation.complete / input.acceptanceInstrumentation.observations >= t.acceptanceInstrumentationCoverage ? 'pass' : 'fail', input.acceptanceInstrumentation.observations > 0 ? input.acceptanceInstrumentation.complete / input.acceptanceInstrumentation.observations : null, t.acceptanceInstrumentationCoverage, 'coverage', true, ['docs/study/phase4-public-pilot-ledger-v1.json', 'src/study/execution.ts'], 'Every study observation must record passed, total, and executed acceptance-check counts; the adjudicator separately blocks passes when execution is incomplete.'),
|
|
130
|
+
criterion('token-reduction-positive', 'token-efficiency', input.tokenPilot.reductionPct >= t.tokenReductionPct ? 'pass' : 'fail', input.tokenPilot.reductionPct, t.tokenReductionPct, 'percent', true, ['docs/study/phase4-public-pilot-result-v1.json'], 'The observed paired token-equivalent delta is positive for the declared pilot.'),
|
|
131
|
+
criterion('token-reduction-consistent', 'token-efficiency', input.tokenPilot.replicates >= t.tokenReplicates ? 'pass' : 'fail', input.tokenPilot.replicates, t.tokenReplicates, 'replicates', true, ['docs/study/phase4-public-pilot-result-v1.json'], 'A single pilot cannot establish consistent reduction; the configured replicate minimum is required.'),
|
|
132
|
+
criterion('currency-cost', 'cost', input.cost.observedUsd === null ? 'not-analyzed' : 'pass', input.cost.observedUsd, 0, 'USD', true, ['docs/study/phase4-public-pilot-result-v1.json'], input.cost.observedUsd === null ? 'Provider pricing or observed USD cost is absent; no financial claim is allowed.' : 'Observed USD cost is available.'),
|
|
133
|
+
criterion('population-generalization', 'generalization', input.generalization.populations >= t.populationCount ? 'pass' : 'fail', input.generalization.populations, t.populationCount, 'populations', true, ['docs/study/phase4-public-pilot-result-v1.json'], 'Enterprise generalization requires multiple independent populations.'),
|
|
134
|
+
criterion('model-generalization', 'generalization', input.generalization.models >= t.modelCount ? 'pass' : 'fail', input.generalization.models, t.modelCount, 'models', true, ['docs/study/phase4-public-pilot-result-v1.json'], 'The minimum model diversity must be present before generalization claims.'),
|
|
135
|
+
criterion('replicate-generalization', 'generalization', input.generalization.replicates >= t.generalizationReplicates ? 'pass' : 'fail', input.generalization.replicates, t.generalizationReplicates, 'replicates', true, ['docs/study/phase4-public-pilot-result-v1.json'], 'Each task needs repeated observations before generalization is promoted.'),
|
|
136
|
+
criterion('documentation-deterministic-quality', 'documentation-quality', input.documentation.titleRate >= t.documentationRate && input.documentation.requiredSectionsRate >= t.documentationRate && input.documentation.contradictions === 0 && input.documentation.stale === 0 && input.documentation.structureGaps === 0 && input.documentation.criticalMetadataCoverage >= t.criticalMetadataCoverage ? 'pass' : 'fail', Math.min(input.documentation.titleRate, input.documentation.requiredSectionsRate, input.documentation.criticalMetadataCoverage), t.documentationRate, 'coverage', true, ['node bin/ak-docs.js audit documentation --json'], 'Structural quality, contradiction, stale-content, and critical metadata gates pass for the declared corpus.'),
|
|
137
|
+
criterion('documentation-example-coverage', 'documentation-quality', input.documentation.examplesRate >= t.examplesRate ? 'pass' : input.documentation.examplesRate > 0 ? 'partial' : 'fail', input.documentation.examplesRate, t.examplesRate, 'coverage', false, ['node bin/ak-docs.js audit documentation --json'], 'Examples are measured as a quality signal; not every document requires an example, so this criterion is advisory until the corpus declares eligibility.'),
|
|
138
|
+
criterion('documentation-exact-duplicates', 'documentation-quality', input.documentation.exactDuplicateGroups === 0 ? 'pass' : 'fail', input.documentation.exactDuplicateGroups, 0, 'groups', true, ['node bin/ak-docs.js audit documentation --json'], 'Exact duplicate documentation groups must be absent.'),
|
|
139
|
+
criterion('documentation-semantic-candidates', 'documentation-quality', input.documentation.semanticContradictionCandidates + input.documentation.semanticRedundancyCandidates + input.documentation.semanticMissingCandidates + input.documentation.semanticClaimCandidates === 0 ? 'pass' : 'fail', input.documentation.semanticContradictionCandidates + input.documentation.semanticRedundancyCandidates + input.documentation.semanticMissingCandidates + input.documentation.semanticClaimCandidates, 0, 'candidates', true, ['semantic-documentation-review', 'semantic-adjudication'], 'Bounded semantic review candidates must be resolved before the reviewed scope is promoted.'),
|
|
140
|
+
criterion('documentation-semantic-review-coverage', 'documentation-quality', input.documentation.semanticCorpus > 0 && input.documentation.semanticReviewed / input.documentation.semanticCorpus >= t.semanticReviewCoverage ? 'pass' : input.documentation.semanticReviewed > 0 ? 'partial' : 'not-analyzed', input.documentation.semanticCorpus > 0 ? input.documentation.semanticReviewed / input.documentation.semanticCorpus : null, t.semanticReviewCoverage, 'coverage', false, ['semantic-documentation-review', 'node bin/ak-docs.js audit documentation --json'], 'Semantic review coverage is reported separately from deterministic checks; bounded review does not prove full-corpus correctness.'),
|
|
141
|
+
criterion('documentation-semantic-coverage', 'documentation-quality', input.documentation.semanticNotAnalyzed <= t.semanticNotAnalyzedMaximum ? 'pass' : 'partial', input.documentation.semanticNotAnalyzed, t.semanticNotAnalyzedMaximum, 'documents', false, ['.codex/verification semantic-documentation-review'], 'Natural-language semantics remain bounded to the configured review scope; unreviewed documents stay visible.'),
|
|
142
|
+
criterion('evidence-reproducibility', 'evidence-quality', input.evidence.contentHashes >= input.evidence.requiredHashes ? 'pass' : 'fail', input.evidence.contentHashes, input.evidence.requiredHashes, 'hashes', true, ['docs/study/*result*.json'], 'All required study artifacts must expose reproducible content hashes.'),
|
|
143
|
+
criterion('latency-improvement', 'agent-efficiency', input.latency.reductionPct >= t.latencyReductionPct ? 'pass' : 'partial', input.latency.reductionPct, t.latencyReductionPct, 'percent', false, ['docs/study/phase4-public-pilot-result-v1.json'], 'Latency is tracked separately from correctness and token cost.'),
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
const dimensions = [...new Set(criteria.map((item) => item.dimension))].map((id) => {
|
|
147
|
+
const items = criteria.filter((item) => item.dimension === id)
|
|
148
|
+
return { id, status: dimensionStatus(items), criteria: items.map((item) => item.id) }
|
|
149
|
+
})
|
|
150
|
+
const summary = {
|
|
151
|
+
pass: criteria.filter((item) => item.status === 'pass').length,
|
|
152
|
+
partial: criteria.filter((item) => item.status === 'partial').length,
|
|
153
|
+
notAnalyzed: criteria.filter((item) => item.status === 'not-analyzed').length,
|
|
154
|
+
fail: criteria.filter((item) => item.status === 'fail').length,
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
type: 'doc-bridge-quality-scorecard',
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
decision: criteria.some((item) => item.required && item.status !== 'pass') ? 'not-ready' : 'ready',
|
|
160
|
+
criteria,
|
|
161
|
+
dimensions,
|
|
162
|
+
summary,
|
|
163
|
+
}
|
|
164
|
+
}
|