algocline 0.35.0

LLM amplification engine — MCP server with Lua scripting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# Package author conventions

This document codifies the conventions algocline expects from package
authors. It is the single source of truth for the docstring convention,
the `M.meta` / `M.spec` / `M.docs` shape contract, and the lint rules
enforced by `alc_hub_gendoc`.

---

## 0. SSoT / Projection — Source vs generated artifacts

algocline pkg conventions enforce a strict separation between
**Source** (hand-written) and **Projection** (machine-generated):

- **Source (SSoT)**: `init.lua` — specifically `M.meta`, `M.spec`,
  and the leading `---` docstring block. This is the only file an
  author edits.
- **Projection**: every other artifact about the pkg, regenerated
  by `alc_hub_dist` from the Source.

Recognised projections (current set; the authoritative list lives in
`crates/algocline-app/src/service/lua/gendoc/docs/projections.lua`):

| Projection target            | `alc_hub_dist` projections key | Purpose                                  |
|------------------------------|--------------------------------|------------------------------------------|
| `docs/narrative/{name}.md`   | `narrative`                    | Rendered docstring + Parameters table    |
| `llms.txt` / `llms-full.txt` | `llms`                         | LLM-readable pkg catalog                 |
| `context7.json`              | `context7`                     | Context7 MCP integration                 |
| `.devin/wiki.json`           | `devin`                        | Devin AI wiki ingestion                  |
| `types/alc_pkgs.d.lua`       | `luacats`                      | LuaCATS type stubs for IDE               |

**Hand-writing or hand-editing any projection is forbidden.** A
discrepancy between Source and Projection always means either
(a) a stale projection that needs regeneration, or (b) a Source
write disguised as a projection edit. The lint pipeline assumes
Source is canonical.

The legacy `M.docs.narrative` field and standalone `narrative.md`
files were transitional violations of this principle — projections
that authors edited by hand. They are now fully generated.

---

## 1. Publish patterns and lint scope

Pkg authors fall into one of three publish patterns. Each pattern has
a different lint posture; the same lint codes (§5) are evaluated, but
the severity boundary that fails a build differs.

| Pattern       | Distribution                                                        | Lint mode                | Author expectation                                                                                |
|---------------|---------------------------------------------------------------------|--------------------------|---------------------------------------------------------------------------------------------------|
| **Bundled**   | Shipped via `algocline-bundled-packages` (the official curated set) | `lint_strict=true`       | Required + Recommended fields all populated. Every warning is treated as an error.                |
| **Community** | Personal repos, gists, hand-installed packages                      | `lint_strict=false`      | Required fields populated. Recommended fields strongly encouraged but warnings are non-blocking.  |
| **Private**   | Local-only experiments, never published                             | Lint not expected to run | No baseline. Authors may run lint at their discretion.                                            |

`lint_strict=true` (Bundled / CI gate) is the only mode that fails
the build on warnings. `lint_strict=false` (default) allows warnings
to surface in tooling output without blocking the build.

After lint passes, `alc_hub_dist` (the **DIST** stage) regenerates
every projection listed in §0.

The Required vs Recommended boundary is defined in §2 (`M.meta`
fields) and §3 (docstring shape). All conventions in this document
apply to all three patterns; only the lint posture differs.

---

## 2. Top-level pkg shape

Every algocline pkg is a Lua module that returns a table `M`. The canonical layout (taken from `cot/init.lua` and trimmed):

```lua
local S = require("alc_shapes")
local T = S.T

local M = {}

---@type AlcMeta
M.meta = {
    name        = "cot",
    version     = "0.1.0",
    description = "Iterative chain-of-thought — cumulative reasoning steps, then synthesis",
    category    = "reasoning",
}

---@type AlcSpec
M.spec = {
    entries = {
        run = {
            input = T.shape({
                task  = T.string:describe("The question or task to reason about"),
                depth = T.number:is_optional():describe("Number of reasoning steps (default: 3)"),
            }),
            result = T.shape({
                chain      = T.array_of(T.string):describe("Ordered insights, one per reasoning step"),
                conclusion = T.string:describe("Synthesized final answer"),
            }),
        },
    },
}

-- M.docs is optional; set schema_version for future compat if needed.
-- Do NOT set M.docs.narrative — that field has been removed.
M.docs = {
    schema_version = 1,
}

function M.run(ctx)
    -- ...
end

return M
```

