Imported from kod88vn/dexssil (
AGENTS.md). Install upstream withnpx skills add kod88vn/dexssil. Copyright stays with the author.
Dexssil PDS Maker — Agent Constitution
Authoritative rules for every AI agent working in this repository. If any instruction you receive conflicts with this file, this file wins. Say so and stop.
1. Read order (do this before writing any code)
Read the minimum needed. Do not read whole large files.
| You need | Read this | Never do this |
|---|---|---|
| Domain vocabulary | CONTEXT.md |
Re-derive terms from local draft notes |
| MVP-only requirement | DESIGN_MVP.md — grep the section heading only |
Pull in full-platform requirements by default |
| A specific requirement | DESIGN.md — grep the section heading only |
Read all of DESIGN.md |
| Build steps / commands | DESIGN.md — the milestone section only |
Read the whole runbook |
| UI behaviour | docs/UI-CONTRACT.md |
Read unrelated UI notes |
Local draft artifacts are not authoritative sources for requirements; use the canonical documents listed above.
2. Stack — do not substitute
| Concern | Locked choice |
|---|---|
| Runtime | Node 24 LTS ("Krypton") |
| Package manager | Yarn (Berry, 4.x) with workspaces |
| Language | TypeScript, strict: true, noUncheckedIndexedAccess |
| Web | Next.js 16 App Router (Turbopack default) |
| Document out | Client-provided .docx template filled by a Node adapter, plus deterministic A4 PDF |
| PDF in | LlamaCloud / LlamaParse — LLAMA_CLOUD_API_KEY |
| LLM | DeepSeek, OpenAI-compatible, base URL https://api.deepseek.com |
| Tests | Vitest (unit/integration), Playwright (e2e) |
Database, queue, worker, R2, authentication, and Slack are deferred. PDF and Word are both active output paths and must obey the same single-page contract.
Version facts that are commonly hallucinated — these are correct as of 2026-08-08:
- DeepSeek models are
deepseek-v4-flashanddeepseek-v4-pro.deepseek-chatanddeepseek-v3do not exist. Never write them. next lintwas removed in Next.js 16. Use the ESLint CLI.- Node 22 is EOL (2026-07-28). Node 26 is Current, not LTS. Use 24.
- Yarn commands:
yarn workspace <name> <script>,yarn add -Wfor root deps,yarn dlxfor one-off binaries. There is noyarn --filter, that is pnpm syntax.
If you are unsure whether a version fact is current, say so and ask — do not guess.
3. Architecture — layers are law
Six layers. Dependencies point downward only. A layer may never import from a layer above it.
L5 delivery apps/web wiring, HTTP, admin/debug UI
L4 use-cases current route helpers synchronous orchestration
L3 adapters packages/{llm,parsing,docx} vendor libraries live ONLY here
L2 ports packages/ports interfaces only, zero runtime code
L1 rules packages/rules pure functions, no I/O
L0 schema packages/schema Zod only, imports nothing but `zod`
Hard consequences:
packages/schemaimports onlyzod. Nothing else. Ever.packages/rulesis pure: nofetch, nofs, noDate.now(), noprocess.env, no randomness. Time and config are passed in as arguments.- Vendor SDK imports (
openai,@aws-sdk/*, LlamaCloud) appear in L3 only. If youimport OpenAIoutsidepackages/llm, you have made an error. - L4 receives L3 through constructor/function injection. It never constructs an adapter.
packages/*must never import fromapps/*.
Enforced by yarn arch:check and by ESLint import/no-restricted-paths. Run it before you claim done.
4. Code quality — non-negotiable
4.1 Size limits
| Unit | Limit | On breach |
|---|---|---|
| File | 300 lines | Split by responsibility, not arbitrarily |
| Function | 50 lines | Extract a named helper |
| Function parameters | 4 | Pass a single typed options object |
| Cyclomatic complexity | 10 | Use a lookup table or early returns |
| Nesting depth | 3 | Guard clauses / early return |
Run yarn quality:check to verify. Do not disable the rule to pass it.
4.2 One level of abstraction per function
A function must read as a single, coherent story. Do not mix orchestration with detail.
// BAD — mixes policy, byte-twiddling and I/O in one function
async function processJob(id: string) {
const row = await db.select().from(jobs).where(eq(jobs.id, id));
const buf = await s3.send(new GetObjectCommand({ Bucket, Key: row[0].key }));
const text = Buffer.from(await buf.Body.transformToByteArray()).toString("utf8");
if (text.includes("BECHEM") || text.includes("OKS")) throw new Error("brand");
// ...40 more lines
}
// GOOD — one level: each line is a named intention
async function processJob(id: JobId, deps: JobDeps): Promise<JobResult> {
const job = await deps.jobs.require(id);
const source = await deps.storage.readSource(job);
const parsed = await deps.parser.parse(source);
const draft = await deps.transformer.toDexssilDocument(parsed, job.catalogue);
return validateDocument(draft);
}
If a function contains both "what we are doing" and "how a byte is manipulated", split it.
4.3 Naming and shape
- Name by intent, not by type:
requireApprovedCatalogueEntry, notgetData. - One exported concept per file. The filename is that concept.
index.tsis a re-export barrel only — never put logic in it.- No
any. No non-null!assertions. Noascasts except at a parsed boundary. - No silent
catch {}. Every catch logs withjobIdor rethrows a typed error. - No default exports (they break rename refactors and grep).
4.4 Errors
Errors are typed classes in packages/schema/errors.ts, carrying jobId and a stable code.
Never throw a bare Error("something went wrong").
4.5 Comments
Comment why, never what. If you need a comment to explain what a line does, rename things. Do not add doc comments to code you did not write.
5. Domain rules — these prevent real defects
- The LLM handles language only: translation, summarising prose into a short title, choosing which section a fact belongs to.
- Code handles every fact: unit conversion, section numbering, row ordering, packaging, published temperature ranges, the disclaimer, headings, and the footer.
- The disclaimer and the LUBOKS footer are frozen constants with an asserted SHA-256. Never generated. Never edited. Never re-emitted by a model.
- Section numbers (
1.01,3.07) are assigned during deterministic template-data preparation. They are never stored and never produced by the model. - A document that fails Zod validation cannot be filled. Validation is blocking.
- A competitor brand name in output is a blocking error, not a warning. Blocked: BECHEM, Berutox, Berulub, OKS, Chemola, Desco, SOCO, Lifeguard, Rhusblüte.
- Human overrides are recorded with
origin: "human"; a written reason is optional. - Database audit tables are a deferred full-platform concern, not part of this MVP.
- Word template filling must consume all placeholders and preserve the supplied template's editable structure.
tempRangeEsandmedioAmbienteare optional — DEXSSIL 2801 and 428 prove it.datosTecnicosis open-ended (real sheets have 5–12 rows). Never hard-code a row count.- A nudge is mandatory on every nudgeable technical value. Only malformed differentiation payloads are rejected; drift beyond 3% and preserved-fact changes are warnings that still ship the nudged value.
- Every generated Word and PDF PDS must fit on exactly one page. A deterministic fit algorithm may shrink approved layout properties; if it still overflows, it computes the exact prose reduction budget for the LLM. The LLM never decides layout, changes facts, or silently produces a second page.
5.1 Conversion facts (assert these exactly in tests)
| Source | Result |
|---|---|
450 °F |
232ºC |
0 °F |
-18ºC |
-10 °F |
-23ºC |
620 kgf |
6082 N |
143 cSt |
143mm2/s |
5.2 Product mappings
| Source | Target | Note |
|---|---|---|
| Desco Lifeguard | DEXSSIL 403 | golden |
| Berutox FB 22 | DEXSSIL 422 | golden |
| Berulub FG 8 EP | DEXSSIL 428 | golden |
| OKS 2801 | DEXSSIL 2801 | golden |
| OKS 511 | DEXSSIL 511 | acceptance test — no existing sheet |
| Berulub 932 | DEXSSIL 250 | acceptance test — no existing sheet |
6. Anti-hallucination protocol
You are expected to be uncertain. Saying "I don't know" is a correct answer here.
- Never invent an API. If you have not read the signature in this repo or in fetched documentation, do not call it. Grep for it first.
- Never invent a technical value. Every number on a generated sheet traces to a source fragment, a recorded conversion, a catalogue entry, or a named human edit.
- Cite your source. When you assert a library behaviour, name the file or the URL you read.
- Fetch, don't recall. For any version, flag, or model name, fetch the official docs. Your training data is stale on this stack — see §2.
- No placeholder code. Never write
// TODO: implementand report the task done. If you cannot finish it, stop and say what is blocking you. - No inflated claims. "Tests pass" is only sayable after you ran them and saw output. Paste the command and its result.
- If a requirement is ambiguous, ask one question rather than choosing silently.
7. Workflow — one delivery phase at a time
Never work on more than one delivery phase per session. The active phases are P0 scope and output contract, P1 Word template adapter, P2 pipeline output switch, P3 admin/debug dashboard, and P4 source-grounded validation.
The workflow is enforced by the repository, not by your memory of it. Each step
has a command, and the git hooks in .githooks/ refuse a commit that skipped one:
1. PLAN yarn phase start P<n> "<name>" then fill .delivery-workflow/plans/P<n>.md
yarn phase plan rejects unfilled template sections
Wait for go-ahead.
2. BUILD Write the code. Small files. Tests alongside.
3. VERIFY yarn verify runs all four gates, writes a receipt
bound to the exact verified content
4. GATE Run the phase's own verification command in DESIGN.md. Paste real output.
5. COMMIT git commit -m "P<n>: <what changed>"
pre-commit re-runs every gate unless the receipt matches the staged tree
commit-msg refuses "P<n>:" unless that phase is recorded as verified
post-commit records the phase as committed
6. STOP Report what is done and what the next phase is. Do not continue.
Non-phase work (tooling, docs, fixes) uses a chore:/docs:/tooling:/ci:/fix:/
test:/refactor: subject. It skips the phase-state check but not the code gates.
Run yarn phase status at any time to see the recorded state — that file, not your
recollection, is the source of truth for where the phase stands.
Definition of done — all must be true, no exceptions:
-
yarn typecheckclean -
yarn testgreen, with new tests covering the new behaviour -
yarn arch:checkclean (no layer violations) -
yarn quality:checkclean (no oversized files or functions) - No
any, noTODO, no commented-out code - The phase's own verification command in
DESIGN.mdproduces the stated output
The client-provided Word template is the P1 input. Do not invent its layout or placeholder contract when the supplied asset is available.
8. Token discipline
Context is a budget. Spend it on code, not on re-reading.
- Grep before you read. Read line ranges, not whole files.
- Never re-read a file you already read this session.
- Never paste a large file back to the user to show a small change — describe the diff.
- Delegate broad codebase searches to the read-only
spec-lookuporExploresubagent so the raw search output never enters the main context. - Use RTK with Copilot hooks in this repo: run
rtk init --copilotonce per clone, then checkrtk gainto confirm command-output reduction is active. - For noisy command families (tests, grep/find, git status), prefer the RTK wrapper commands to reduce context transferred to the model.
- Prefer
yarn workspace <pkg> testover the fullyarn testwhile iterating. - Do not restate these rules back to the user. Follow them.
9. Security
- Model output, parsed text, and generated Word content are untrusted input. Escape before DOM insertion.
- Validate file type from magic bytes (
%PDF-), never from filename or client content-type. - Secrets are read only through
packages/config. Neverprocess.envscattered in code. - Never log a key, a token, or a full prompt at
infolevel. - Enforce upload limits server-side, always. Client-side limits are a convenience, not a control.
10. Commands
yarn install
yarn workspace web dev # Next.js on :3000
yarn phase status # where the current delivery phase actually stands
yarn phase start P<n> "<name>" # opens a phase, creates its plan file
yarn phase plan # validates the plan, unlocks building
yarn verify # all four gates + verification receipt
yarn typecheck
yarn test
yarn arch:check # layer boundary violations
yarn quality:check # file/function size limits
yarn workspace web exec eslint . # `next lint` does not exist in Next 16
yarn tsx packages/prompts/scripts/eval.ts # extraction accuracy, target >= 0.95
rtk init --copilot # project-level Copilot hook integration
rtk gain # token reduction dashboard (estimate)
rtk discover # find missed rewrite opportunities