Skip to content

Commit 6e02c1c

Browse files
authored
docs(agent-docs): enforce canonical AGENTS symlinks (#3298)
1 parent 051d208 commit 6e02c1c

4 files changed

Lines changed: 111 additions & 90 deletions

File tree

.github/scripts/agent-docs-l1.mjs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const CONFIG = {
2828
'tests/consumer-typecheck/node_modules',
2929
],
3030
intentionalDifferentPairs: ['packages/superdoc/AGENTS.md:packages/superdoc/CLAUDE.md'],
31+
canonicalSymlinkTarget: 'AGENTS.md',
3132
knownCommands: [
3233
'pnpm test',
3334
'pnpm test:behavior',
@@ -285,9 +286,15 @@ function classifyPairs(files) {
285286
pairs.push({ dir, classification: 'intentional-different', detail: `allowlisted: ${a.lineCount}L vs ${c.lineCount}L` });
286287
continue;
287288
}
288-
const linked = (a.isSymlink && a.symlinkTarget === c.absPath) || (c.isSymlink && c.symlinkTarget === a.absPath);
289-
if (linked) {
290-
pairs.push({ dir, classification: 'linked', detail: `canonical: ${a.isSymlink ? 'CLAUDE.md' : 'AGENTS.md'}` });
289+
const agentsPointsToClaude = a.isSymlink && a.symlinkTarget === c.absPath;
290+
const claudePointsToAgents = c.isSymlink && c.symlinkTarget === a.absPath;
291+
if (agentsPointsToClaude || claudePointsToAgents) {
292+
const canonical = agentsPointsToClaude ? 'CLAUDE.md' : 'AGENTS.md';
293+
const classification = canonical === CONFIG.canonicalSymlinkTarget ? 'linked' : 'linked-inverted';
294+
const detail = canonical === CONFIG.canonicalSymlinkTarget
295+
? `canonical: ${canonical}`
296+
: `canonical: ${canonical}; expected ${CONFIG.canonicalSymlinkTarget}`;
297+
pairs.push({ dir, classification, detail });
291298
continue;
292299
}
293300
// Either side might be a broken symlink we already flagged; if so we
@@ -335,6 +342,12 @@ export function computeFlags(file) {
335342
return reasons;
336343
}
337344

345+
function pairFlaggedForReview(pair) {
346+
if (pair.classification === 'linked-inverted') return true;
347+
if (pair.classification === 'unexpected-duplicate') return true;
348+
return false;
349+
}
350+
338351
// L2/L3 gating: stricter than computeFlags. A single-broken-ref doc still
339352
// appears in the L1 report (via computeFlags) but only triggers paid L2/L3
340353
// review when there are 2+ broken refs or budget is significantly exceeded.
@@ -392,9 +405,14 @@ export function renderL1Markdown(scan) {
392405
findingsByFile.push({ file: f, reasons });
393406
}
394407
lines.push('\n## Deterministic findings\n');
395-
if (findingsByFile.length === 0) {
408+
const pairFindings = scan.pairs.filter(pairFlaggedForReview);
409+
if (findingsByFile.length === 0 && pairFindings.length === 0) {
396410
lines.push('None.\n');
397411
} else {
412+
for (const pair of pairFindings) {
413+
lines.push(`### \`${pair.dir || '(root)'}\`\n`);
414+
lines.push(`- ${pair.classification}: ${pair.detail}\n`);
415+
}
398416
for (const { file, reasons } of findingsByFile) {
399417
lines.push(`### \`${file.relPath}\`\n`);
400418
lines.push(reasons.map((r) => `- ${r}`).join('\n'));

AGENTS.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

AGENTS.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# SuperDoc
2+
3+
A document editing and rendering library for the web.
4+
5+
## Architecture: Rendering
6+
7+
SuperDoc uses its own rendering pipeline. ProseMirror stores document state; it is not the visual renderer.
8+
9+
```
10+
.docx
11+
→ super-converter parses OOXML into the hidden PM doc
12+
→ pm-adapter reads PM state and resolved styles
13+
→ FlowBlock[]
14+
→ layout-engine paginates
15+
→ ResolvedLayout
16+
→ DomPainter paints DOM
17+
```
18+
19+
- `PresentationEditor` wraps a hidden ProseMirror `Editor`. Its contenteditable DOM is never shown. PresentationEditor bridges editor events into layout/paint state; do not resolve OOXML semantics there.
20+
- **DomPainter** (`layout-engine/painters/dom/`) owns all visual rendering.
21+
- Style-resolved properties flow `pm-adapter` → DomPainter. Do not style document content with PM decorations.
22+
23+
### Where To Put Your Change
24+
25+
| Concern | Where | Rule |
26+
|---|---|---|
27+
| DOCX import/export | `super-editor/src/editors/v1/core/super-converter/` | Parse and preserve OOXML, style refs, inline properties. Do not bake resolved formatting into direct attrs. |
28+
| Style cascade | `layout-engine/style-engine/` | Single source of truth for defaults, styles, conditional formatting, inline overrides. |
29+
| Static document visuals | `pm-adapter/` data + `layout-engine/painters/dom/` rendering | Feed typed data into DomPainter. Do not style static content with PM decorations. |
30+
| Direction-aware properties | `layout-engine/painters/dom/` | DomPainter mirrors at paint time for `w:bidiVisual`. pm-adapter stores logical sides LTR-default. Pre-mirroring upstream is a double-swap. See `packages/layout-engine/pm-adapter/src/direction/README.md`. |
31+
| Editing behavior | `super-editor/src/editors/v1/extensions/` | Commands, keybindings, editor plugins. Do not duplicate cascade or render document visuals here. |
32+
| Final DOM rendering | `layout-engine/painters/dom/` | Render `ResolvedLayout`. Paint-time transforms (e.g. RTL mirror) live here. |
33+
| New doc-api operation | `packages/document-api/src/contract/operation-definitions.ts` | Contract-first; touches 4 files. See `packages/document-api/README.md`. |
34+
35+
For specialized boundaries (interaction mapping, geometry/pagination, ephemeral overlays, presentation state bridge, consumer SDK surface), see `packages/layout-engine/AGENTS.md` and the relevant package AGENTS.md.
36+
37+
### Boundary check
38+
39+
Before adding a visual or direction-aware path, run:
40+
41+
```bash
42+
# Painter must not import upstream packages.
43+
rg "@superdoc/(pm-adapter|style-engine|layout-bridge|layout-resolved)" packages/layout-engine/painters/dom/src
44+
```
45+
46+
More checks in `packages/layout-engine/AGENTS.md`.
47+
48+
## Style Resolution Boundary
49+
50+
The importer stores raw OOXML. The style-engine resolves at render time.
51+
52+
- Converter (`super-converter/`) parses and stores only what is explicitly in the XML.
53+
- Style-engine (`layout-engine/style-engine/`) owns cascade logic.
54+
55+
**Why**: resolving during import bakes inline properties into nodes; export then writes direct formatting instead of style references and loses document intent.
56+
57+
## Document API Contract
58+
59+
`packages/document-api/` uses a contract-first pattern.
60+
61+
- **`operation-definitions.ts`** is the canonical object. All downstream maps project from it.
62+
- **`operation-registry.ts`** is the type-level registry (`input`, `options`, `output`).
63+
- **`invoke.ts`** is the dispatch table, validated against the registry at compile time.
64+
65+
Adding an operation touches 4 files: `operation-definitions.ts`, `operation-registry.ts`, `invoke.ts`, and the implementation. Run `pnpm run generate:all` after. See `packages/document-api/README.md`.
66+
67+
Do not hand-edit `COMMAND_CATALOG`, `OPERATION_MEMBER_PATH_MAP`, `OPERATION_REFERENCE_DOC_PATH_MAP`, or `REFERENCE_OPERATION_GROUPS`. They are derived from `OPERATION_DEFINITIONS`.
68+
69+
## Commands
70+
71+
- `pnpm build` - build all packages
72+
- `pnpm test` - unit tests
73+
- `pnpm dev` - dev server from `examples/`
74+
- `pnpm run generate:all` - regenerate schemas, SDK clients, tool catalogs, reference docs
75+
76+
## Testing
77+
78+
| What to verify | Command | Speed |
79+
|---|---|---|
80+
| Logic works? | `pnpm test` | seconds |
81+
| Editing works? | `pnpm test:behavior` | minutes |
82+
| Layout regressed? | `pnpm test:layout` | ~10 min |
83+
| Pixel diff? | `pnpm test:visual` | ~5 min |
84+
85+
Per-package detail: `tests/behavior/AGENTS.md`, `tests/visual/AGENTS.md`. Eval suite: `evals/AGENTS.md`.

CLAUDE.md

Lines changed: 0 additions & 85 deletions
This file was deleted.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

agent-docs-policy.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ encyclopedias of the codebase.
1313
Loaded only when an agent reads files in that package.
1414
- **`.claude/rules/<topic>.md`** with `paths:` frontmatter: rules that only
1515
apply to matching files (e.g. JSDoc rules for `**/*.js`).
16+
- **Symlink pairs**: when both `AGENTS.md` and `CLAUDE.md` exist with the same
17+
content, make `AGENTS.md` canonical and symlink `CLAUDE.md` to it. Allowlist
18+
intentional audience-specific pairs that must differ.
1619
- **Hooks or scripts**: anything that must be enforced rather than advised.
1720
Doc-level "always do X" rules that have a deterministic check belong in CI,
1821
not in agent docs.

0 commit comments

Comments
 (0)