Each field below is tagged with one of three status labels. The same labels are used throughout this document.

| Status          | Meaning                                                                                                                                                       |
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Required**    | Must be present. Missing it is a lint error in every publish pattern (§1).                                                                                    |
| **Recommended** | Not required, but strongly encouraged. **Bundled** pkgs are expected to populate it; missing it is a warning by default and an error under `lint_strict=true`. |
| **Optional**    | May be omitted. No lint emission.                                                                                                                             |

### 2.1 `M.meta` — identity

| Field         | Status       | Type   | Notes                                                          |
|---------------|--------------|--------|----------------------------------------------------------------|
| `name`        | **Required** | string | Must match the pkg directory name (lint: `E_NAME_MISMATCH`).   |
| `version`     | **Required** | string | SemVer.                                                        |
| `description` | **Required** | string | One-line tagline (≤ 80 char). Projected into `llms.txt` entry. |
| `category`    | **Required** | string | Grouping key for `llms.txt` and hub search.                    |

(Lint codes: `E_META_MISSING_{NAME,VERSION,DESCRIPTION,CATEGORY}` — see §5.)

### 2.2 `M.spec` — runtime contract

`M.spec.entries.{entry}.{input, result}` declares each entry's
runtime contract using the `alc_shapes` T DSL. This is the
**SSoT for `## Parameters` projections** (§3.4) and for
`alc.run(<pkg>, ctx)` runtime validation.

| Field                                | Status          | Type                            | Notes                                                                                                                              |
|--------------------------------------|-----------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `M.spec.entries.{entry}.input`       | **Recommended** | `T.shape(...)`                  | Each field MUST carry `:describe("...")`. The `:describe` text becomes the `description` column of the `## Parameters` projection. |
| `M.spec.entries.{entry}.result`      | **Recommended** | `T.shape(...)` or `T.ref(name)` | Consumed by `alc_hub_dist projections=["luacats"]` for IDE type stubs.                                                             |

A pkg without `M.spec` is treated as **opaque**: downstream
generators emit "no declared shape" sections. This is acceptable
for **Community** and **Private** patterns but is rejected by
`lint_strict=true` for **Bundled** pkgs.

> **Note on naming**: the Lua-source location is
> `M.spec.entries.{entry}.input`. The name `input_shape` appears
> in `alc_hub_dist`'s JSON projection output (the field key in the
> generated JSON), but is **not** the source location. Do not
> write `M.meta.input_shape`; that phrasing is stale and the
> linter does not recognise it.

(Lint codes for V1: `E_META_MISSING_INPUT_SHAPE` / `E_PARAM_MISSING_DESCRIBE` — see §5.)

### 2.3 `M.docs` — optional container

`M.docs` is preserved as a container for future schema markers.
The only recognised field is `schema_version`. **`M.docs.narrative`
is removed** — do not set it. The linter will warn on unknown
`M.docs` fields in a future release.

---

## 3. Docstring-driven narrative

`init.lua` is the single source of truth for narrative content. The
gendoc pipeline (`extract.lua split_sections` + `projections.lua
narrative_md`) renders the docstring H2 sections into Markdown on
demand. The output is the `narrative` projection (§0).

**Removed conventions** (do not use):

- `M.docs.narrative = "narrative.md"` — field is gone
- Separate `narrative.md` files — not recognised; will be ignored

### 3.1 How the resource is served

When a client requests `alc://packages/{name}/narrative`:

1. The engine locates the pkg's `init.lua` (variant scope first, then global scope).
2. A fresh mlua VM runs `extract.build_pkg_info` + `projections.narrative_md`.
3. The rendered Markdown is returned as `text/markdown`.

No cache — every request re-renders from the current `init.lua`.

### 3.2 Recommended H2 sections

The H2 sections below map onto the four Diátaxis documentation
classes. Write only the sections relevant to the pkg — a simple
wrapper may need only `## Usage`; a paper implementation typically
needs `## Algorithm` + `## Theoretical foundations` + `## References`.

