Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions .github/scripts/agent-docs-l1.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const CONFIG = {
'tests/consumer-typecheck/node_modules',
],
intentionalDifferentPairs: ['packages/superdoc/AGENTS.md:packages/superdoc/CLAUDE.md'],
canonicalSymlinkTarget: 'AGENTS.md',
knownCommands: [
'pnpm test',
'pnpm test:behavior',
Expand Down Expand Up @@ -285,9 +286,15 @@ function classifyPairs(files) {
pairs.push({ dir, classification: 'intentional-different', detail: `allowlisted: ${a.lineCount}L vs ${c.lineCount}L` });
continue;
}
const linked = (a.isSymlink && a.symlinkTarget === c.absPath) || (c.isSymlink && c.symlinkTarget === a.absPath);
if (linked) {
pairs.push({ dir, classification: 'linked', detail: `canonical: ${a.isSymlink ? 'CLAUDE.md' : 'AGENTS.md'}` });
const agentsPointsToClaude = a.isSymlink && a.symlinkTarget === c.absPath;
const claudePointsToAgents = c.isSymlink && c.symlinkTarget === a.absPath;
if (agentsPointsToClaude || claudePointsToAgents) {
const canonical = agentsPointsToClaude ? 'CLAUDE.md' : 'AGENTS.md';
const classification = canonical === CONFIG.canonicalSymlinkTarget ? 'linked' : 'linked-inverted';
const detail = canonical === CONFIG.canonicalSymlinkTarget
? `canonical: ${canonical}`
: `canonical: ${canonical}; expected ${CONFIG.canonicalSymlinkTarget}`;
pairs.push({ dir, classification, detail });
continue;
}
// Either side might be a broken symlink we already flagged; if so we
Expand Down Expand Up @@ -335,6 +342,12 @@ export function computeFlags(file) {
return reasons;
}

function pairFlaggedForReview(pair) {
if (pair.classification === 'linked-inverted') return true;
if (pair.classification === 'unexpected-duplicate') return true;
Comment on lines +346 to +347
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include pair findings in the full audit summary

When an inverted symlink pair is found during a full weekly/dispatch audit with ANTHROPIC_API_KEY available, this helper marks the pair as review-worthy only for renderL1Markdown; flaggedForReview(scan) still returns only files, and .github/scripts/agent-docs-audit.mjs prints renderMarkdown(report) rather than the L1 markdown on the non-skip path. As a result, linked-inverted pairs are uploaded in /tmp/agent-docs-audit-l1.md but are absent from the Step Summary/JSON that reviewers normally see, so the new canonical-direction finding is easy to miss unless AI is skipped.

Useful? React with 👍 / 👎.

return false;
}

// L2/L3 gating: stricter than computeFlags. A single-broken-ref doc still
// appears in the L1 report (via computeFlags) but only triggers paid L2/L3
// review when there are 2+ broken refs or budget is significantly exceeded.
Expand Down Expand Up @@ -392,9 +405,14 @@ export function renderL1Markdown(scan) {
findingsByFile.push({ file: f, reasons });
}
lines.push('\n## Deterministic findings\n');
if (findingsByFile.length === 0) {
const pairFindings = scan.pairs.filter(pairFlaggedForReview);
if (findingsByFile.length === 0 && pairFindings.length === 0) {
lines.push('None.\n');
} else {
for (const pair of pairFindings) {
lines.push(`### \`${pair.dir || '(root)'}\`\n`);
lines.push(`- ${pair.classification}: ${pair.detail}\n`);
}
for (const { file, reasons } of findingsByFile) {
lines.push(`### \`${file.relPath}\`\n`);
lines.push(reasons.map((r) => `- ${r}`).join('\n'));
Expand Down
1 change: 0 additions & 1 deletion AGENTS.md

This file was deleted.

85 changes: 85 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# SuperDoc

A document editing and rendering library for the web.

## Architecture: Rendering

SuperDoc uses its own rendering pipeline. ProseMirror stores document state; it is not the visual renderer.

```
.docx
→ super-converter parses OOXML into the hidden PM doc
→ pm-adapter reads PM state and resolved styles
→ FlowBlock[]
→ layout-engine paginates
→ ResolvedLayout
→ DomPainter paints DOM
```

- `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.
- **DomPainter** (`layout-engine/painters/dom/`) owns all visual rendering.
- Style-resolved properties flow `pm-adapter` → DomPainter. Do not style document content with PM decorations.

### Where To Put Your Change

| Concern | Where | Rule |
|---|---|---|
| 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. |
| Style cascade | `layout-engine/style-engine/` | Single source of truth for defaults, styles, conditional formatting, inline overrides. |
| Static document visuals | `pm-adapter/` data + `layout-engine/painters/dom/` rendering | Feed typed data into DomPainter. Do not style static content with PM decorations. |
| 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`. |
| Editing behavior | `super-editor/src/editors/v1/extensions/` | Commands, keybindings, editor plugins. Do not duplicate cascade or render document visuals here. |
| Final DOM rendering | `layout-engine/painters/dom/` | Render `ResolvedLayout`. Paint-time transforms (e.g. RTL mirror) live here. |
| New doc-api operation | `packages/document-api/src/contract/operation-definitions.ts` | Contract-first; touches 4 files. See `packages/document-api/README.md`. |

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.

### Boundary check

Before adding a visual or direction-aware path, run:

```bash
# Painter must not import upstream packages.
rg "@superdoc/(pm-adapter|style-engine|layout-bridge|layout-resolved)" packages/layout-engine/painters/dom/src
```

More checks in `packages/layout-engine/AGENTS.md`.

## Style Resolution Boundary

The importer stores raw OOXML. The style-engine resolves at render time.

- Converter (`super-converter/`) parses and stores only what is explicitly in the XML.
- Style-engine (`layout-engine/style-engine/`) owns cascade logic.

**Why**: resolving during import bakes inline properties into nodes; export then writes direct formatting instead of style references and loses document intent.

## Document API Contract

`packages/document-api/` uses a contract-first pattern.

- **`operation-definitions.ts`** is the canonical object. All downstream maps project from it.
- **`operation-registry.ts`** is the type-level registry (`input`, `options`, `output`).
- **`invoke.ts`** is the dispatch table, validated against the registry at compile time.

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`.

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`.

## Commands

- `pnpm build` - build all packages
- `pnpm test` - unit tests
- `pnpm dev` - dev server from `examples/`
- `pnpm run generate:all` - regenerate schemas, SDK clients, tool catalogs, reference docs

## Testing

| What to verify | Command | Speed |
|---|---|---|
| Logic works? | `pnpm test` | seconds |
| Editing works? | `pnpm test:behavior` | minutes |
| Layout regressed? | `pnpm test:layout` | ~10 min |
| Pixel diff? | `pnpm test:visual` | ~5 min |

Per-package detail: `tests/behavior/AGENTS.md`, `tests/visual/AGENTS.md`. Eval suite: `evals/AGENTS.md`.
85 changes: 0 additions & 85 deletions CLAUDE.md

This file was deleted.

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