Skip to content

Commit 3a100c9

Browse files
os-zhuangclaude
andauthored
chore(docs): tooling to keep hand-written docs in sync with implementation (#1906)
* chore(docs): tooling to keep hand-written docs in sync with implementation Stands up recurring implementation-accuracy verification for the 128 hand-written docs as the platform evolves, layered cheapest-and-earliest first: 1. scripts/docs-audit/affected-docs.mjs — maps packages/** changes to the hand-written docs that reference the affected packages (by npm name / repo path), so an audit can be scoped to what actually changed instead of re-auditing everything. Supports --all and --json. 2. .github/workflows/docs-drift-check.yml — advisory CI gate: on PRs touching packages/**, posts a sticky PR comment listing the docs that reference the changed code. Flags drift at the source; never fails the build. 3. .claude/workflows/docs-accuracy-audit.js — the reusable multi-agent audit (audit + adversarial verifier per doc), parameterized by args.docs; defaults to all hand-written docs. Same pipeline that produced #1866 and #1904. 4. scripts/docs-audit/README.md — how the four parts fit together, including the scheduled-routine backstop. References (content/docs/references/, generated from packages/spec) are out of scope and handled by a separate regenerate pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: add no-release changeset for docs-drift tooling Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8c7e7e4 commit 3a100c9

5 files changed

Lines changed: 429 additions & 0 deletions

File tree

.changeset/docs-drift-tooling.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
chore(docs): add recurring doc-accuracy verification tooling — `affected-docs.mjs` change→docs mapping, an advisory `docs-drift-check` CI gate, and a reusable `docs-accuracy-audit` workflow. Repo-internal tooling; no package version impact.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
export const meta = {
2+
name: 'docs-accuracy-audit',
3+
description: 'Audit + fix hand-written ObjectStack docs against actual implementation, with adversarial verification. Scope with args.docs; defaults to all hand-written docs.',
4+
whenToUse: 'Periodic or change-scoped documentation accuracy verification. Pass args.docs = [paths] to scope (e.g. output of scripts/docs-audit/affected-docs.mjs); omit for a full audit of every hand-written doc.',
5+
phases: [
6+
{ title: 'Audit & Fix', detail: 'one agent per doc: read, locate implementation, apply evidence-backed edits' },
7+
{ title: 'Adversarial Verify', detail: 'second agent re-checks each applied fix against code, repairs regressions' },
8+
],
9+
}
10+
11+
// Default scope = every hand-written doc (content/docs/** minus references/). Keep in
12+
// sync with `node scripts/docs-audit/affected-docs.mjs --all`. Callers normally pass a
13+
// scoped subset via args.docs (e.g. only docs whose backing packages/ code changed).
14+
const ALL_HANDWRITTEN = ["content/docs/concepts/architecture.mdx","content/docs/concepts/cloud-artifact-api.mdx","content/docs/concepts/cluster-semantics.mdx","content/docs/concepts/core/architecture.mdx","content/docs/concepts/core/events.mdx","content/docs/concepts/core/index.mdx","content/docs/concepts/core/plugins.mdx","content/docs/concepts/core/services.mdx","content/docs/concepts/design-principles.mdx","content/docs/concepts/implementation-status.mdx","content/docs/concepts/index.mdx","content/docs/concepts/metadata-driven.mdx","content/docs/concepts/metadata-lifecycle.mdx","content/docs/concepts/north-star.mdx","content/docs/concepts/packages.mdx","content/docs/concepts/setup-app.mdx","content/docs/concepts/skills.mdx","content/docs/concepts/terminology.mdx","content/docs/concepts/webhook-delivery.mdx","content/docs/getting-started/architecture.mdx","content/docs/getting-started/cli.mdx","content/docs/getting-started/core-concepts.mdx","content/docs/getting-started/examples.mdx","content/docs/getting-started/glossary.mdx","content/docs/getting-started/index.mdx","content/docs/getting-started/quick-start.mdx","content/docs/guides/adding-a-metadata-type.mdx","content/docs/guides/ai-capabilities.mdx","content/docs/guides/airtable-dashboard-analysis.mdx","content/docs/guides/analytics-datasets.mdx","content/docs/guides/api-reference.mdx","content/docs/guides/auth-sso.mdx","content/docs/guides/authentication.mdx","content/docs/guides/business-logic.mdx","content/docs/guides/cheatsheets/backward-compatibility.mdx","content/docs/guides/cheatsheets/error-catalog.mdx","content/docs/guides/cheatsheets/field-type-decision-tree.mdx","content/docs/guides/cheatsheets/field-type-gallery.mdx","content/docs/guides/cheatsheets/field-validation-rules.mdx","content/docs/guides/cheatsheets/permissions-matrix.mdx","content/docs/guides/cheatsheets/protocol-diagram.mdx","content/docs/guides/cheatsheets/query-cheat-sheet.mdx","content/docs/guides/cheatsheets/quick-reference.mdx","content/docs/guides/cheatsheets/wire-format.mdx","content/docs/guides/client-sdk.mdx","content/docs/guides/cloud-deployment.mdx","content/docs/guides/common-patterns.mdx","content/docs/guides/contracts/auth-service.mdx","content/docs/guides/contracts/cache-service.mdx","content/docs/guides/contracts/data-engine.mdx","content/docs/guides/contracts/index.mdx","content/docs/guides/contracts/metadata-service.mdx","content/docs/guides/contracts/storage-service.mdx","content/docs/guides/data-flow.mdx","content/docs/guides/data-modeling.mdx","content/docs/guides/deployment-vercel.mdx","content/docs/guides/driver-configuration.mdx","content/docs/guides/environment-variables.mdx","content/docs/guides/error-handling-client.mdx","content/docs/guides/error-handling-server.mdx","content/docs/guides/formula.mdx","content/docs/guides/hook-bodies.mdx","content/docs/guides/index.mdx","content/docs/guides/kernel-services.mdx","content/docs/guides/metadata/app.mdx","content/docs/guides/metadata/dashboard.mdx","content/docs/guides/metadata/doc.mdx","content/docs/guides/metadata/field.mdx","content/docs/guides/metadata/flow.mdx","content/docs/guides/metadata/index.mdx","content/docs/guides/metadata/object.mdx","content/docs/guides/metadata/page.mdx","content/docs/guides/metadata/permission.mdx","content/docs/guides/metadata/validation.mdx","content/docs/guides/metadata/view.mdx","content/docs/guides/metadata/workflow.mdx","content/docs/guides/objectql-migration.mdx","content/docs/guides/packages.mdx","content/docs/guides/plugin-chatbot-integration.mdx","content/docs/guides/plugin-development.mdx","content/docs/guides/plugins.mdx","content/docs/guides/production-readiness.mdx","content/docs/guides/project-scoping.mdx","content/docs/guides/public-forms.mdx","content/docs/guides/publish-and-preview.mdx","content/docs/guides/runtime-services/audit-service.mdx","content/docs/guides/runtime-services/data-service.mdx","content/docs/guides/runtime-services/email-service.mdx","content/docs/guides/runtime-services/examples.mdx","content/docs/guides/runtime-services/index.mdx","content/docs/guides/runtime-services/queue-service.mdx","content/docs/guides/runtime-services/settings-service.mdx","content/docs/guides/runtime-services/sharing-service.mdx","content/docs/guides/runtime-services/storage-service.mdx","content/docs/guides/runtime-services/versioning.mdx","content/docs/guides/security.mdx","content/docs/guides/seed-data.mdx","content/docs/guides/single-project-mode.mdx","content/docs/guides/skills.mdx","content/docs/guides/standards.mdx","content/docs/guides/troubleshooting.mdx","content/docs/index.mdx","content/docs/protocol/index.mdx","content/docs/protocol/knowledge.mdx","content/docs/protocol/objectos/config-resolution.mdx","content/docs/protocol/objectos/error-handling.mdx","content/docs/protocol/objectos/http-protocol.mdx","content/docs/protocol/objectos/i18n-standard.mdx","content/docs/protocol/objectos/index.mdx","content/docs/protocol/objectos/lifecycle.mdx","content/docs/protocol/objectos/metadata-service.mdx","content/docs/protocol/objectos/plugin-spec.mdx","content/docs/protocol/objectos/realtime-protocol.mdx","content/docs/protocol/objectos/runtime-capabilities.mdx","content/docs/protocol/objectql/index.mdx","content/docs/protocol/objectql/query-syntax.mdx","content/docs/protocol/objectql/schema.mdx","content/docs/protocol/objectql/security.mdx","content/docs/protocol/objectql/state-machine.mdx","content/docs/protocol/objectql/types.mdx","content/docs/protocol/objectui/actions.mdx","content/docs/protocol/objectui/concept.mdx","content/docs/protocol/objectui/index.mdx","content/docs/protocol/objectui/layout-dsl.mdx","content/docs/protocol/objectui/record-alert.mdx","content/docs/protocol/objectui/widget-contract.mdx","content/docs/releases/index.mdx","content/docs/releases/v9.mdx"]
15+
16+
const DOCS = (args && Array.isArray(args.docs) && args.docs.length) ? args.docs : ALL_HANDWRITTEN
17+
18+
const PACKAGE_MAP = `ObjectStack is a metadata-driven application framework. Implementation lives in packages/:
19+
- packages/spec — Zod schemas for every metadata type (.zod.ts); source of truth for shapes & enums. Also packages/spec/src/{data,ui,...}.
20+
- packages/core — kernel: plugin system, service registry, lifecycle, events.
21+
- packages/runtime — runtime services (data, email, queue, settings, sharing, storage, audit, versioning).
22+
- packages/services — service contracts/interfaces (IAuthService, IDataEngine, etc.).
23+
- packages/metadata, metadata-core, metadata-fs — metadata loading/registry (meta.getItem(type, name)).
24+
- packages/cli — the \`os\` CLI; commands in packages/cli/src/commands/ (dev.ts, init.ts, serve.ts, meta/, data/, cloud/, etc.).
25+
- packages/client + client-react — client SDK; public surface in packages/client/src/index.ts.
26+
- packages/rest — REST API; data routes mounted under /api/v1/data/{object}.
27+
- packages/objectql — ObjectQL query engine + types.
28+
- packages/formula — CEL formula engine.
29+
- packages/triggers, observability, mcp, connectors, adapters, platform-objects.
30+
- packages/plugins/* — plugin-auth, plugin-security, plugin-sharing, plugin-approvals, plugin-email, plugin-webhooks, plugin-reports, plugin-audit, plugin-dev, plugin-hono-server, driver-*, knowledge-*, embedder-*.
31+
- apps/console — the admin console app; apps/docs — this docs site.`
32+
33+
const HOUSE_FACTS = `ESTABLISHED CORRECTIONS from prior doc-accuracy audits (PR #1866 + #1904) — already applied across the docs. Treat as strong priors and apply CONSISTENTLY; re-verify against current code only if a doc's usage looks context-specific:
34+
- CLI binary is \`os\`. There is NO \`os studio\` command — the UI dev command is \`os dev --ui\` (verify in packages/cli/src/commands/dev.ts).
35+
- Metadata access by type+name: \`meta.getItem('object', name)\` — NOT \`client.meta.getObject()\`.
36+
- \`client.ai.chat()\` was REMOVED — do not reference it.
37+
- Approvals are request-id based (ADR-0019): \`client.approvals.*\` — NOT \`client.workflow.approve/reject\`.
38+
- There are NO \`defineProfile\` / \`defineAction\` / \`defineHook\` / \`definePlugin\` helper functions.
39+
- Security model is the real PermissionSet schema (objects/fields, allowCreate/allowRead/allowEdit/allowDelete/..., isProfile) — NOT a Salesforce-style Profile type with objectPermissions/fieldPermissions. FLS non-editable write REJECTS with PermissionDeniedError (403), it is not silently stripped. OWDModel values are public_read / public_read_write.
40+
- Formulas/conditions use CEL — NOT Salesforce UPPERCASE functions.
41+
- Console/portal path is /_console — NOT /_studio.
42+
- REST data path is /api/v1/data/{object} — NOT /api/v1/{object}. REST routes carry the /v1 prefix.
43+
- Env vars are OS_AUTH_SECRET (not AUTH_SECRET) and OS_PORT (not PORT); nested keys use single underscore unless the schema says otherwise. Mock-server toggle is VITE_USE_MOCK_SERVER (no VITE_RUNTIME_MODE or ?mode= switch).
44+
- The repo is github.com/objectstack-ai/framework (NOT objectstack-ai/spec). Fix broken cross-repo links/paths accordingly.
45+
- Package names: @objectstack/<x> (e.g. @objectstack/service-cache, NOT @objectstack/services/service-cache). Some types only export via subpaths (e.g. @objectstack/spec/ui).
46+
- Auto-generated reference docs live in content/docs/references/ and are OUT OF SCOPE — never edit them.`
47+
48+
const RULES = `HARD RULES:
49+
1. Edit the doc FILE IN PLACE with Edit/Write. The edits to disk are the real deliverable; your structured output is just a log of what you changed.
50+
2. PRESERVE frontmatter (the --- title/description block) EXACTLY. Do NOT move, rename, or change the file's path or slug.
51+
3. Keep MDX/JSX valid: <Callout>, <Tabs>, <Steps>, code fences, import lines must stay well-formed.
52+
4. EVERY factual fix must be backed by evidence you actually read — cite file:line. If you cannot find code confirming a claim is wrong, DO NOT change it. Record it under 'unresolved' instead.
53+
5. Do not fabricate APIs, flags, paths, or features. If the doc describes a feature that does NOT exist in code (removed/aspirational), remove it or qualify it as not-yet-implemented — backed by grep-empty evidence.
54+
6. Make minimal, precise edits — fix what is wrong, leave correct prose alone.
55+
7. Verify code samples, CLI commands, API method names, config keys, env vars, file paths, enum values, and links against the actual implementation.`
56+
57+
const FIX_LOG_SCHEMA = {
58+
type: 'object', additionalProperties: false,
59+
required: ['doc', 'implementationFound', 'fixesApplied', 'fixCount', 'unresolved', 'notes'],
60+
properties: {
61+
doc: { type: 'string' },
62+
implementationFound: { type: 'boolean' },
63+
fixesApplied: { type: 'array', items: { type: 'object', additionalProperties: false,
64+
required: ['category', 'summary', 'before', 'after', 'evidence'],
65+
properties: {
66+
category: { type: 'string', enum: ['broken-example', 'inaccurate-api', 'outdated-path', 'outdated-env', 'security-model', 'fabricated-feature', 'broken-link', 'naming-drift', 'enum-drift', 'other'] },
67+
summary: { type: 'string' }, before: { type: 'string' }, after: { type: 'string' }, evidence: { type: 'string' },
68+
} } },
69+
fixCount: { type: 'number' },
70+
unresolved: { type: 'array', items: { type: 'string' } },
71+
notes: { type: 'string' },
72+
},
73+
}
74+
75+
const VERDICT_SCHEMA = {
76+
type: 'object', additionalProperties: false,
77+
required: ['doc', 'fixesReviewed', 'confirmed', 'correctionsMade', 'regressionsFound', 'buildSafe', 'residualInaccuracies'],
78+
properties: {
79+
doc: { type: 'string' },
80+
fixesReviewed: { type: 'number' },
81+
confirmed: { type: 'number' },
82+
correctionsMade: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['what', 'why', 'evidence'], properties: { what: { type: 'string' }, why: { type: 'string' }, evidence: { type: 'string' } } } },
83+
regressionsFound: { type: 'array', items: { type: 'string' } },
84+
buildSafe: { type: 'boolean' },
85+
residualInaccuracies: { type: 'array', items: { type: 'string' } },
86+
},
87+
}
88+
89+
function auditPrompt(doc) {
90+
return `You are auditing a single hand-written ObjectStack documentation file for IMPLEMENTATION ACCURACY and fixing it in place.
91+
92+
TARGET DOC: ${doc}
93+
94+
${PACKAGE_MAP}
95+
96+
${HOUSE_FACTS}
97+
98+
${RULES}
99+
100+
PROCEDURE:
101+
1. Read the entire doc (${doc}).
102+
2. For each technical claim — code sample, CLI command, client/server API call, method/type name, config key, enum value, env var, file path, route, link — LOCATE the backing implementation under packages/ (Grep/Glob/Read; ripgrep via Bash is fine) and confirm whether the doc matches reality.
103+
3. Apply evidence-backed fixes directly with Edit. Preserve frontmatter and MDX validity.
104+
4. If a section documents a non-existent/removed/aspirational feature, remove it or qualify it as not-yet-implemented (with grep-empty evidence).
105+
5. Return a structured log of every fix with file:line evidence, plus anything suspected-but-unconfirmed under 'unresolved'.
106+
107+
A doc with no real inaccuracies should return fixCount 0 — do not invent changes. The edits you write to disk ARE the deliverable.`
108+
}
109+
110+
function verifyPrompt(doc, fixLog) {
111+
return `You are the ADVERSARIAL VERIFIER for an implementation-accuracy fix just applied to an ObjectStack doc. Assume the previous agent may have over-corrected or introduced errors.
112+
113+
TARGET DOC (already edited): ${doc}
114+
115+
FIX LOG from the audit agent (JSON):
116+
${JSON.stringify(fixLog).slice(0, 6000)}
117+
118+
${PACKAGE_MAP}
119+
120+
${HOUSE_FACTS}
121+
122+
${RULES}
123+
124+
PROCEDURE:
125+
1. Read the current (edited) doc.
126+
2. For EACH applied fix, independently verify the "after" value against the cited evidence AND the live code (re-grep/re-read — do NOT trust the evidence string blindly).
127+
3. Hunt for REGRESSIONS: broken MDX/JSX, altered frontmatter, changed slug, previously-correct content replaced with something wrong, or a NEW inaccuracy.
128+
4. If you find a wrong fix or regression, REPAIR it in place with Edit (same hard rules). Record each repair under correctionsMade with evidence.
129+
5. Confirm frontmatter intact and the file is MDX/build-safe.
130+
6. List remaining suspected inaccuracies under residualInaccuracies (report only; do not fix speculative items).
131+
132+
Return the verdict.`
133+
}
134+
135+
phase('Audit & Fix')
136+
log(`Auditing ${DOCS.length} hand-written doc(s) (pipelined: audit -> adversarial verify per doc)`)
137+
138+
const results = await pipeline(
139+
DOCS,
140+
(doc) => agent(auditPrompt(doc), { label: `audit:${doc.replace('content/docs/', '')}`, phase: 'Audit & Fix', schema: FIX_LOG_SCHEMA }),
141+
(fixLog, doc) => {
142+
if (!fixLog) return null
143+
return agent(verifyPrompt(doc, fixLog), { label: `verify:${doc.replace('content/docs/', '')}`, phase: 'Adversarial Verify', schema: VERDICT_SCHEMA })
144+
.then((v) => ({ doc, fixLog, verdict: v }))
145+
}
146+
)
147+
148+
const clean = results.filter(Boolean)
149+
const totalFixes = clean.reduce((n, r) => n + (r.fixLog?.fixCount || 0), 0)
150+
const totalRepairs = clean.reduce((n, r) => n + (r.verdict?.correctionsMade?.length || 0), 0)
151+
const totalResidual = clean.reduce((n, r) => n + (r.verdict?.residualInaccuracies?.length || 0), 0)
152+
153+
return {
154+
docsProcessed: clean.length,
155+
docsDropped: DOCS.length - clean.length,
156+
docsWithChanges: clean.filter((r) => (r.fixLog?.fixCount || 0) > 0 || (r.verdict?.correctionsMade?.length || 0) > 0).length,
157+
totalFixesApplied: totalFixes,
158+
totalVerifierRepairs: totalRepairs,
159+
totalResidualForFollowup: totalResidual,
160+
docsMissingVerifier: clean.filter((r) => !r.verdict).map((r) => r.doc),
161+
perDoc: clean.map((r) => ({
162+
doc: r.doc,
163+
fixes: r.fixLog?.fixCount || 0,
164+
implFound: r.fixLog?.implementationFound,
165+
confirmed: r.verdict?.confirmed,
166+
repairs: r.verdict?.correctionsMade?.length || 0,
167+
regressions: r.verdict?.regressionsFound || [],
168+
buildSafe: r.verdict?.buildSafe,
169+
residual: r.verdict?.residualInaccuracies || [],
170+
})),
171+
}

0 commit comments

Comments
 (0)