| Section                               | Diátaxis class | When to write                                                                                                                            |
|---------------------------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `## Usage`                            | How-to         | Minimum working example. Write for almost every pkg.                                                                                     |
| `## When to use`                      | How-to         | When the pkg overlaps with siblings and the choice is non-obvious.                                                                       |
| `## Algorithm`                        | Explanation    | When the algorithm has 3+ named steps or a non-trivial invariant.                                                                        |
| `## Theoretical foundations`          | Explanation    | When correctness follows from a theorem or paper. State the theorem.                                                                     |
| `## Entry contract`                   | Reference      | When the pkg exposes multiple named entries.                                                                                             |
| `## Caveats`                          | Reference      | Pkg-wide rationale that does not fit a single parameter `:describe()` — known limitations, why certain knobs are hidden, edge cases.     |
| `## Empirical validation`             | Reference      | Bench data, sweep results, coverage observations.                                                                                        |
| `## Comparison with related packages` | Reference      | When a sibling pkg does something similar. One bullet per peer.                                                                          |
| `## References`                       | Reference      | Papers, arXiv IDs, books. Bullet list — see §4.4.                                                                                        |

`## Parameters` is **not** in the list above — it is generated from
`M.spec.entries.{entry}.input` (§2.2 / §4.3) and must not be
hand-written.

### 3.3 Canonical example

The reference docstring below demonstrates the required elements:

```lua
--- conformal_vote — split conformal prediction gate for multi-agent deliberation
---
--- Linear opinion pool + split conformal prediction post-hoc decision layer.
--- Emits a three-way decision (commit / escalate / anomaly) with a finite-sample
--- coverage guarantee `Pr[Y ∈ C(X)] ≥ 1-α` (Theorem 2). Calibration and online
--- rounds share aggregation weights so exchangeability is preserved.
---
--- ## Algorithm
---
--- Given N agents that each emit a verbalized probability distribution
--- π_i(y|x) over a fixed option set, the pkg performs:
---
--- ```math
--- P_social(y|x) = Σ_i w_i · π_i(y|x)        (linear opinion pool)
--- s_nc(x, y)    = 1 - P_social(y|x)          (nonconformity score)
--- q̂            = sorted[⌈(n+1)(1-α)⌉]        (finite-sample quantile, §4.3)
--- C(x)          = { y : P_social(y|x) ≥ 1 - q̂ }   (prediction set)
--- ```
---
--- ## Theoretical foundations
---
--- Theorem 2 guarantees `Pr[Y ∈ C(X)] ≥ 1-α` in finite samples whenever
--- calibration and online rounds share the same aggregation weights and
--- the data is exchangeable.
---
--- ## Entry contract
---
--- - `calibrate`   — pure, direct-args. returns `{ q_hat, tau, alpha, n, weights }`
--- - `aggregate`   — pure, direct-args. returns `{ [label] = p_social }`
--- - `predict_set` — pure, direct-args. returns `{ labels, top1, top1_prob, ... }`
--- - `decide`      — pure, direct-args. returns `{ action, selected }`
--- - `run`         — Strategy, ctx-threading. queries N agents via `alc.llm`
---
--- ## Comparison with related packages
---
--- Category: `validation` (alongside `sprt`, `eval_guard`, `inverse_u`).
---
--- ## References
---
--- Wang, Xie, Wang, Gao, Yang, Li, Qiu, Han, Qiu, Huang, Zhu, Woo (2026).
--- "From Debate to Decision: Conformal Social Choice for Safe Multi-Agent
--- Deliberation". arXiv:2604.07667.
```

---

## 4. Docstring style

### 4.1 1-line summary and abstract

Every docstring opens with a 1-line summary, followed by a 1–3 sentence abstract:

```lua
--- {PkgName} — {verb phrase}
---
--- {abstract: 1-3 sentences explaining the core capability}
---
--- ## {first H2 section}
```

**1-line summary** (line 1):

- One clause, ≤ 80 characters, of the form
  `{PkgName}({StyledName}) — {verb phrase}` when the pkg has a stylized
  name (typically a paper-cited abbreviation such as `CoT`, `UCB`,
  `MCTS`), or `{PkgName} — {verb phrase}` when the pkg directory name
  is already the canonical form
- `PkgName` is always the pkg directory name (lowercase, matches
  `M.meta.name`). `StyledName` is the conventional reading-aid form
  used in the literature; omit it when no such form exists.
- `` is the em dash (UTF-8 `U+2014`)
- Becomes the H1 title in the rendered narrative and the entry in `llms.txt`
- `M.meta.description` (§2.1) carries the same (or slightly expanded) wording for JSON consumers

Good:

```lua
--- conformal_vote — split conformal prediction gate for multi-agent deliberation
--- cot(CoT) — iterative chain-of-thought reasoning
--- ucb(UCB) — upper confidence bound multi-armed bandit
```

Too vague:

```lua
--- conformal_vote — a useful voting package
```

**Abstract** (lines after the blank `---` separator, until the first `## ` heading):

- 1 to 3 sentences
- Plain prose only — no headings, no lists, no code fences
- Explains the core capability at a glance; rationale and algorithm details belong in the H2 sections (§3.2)

If the pkg is trivial enough that no H2 sections follow, the abstract may stand alone — the rendered narrative will then consist of H1 + abstract only.

### 4.2 Markdown syntax

Within the docstring body, follow these Markdown rules. Concrete examples follow each rule.

**Headings**:

- `#` (H1) — **forbidden**. The generator synthesises H1 from the 1-line summary (§4.1).
- `##` is the highest permitted level
- `###` is allowed as a subsection within a `##` block
- `####` and lower — forbidden
- Each heading must be followed by a blank `---` line before body text:

```lua
--- ## Algorithm
---
--- 1. step
```

**Code fences**:

- Use **explicit** triple backticks (`` ``` ``). 4-space-indent fences are forbidden
- Language hint is recommended (`` ```lua `` for Lua, `` ```math `` for GitHub MathJax-rendered equations)
- One snippet per fence. Multiple snippets must use independent fences
- Inline code uses single backticks (`` ` ``)

```lua
--- ## Usage
---
--- ```lua
--- local pkg = require("pkg")
--- return pkg.run(ctx)
--- ```
```

**Lists**:

- Bullet: `-` (hyphen) only. `*` and `+` are forbidden
- Numbered: `1.` / `2.` / `3.` style
- Letter-numbering (`a.` / `b.`) is renderer-dependent and forbidden
- Indentation: 2 spaces for sub-bullets under `-`, 3 spaces for sub-bullets under numbered lists
- Maximum nesting depth: 2 levels. 3+ levels of nesting are forbidden — renderer-dependent

**Links**:

- GitHub-style markdown links only: `[text](url)`
- Bare URLs are forbidden in body text; they are permitted **only** inside `## References` citations (§4.4)

```lua
--- See [the README](../README.md) for derivation.
```

**Encoding**:

- UTF-8 only
- em dash `` (`U+2014`) and en dash `` (`U+2013`) are allowed
- All docstrings are written in English. The rendered narrative and `llms.txt` are public artifacts; non-English docstrings break readability for downstream consumers.

### 4.3 Parameters — generated from `M.spec`

The `## Parameters` section in the rendered narrative is **machine-generated** from `M.spec.entries.{entry}.input` (§2.2). Authors do not write `## Parameters` in the docstring.

The generator emits one row per shape field, using:

- field name as `key`
- shape type as `type`
- `:is_optional()` as `required` (true / false)
- `:describe("...")` text as `description`

**`:describe()` requirements**:

Every shape field MUST carry `:describe("...")`. The describe text is the only source for the `description` column.

```lua
M.spec = {
    entries = {
        run = {
            input = T.shape({
                task  = T.string:describe("The question or task to reason about"),
                depth = T.number:is_optional():describe("Number of reasoning steps (default: 3)"),
            }),
        },
    },
}
```

In **Bundled** mode (`lint_strict=true`), missing `:describe()` is a hard error. In **Community** mode, missing `:describe()` is a warning (lint code `E_PARAM_MISSING_DESCRIBE` — see §5).

**Where to put the rationale** (two-tier rule):

| Scope of rationale                                                                                              | Where to write                                                                                                                              |
|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| Per-parameter semantic rationale (e.g. *why* a numeric default was chosen, when truncation breaks invariants)   | Inside the parameter's `:describe("...")` text. The text may be long; the projection emits it verbatim into the `## Parameters` table.      |
| Pkg-wide rationale (e.g. *why* a token budget knob is hidden, why certain options are intentionally not exposed) | `## Caveats` H2 section (§3.2)                                                                                                              |

This separation ensures (a) parameter-specific rationale survives every projection (`## Parameters` table, `context7.json`, `llms-full.txt`, LuaCATS stubs), and (b) pkg-wide design rationale lives where readers look for caveats.

**Hand-written `## Parameters` is forbidden**:

Writing `## Parameters` in the docstring while `M.spec.entries.{entry}.input` is also declared raises lint error `E_PARAMETERS_CONFLICT`. The two are mutually exclusive: `M.spec` is the SSoT.

For pkgs without `M.spec` (opaque pkgs — Community / Private), the generator emits no `## Parameters` section. Authors do not need to write one.

### 4.4 References

The `## References` H2 section uses a flat bullet list. Do not wrap citations in code fences.

- Each citation is a single bullet starting with `-`
- Continuation lines indent by 2 spaces
- Bare URLs are permitted **only** inside this section (e.g. arXiv links, paper DOIs)

```lua
--- ## References
---
--- - Friedman, M. (1937). "The use of ranks to avoid the assumption of
---   normality ...," J. Am. Stat. Assoc. 32(200): 675–701.
--- - Wang, X. et al. (2026). "From Debate to Decision: Conformal Social
---   Choice for Safe Multi-Agent Deliberation". arXiv:2604.07667.
```

Inline citations in body text reference the bullet by surname or arXiv ID — no inline URL.

### 4.5 LuaCATS annotations

LuaCATS annotations (`---@type`, `---@param`, `---@return`, etc.) appear **after** the narrative docstring body. The generator stops narrative extraction at the first `---@` line.

```lua
--- cot(CoT) — iterative chain-of-thought reasoning
---
--- Builds a reasoning chain step by step, then synthesizes the chain
--- into a single coherent conclusion.
---
--- ## Usage
---
--- ```lua
--- local cot = require("cot")
--- return cot.run({ task = "Why is the sky blue?", depth = 3 })
--- ```

local S = require("alc_shapes")
local T = S.T

local M = {}

---@type AlcMeta
M.meta = {
    name        = "cot",
    version     = "0.1.0",
    description = "Iterative chain-of-thought — cumulative reasoning steps, then synthesis",
    category    = "reasoning",
}

---@type AlcSpec
M.spec = {
    entries = {
        run = {
            input = T.shape({
                task  = T.string:describe("The question or task to reason about"),
                depth = T.number:is_optional():describe("Number of reasoning steps (default: 3)"),
            }),
            result = T.shape({
                chain      = T.array_of(T.string):describe("Ordered insights, one per reasoning step"),
                conclusion = T.string:describe("Synthesized final answer"),
            }),
        },
    },
}

---@param ctx AlcCtx
---@return AlcCtx
function M.run(ctx) ... end

return M
```

Place them last so the narrative reads as a single coherent block when stripped of annotations. LuaCATS is consumed by `alc_hub_dist projections=["luacats"]` to generate IDE type stubs (§0).

### 4.6 Disallowed constructs (collected)

The constructs below are forbidden in any docstring. Each is also covered in the relevant subsection above; this table consolidates them with the corresponding lint code (§5).

| NG                                            | Reason                                                            | Lint code                  |
|-----------------------------------------------|-------------------------------------------------------------------|----------------------------|
| `#` (H1) heading                              | Generator synthesises H1 from the 1-line summary (§4.1)           | `E_H1_IN_DOCSTRING`        |
| `$...$` / `$$...$$` inline LaTeX              | Renders only on GitHub; breaks elsewhere. Use `` ```math `` fence | (no lint, manual review)   |
| 4-space-indent code fence                     | Use explicit `` ``` `` only                                       | (no lint, manual review)   |
| HTML tag (`<br>`, `<sub>`, etc.)              | Markdown only                                                     | (no lint, manual review)   |
| Emoji                                         | Breaks llms.txt and CRAN-style consumers                          | (no lint, manual review)   |
| 3+ levels of list nesting                     | Renderer-dependent                                                | (no lint, manual review)   |
| Hand-written `## Parameters` heading          | `M.spec.entries.{entry}.input` is the SSoT (§4.3)                 | `E_PARAMETERS_CONFLICT`    |

---

## 5. Lint rules

`alc_hub_gendoc` runs the lint pipeline and emits the codes below. `lint_strict=true` (Bundled / CI gate, §1) treats every `error` severity as a build failure; `lint_strict=false` (default) surfaces them as non-blocking diagnostics.

| Code                         | Severity        | Status       | Description                                                                                |
|------------------------------|-----------------|--------------|--------------------------------------------------------------------------------------------|
| `E_META_MISSING_NAME`        | error           | active       | `M.meta.name` missing                                                                      |
| `E_META_MISSING_VERSION`     | error           | active       | `M.meta.version` missing                                                                   |
| `E_META_MISSING_DESCRIPTION` | error           | active       | `M.meta.description` missing                                                               |
| `E_META_MISSING_CATEGORY`    | error           | active       | `M.meta.category` missing                                                                  |
| `E_NAME_MISMATCH`            | error           | active       | `M.meta.name` ≠ pkg directory name                                                         |
| `E_H1_IN_DOCSTRING`          | error           | active       | `#` heading present in docstring                                                           |
| `E_PARAMETERS_CONFLICT`      | error           | active       | `M.spec.entries.{entry}.input` declared and hand-written `## Parameters` both present      |
| `E_RESULT_CONFLICT`          | error           | active       | `M.spec.entries.{entry}.result` declared and hand-written `## Result` both present         |
| `E_META_MISSING_INPUT_SHAPE` | warning / error | planned (V1) | `M.spec.entries.{entry}.input` missing. `warning` in default mode, `error` under `lint_strict=true`. |
| `E_PARAM_MISSING_DESCRIBE`   | warning / error | planned (V1) | Shape field without `:describe(...)`. `warning` in default mode, `error` under `lint_strict=true`.   |
| `W_DESCRIPTION_MULTILINE`    | warning         | active       | `M.meta.description` contains a newline                                                    |
| `W_FAKE_LABEL`               | warning         | active       | `Usage:` / `Args:` style label — promote to `## Usage` etc.                                |
| `W_EMPTY_NARRATIVE`          | warning         | active       | No abstract and no H2 sections                                                             |

**Severity convention** (per `lint.lua`):

- `error``lint_strict=true` rejects the pkg; `lint_strict=false` reports non-blocking
- `warning` — never blocks the build, surfaces in tooling diagnostics
- Planned rules with split severity emit `warning` in default mode and **promote to `error`** under `lint_strict=true`

Active rules are implemented in `crates/algocline-app/src/service/lua/gendoc/docs/lint.lua`. Planned rules are not yet implemented.

---

## 6. Migration

### 6.1 Migration from `M.docs.narrative` / `narrative.md`

If an existing pkg uses the old (pre-narrative-decommission) convention:

1. Move narrative content into the `---` docstring block as H2 sections.
2. Remove `M.docs.narrative` from the `M.docs` table (or drop `M.docs` entirely if `schema_version` is also absent).
3. Delete the standalone `narrative.md` file.
4. Run `alc_hub_dist` with `projections=["narrative"]` to regenerate `docs/narrative/{name}.md`.
5. Verify `alc://packages/{name}/narrative` returns the expected Markdown.

### 6.2 Migration to V1 conventions

For pkgs predating the V1 conventions in this document:

1. **Move parameters into `M.spec`**: if the pkg declared `M.meta.input_shape` (a stale phrasing) or had a hand-written `## Parameters` H2, move the shape definition into `M.spec.entries.{entry}.input` (§2.2). Delete the `## Parameters` H2.
2. **Add `:describe()` to every shape field**: each field in `input` and `result` must carry a `:describe("...")` clause. Fields without describe text become empty rows in the projected `## Parameters` table.
3. **Move pkg-wide rationale to `## Caveats`**: prose explaining *why* certain knobs are hidden, why a token budget is fixed, etc., goes into the `## Caveats` H2 (§3.2). Per-parameter rationale stays inside `:describe()` (§4.3).
4. **Run lint**: `alc_hub_gendoc lint_strict=false` first to see warnings, then `lint_strict=true` if the pkg targets the Bundled distribution (§1).
5. **Regenerate projections**: `alc_hub_dist` to refresh `docs/narrative/{name}.md` and downstream artifacts (§0).