miki-template 1.3.3 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.github/release-notes/v1.3.1.md +55 -55
  2. package/.github/release-notes/v1.3.3.md +77 -0
  3. package/.github/workflows/ci.yml +38 -54
  4. package/.github/workflows/release.yml +106 -0
  5. package/AGENT.md +71 -71
  6. package/API_REFERENCE.md +314 -314
  7. package/CHANGELOG.md +173 -169
  8. package/CODE_OF_CONDUCT.md +14 -14
  9. package/CONTRIBUTING.md +27 -27
  10. package/README.md +342 -321
  11. package/ROADMAP.md +40 -40
  12. package/benchmarks/report.json +16 -16
  13. package/benchmarks/run.js +49 -49
  14. package/benchmarks/stress.mjs +647 -647
  15. package/benchmarks/templates/large.dtpl +7 -7
  16. package/benchmarks/templates/medium.dtpl +3 -3
  17. package/benchmarks/templates/small.dtpl +7 -7
  18. package/context/component.md +109 -109
  19. package/context/prd.md +131 -131
  20. package/context/project-structure.md +33 -33
  21. package/dir/base.html +22 -22
  22. package/dir/cmpnt.html +10 -10
  23. package/dir/footer.html +2 -2
  24. package/dir/home.html +80 -80
  25. package/dir/index.html +80 -0
  26. package/dir/navbar.html +8 -8
  27. package/docs/README.md +18 -18
  28. package/docs/advanced_usage.md +71 -71
  29. package/docs/api.md +119 -119
  30. package/docs/filters.md +708 -708
  31. package/docs/installation.md +106 -106
  32. package/docs/overview.md +57 -57
  33. package/docs/partialdef.md +70 -70
  34. package/docs/security.md +27 -27
  35. package/docs/tags.md +673 -673
  36. package/docs/usage.md +646 -646
  37. package/eslint.config.mjs +42 -42
  38. package/ex.mjs +32 -32
  39. package/miki-template-extension/.github/workflows/ci.yml +116 -116
  40. package/miki-template-extension/.vscodeignore +7 -7
  41. package/miki-template-extension/CHANGELOG.md +99 -99
  42. package/miki-template-extension/LICENSE +21 -21
  43. package/miki-template-extension/README.md +273 -273
  44. package/miki-template-extension/extension.js +1013 -1013
  45. package/miki-template-extension/icon.svg +10 -10
  46. package/miki-template-extension/package.json +280 -280
  47. package/miki-template-extension/snippets/miki-template.json +717 -717
  48. package/miki-template-extension/syntaxes/language-configuration.json +114 -114
  49. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +355 -355
  50. package/miki-template-extension/tests/grammar-tests.json +162 -162
  51. package/miki-template-extension/tests/run-grammar-tests.js +82 -82
  52. package/package.json +37 -34
  53. package/sample-app/package-lock.json +901 -0
  54. package/sample-app/package.json +9 -0
  55. package/sample-app/server.js +14 -0
  56. package/sample-app/views/index.html +1 -0
  57. package/scripts/build-vsix.js +129 -129
  58. package/scripts/build-vsix.ps1 +15 -15
  59. package/snippets/miki-template.json +177 -177
  60. package/src/asyncRender.js +20 -20
  61. package/src/cache.js +80 -80
  62. package/src/context.js +126 -126
  63. package/src/context_processors.js +48 -48
  64. package/src/esm.mjs +84 -84
  65. package/src/filters.js +975 -975
  66. package/src/i18n.js +171 -171
  67. package/src/index.js +974 -940
  68. package/src/lexer.js +114 -114
  69. package/src/libraries.js +371 -371
  70. package/src/parser.js +270 -270
  71. package/src/security.js +53 -53
  72. package/src/tags/control.js +719 -719
  73. package/src/tags/extra.js +154 -154
  74. package/src/tags/helpers.js +26 -26
  75. package/src/tags/i18n.js +256 -256
  76. package/src/tags/inheritance.js +335 -335
  77. package/src/tags/registry.js +18 -18
  78. package/src/tags/util.js +400 -400
  79. package/src/types.d.ts +107 -107
  80. package/syntaxes/language-configuration.json +26 -26
  81. package/syntaxes/miki-template.tmLanguage.json +146 -146
  82. package/tests/asyncRender.test.js +17 -17
  83. package/tests/base.html +6 -6
  84. package/tests/child.html +3 -3
  85. package/tests/context_processors.test.js +13 -13
  86. package/tests/esm.test.mjs +61 -61
  87. package/tests/filters.test.js +254 -254
  88. package/tests/include_security.test.js +9 -9
  89. package/tests/integration/README.md +32 -32
  90. package/tests/integration/features.test.cjs +1681 -1681
  91. package/tests/integration/features.test.mjs +1697 -1697
  92. package/tests/integration/templates/base.miki +6 -6
  93. package/tests/integration/templates/child.miki +6 -6
  94. package/tests/integration/templates/index.html +17 -17
  95. package/tests/lexer.test.js +45 -45
  96. package/tests/parser.test.js +57 -57
  97. package/tests/partial.html +1 -1
  98. package/tests/partialdef.test.js +79 -79
  99. package/tests/production_checks.js +57 -57
  100. package/tests/security.test.js +28 -28
  101. package/tests/tags.test.js +233 -233
@@ -1,55 +1,55 @@
1
- # miki-template v1.3.1
2
-
3
- ## Highlights
4
-
5
- - **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires the view engine, the views directory, and a `res.render` shim that makes HTMX-style partial responses Just Work.
6
-
7
- ```js
8
- const express = require('express');
9
- const miki = require('miki-template');
10
-
11
- const app = express();
12
- miki.setupExpress(app, { extension: 'html', views: './views' });
13
-
14
- // Full page
15
- app.get('/', (req, res) => res.render('home', { user: req.user }));
16
-
17
- // Partial — just append `#partialName` to the view name
18
- app.get('/partials/:name', (req, res) =>
19
- res.render(`home#${req.params.name}`, { user: req.user })
20
- );
21
- ```
22
-
23
- - **`miki.expressPartialRenderer()` middleware** for drop-in `res.renderPartial(view, locals)` without changing your existing `app.engine()` setup.
24
- - **`miki.express()`** factory for `app.engine('html', miki.express())` — honors `view#partial` selectors.
25
- - **`renderPartialFromSource(source, partialName, ctx, opts)`** and **`renderPartialFromFile(...)`** — render a single named `{% partialdef %}` from a string or file. Walks the AST (and the `extends` chain) to find partials nested inside blocks.
26
- - **`{% include "file.html#partial" with ... %}`** — partial-selector syntax works with `include`, not just `res.render`.
27
- - **Built-in libraries** (`humanize`, `cache`, `lorem`) are now auto-activated on module load. `{% lorem 5 p %}` works without `{% load lorem %}`.
28
-
29
- ## Tag fixes
30
-
31
- - `{% with x=1, y=2 as pair %}` (multi-pair + alias) parses correctly.
32
- - `{% with a=x b=y %}` (multi-pair, no alias) now binds each pair instead of being treated as one expression.
33
- - `{% cycle 'a' 'b' as name %}` now emits nothing but stores the value (Django semantics).
34
- - `{{ block.super }}` works inside partials that override blocks.
35
- - `extends` resolves `views` from `options.settings.views` (Express path), `options.views`, or default.
36
- - Path-traversal protection applies to `extends` *and* `include`, including `include "file#partial"`.
37
- - `default` vs `default_if_none`: `default` falls back on `''`, `null`, and `undefined`; `default_if_none` only on `null`/`undefined` (Django parity).
38
- - `removetags` accepts multiple tag names.
39
- - `Context.get('a.b.c')` returns `undefined` for missing keys (was `''`), so `{% if x %}` and `default_if_none` work correctly.
40
-
41
- ## Test coverage
42
-
43
- **382/382 tests pass.** Added a 190-test integration suite (`tests/integration/`) covering every tag, filter, and feature under both CommonJS and ESM, exercised against a real Express HTTP server on ephemeral ports. The test runner recursively picks up `.test.{cjs,mjs,js}` files.
44
-
45
- ## Docs
46
-
47
- - `README.md` — promoted `miki.setupExpress` to the first listed feature; the Express section leads with the one-liner.
48
- - `docs/usage.md` — full Express section rewritten with the new setup, options table, and middleware variant.
49
- - `docs/api.md` — added rows for the new APIs; updated CJS/ESM import snippets.
50
- - `docs/partialdef.md` — documented `include "file#partial"`, `renderPartialFromSource/File`, and the HTMX-over-HTTP flow.
51
- - `CHANGELOG.md` — full entry for 1.3.1.
52
-
53
- ## Compatibility
54
-
55
- No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
1
+ # miki-template v1.3.1
2
+
3
+ ## Highlights
4
+
5
+ - **One-line Express integration**: `miki.setupExpress(app, { extension: 'html', views: dir })` wires the view engine, the views directory, and a `res.render` shim that makes HTMX-style partial responses Just Work.
6
+
7
+ ```js
8
+ const express = require('express');
9
+ const miki = require('miki-template');
10
+
11
+ const app = express();
12
+ miki.setupExpress(app, { extension: 'html', views: './views' });
13
+
14
+ // Full page
15
+ app.get('/', (req, res) => res.render('home', { user: req.user }));
16
+
17
+ // Partial — just append `#partialName` to the view name
18
+ app.get('/partials/:name', (req, res) =>
19
+ res.render(`home#${req.params.name}`, { user: req.user })
20
+ );
21
+ ```
22
+
23
+ - **`miki.expressPartialRenderer()` middleware** for drop-in `res.renderPartial(view, locals)` without changing your existing `app.engine()` setup.
24
+ - **`miki.express()`** factory for `app.engine('html', miki.express())` — honors `view#partial` selectors.
25
+ - **`renderPartialFromSource(source, partialName, ctx, opts)`** and **`renderPartialFromFile(...)`** — render a single named `{% partialdef %}` from a string or file. Walks the AST (and the `extends` chain) to find partials nested inside blocks.
26
+ - **`{% include "file.html#partial" with ... %}`** — partial-selector syntax works with `include`, not just `res.render`.
27
+ - **Built-in libraries** (`humanize`, `cache`, `lorem`) are now auto-activated on module load. `{% lorem 5 p %}` works without `{% load lorem %}`.
28
+
29
+ ## Tag fixes
30
+
31
+ - `{% with x=1, y=2 as pair %}` (multi-pair + alias) parses correctly.
32
+ - `{% with a=x b=y %}` (multi-pair, no alias) now binds each pair instead of being treated as one expression.
33
+ - `{% cycle 'a' 'b' as name %}` now emits nothing but stores the value (Django semantics).
34
+ - `{{ block.super }}` works inside partials that override blocks.
35
+ - `extends` resolves `views` from `options.settings.views` (Express path), `options.views`, or default.
36
+ - Path-traversal protection applies to `extends` *and* `include`, including `include "file#partial"`.
37
+ - `default` vs `default_if_none`: `default` falls back on `''`, `null`, and `undefined`; `default_if_none` only on `null`/`undefined` (Django parity).
38
+ - `removetags` accepts multiple tag names.
39
+ - `Context.get('a.b.c')` returns `undefined` for missing keys (was `''`), so `{% if x %}` and `default_if_none` work correctly.
40
+
41
+ ## Test coverage
42
+
43
+ **382/382 tests pass.** Added a 190-test integration suite (`tests/integration/`) covering every tag, filter, and feature under both CommonJS and ESM, exercised against a real Express HTTP server on ephemeral ports. The test runner recursively picks up `.test.{cjs,mjs,js}` files.
44
+
45
+ ## Docs
46
+
47
+ - `README.md` — promoted `miki.setupExpress` to the first listed feature; the Express section leads with the one-liner.
48
+ - `docs/usage.md` — full Express section rewritten with the new setup, options table, and middleware variant.
49
+ - `docs/api.md` — added rows for the new APIs; updated CJS/ESM import snippets.
50
+ - `docs/partialdef.md` — documented `include "file#partial"`, `renderPartialFromSource/File`, and the HTMX-over-HTTP flow.
51
+ - `CHANGELOG.md` — full entry for 1.3.1.
52
+
53
+ ## Compatibility
54
+
55
+ No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
@@ -0,0 +1,77 @@
1
+ # miki-template v1.3.3
2
+
3
+ ## Highlights
4
+
5
+ - **Date/time filter fixed** — `{{ d|date:"yyyy-MM-dd" }}` now returns `"2024-06-15"` instead of `"24242424-JunJun-1515"`. Multi-character tokens (`yyyy`, `MM`, `dd`, `HH`, `mm`, `ii`, `ss`) are matched longest-first; backward-compatible single-character aliases (`m`/`d`/`H`/`i`/`s`) still work for Django-style unpadded values.
6
+ - **Inheritance 4× faster** — `{% extends %}` no longer re-reads the parent file from disk on every render. A 64-entry LRU cache in `src/cache.js` memoises the parent source. Inheritance rendering improved from ~481 rps to **~2,056 rps** in the strict benchmark.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install miki-template@1.3.3
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```js
17
+ const miki = require('miki-template');
18
+ const express = require('express');
19
+
20
+ const app = express();
21
+ miki.setupExpress(app, { extension: 'html', views: './views' });
22
+
23
+ // Full page
24
+ app.get('/', (req, res) => res.render('home', { user: req.user }));
25
+
26
+ // HTMX partial — just append #partialName
27
+ app.get('/partials/:name', (req, res) =>
28
+ res.render(`home#${req.params.name}`, { user: req.user })
29
+ );
30
+
31
+ app.listen(3000);
32
+ ```
33
+
34
+ ## What's changed
35
+
36
+ ### Fixed
37
+ - `date` and `time` filters — multi-character tokens render correctly.
38
+
39
+ ### Performance
40
+ - Inheritance parent-source LRU cache in `src/cache.js` (`getParentSource` / `hasParentSource`). `clearCache()` also clears it.
41
+
42
+ ### Tooling
43
+ - New `benchmarks/stress.mjs` — 37-check strict benchmark (correctness, compile speed, render speed, cache, scale, endurance, partial rendering, async, concurrency). Run with `node benchmarks/stress.mjs`. Exits with code 1 on any failure.
44
+ - `eslint.config.mjs` — added `URL`, `URLSearchParams`, `TextEncoder`, `TextDecoder`, `fetch`, `crypto`, `performance` to the Node 18+ globals list.
45
+ - Removed dead code: unused `elifBranches` variable in `parseIfChanged`, unused `extractPluralMappings` function in `i18n.js`.
46
+ - Renamed unused tag-parser `parser` parameters to `_parser` in `lorem` library and `parseLoad`.
47
+
48
+ ## Verification
49
+
50
+ | Check | Result |
51
+ |---|---|
52
+ | `npx eslint src/**/*.js` | 0 errors, 0 warnings |
53
+ | `npm test` | 397/397 passing |
54
+ | `node benchmarks/stress.mjs` | 37/37 passing |
55
+
56
+ ## Performance (from the stress benchmark)
57
+
58
+ | Template | Speed |
59
+ |---|---|
60
+ | Small (76 B) | **175,439 rps** (0.006 ms) |
61
+ | Medium (595 B) | 13,947 rps (0.072 ms) |
62
+ | Large (1.6 KB, 6 sections × 12 items) | 2,277 rps (0.439 ms) |
63
+ | Inheritance child | 2,056 rps (0.486 ms) |
64
+ | Partials (50 entries) | 2,235 rps (0.447 ms) |
65
+ | Scale: 50,000 items | 107 ms median |
66
+ | Endurance: 100,000 renders | 7.5s total, RSS +0.9 MB |
67
+ | Async: 200 concurrent LARGE | 237 ms total |
68
+
69
+ ## Compatibility
70
+
71
+ No breaking changes. All existing public APIs continue to work; new APIs are opt-in.
72
+
73
+ ## Links
74
+
75
+ - **Repository**: https://github.com/alainmiki/miki-template
76
+ - **Tag**: https://github.com/alainmiki/miki-template/releases/tag/v1.3.3
77
+ - **Previous**: [v1.3.1](https://github.com/alainmiki/miki-template/releases/tag/v1.3.1)
@@ -1,54 +1,38 @@
1
- # CI/CD Workflow
2
-
3
- name: CI
4
-
5
- on:
6
- push:
7
- branches: [ main ]
8
- pull_request:
9
- branches: [ main ]
10
- release:
11
- types: [ created ]
12
-
13
- jobs:
14
- lint:
15
- runs-on: windows-latest
16
- steps:
17
- - uses: actions/checkout@v4
18
- - name: Setup Node.js
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: '20'
22
- - run: npm ci
23
- - name: Lint code
24
- run: npx eslint src/**/*.js
25
-
26
- test:
27
- runs-on: windows-latest
28
- needs: lint
29
- steps:
30
- - uses: actions/checkout@v4
31
- - name: Setup Node.js
32
- uses: actions/setup-node@v4
33
- with:
34
- node-version: '20'
35
- - run: npm ci
36
- - name: Run test suite
37
- run: npm test
38
-
39
- publish:
40
- runs-on: windows-latest
41
- needs: test
42
- if: github.event_name == 'release' && github.event.action == 'created'
43
- steps:
44
- - uses: actions/checkout@v4
45
- - name: Setup Node.js
46
- uses: actions/setup-node@v4
47
- with:
48
- node-version: '20'
49
- registry-url: 'https://registry.npmjs.org'
50
- - run: npm ci
51
- - name: Publish package to npm
52
- run: npm publish --access public
53
- env:
54
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
1
+ # CI Workflow — lint + test on every push and PR.
2
+ #
3
+ # Releases are handled by .github/workflows/release.yml, which triggers
4
+ # automatically when a `chore(release):` commit lands on main.
5
+
6
+ name: CI
7
+
8
+ on:
9
+ push:
10
+ branches: [ main ]
11
+ pull_request:
12
+ branches: [ main ]
13
+
14
+ jobs:
15
+ lint:
16
+ runs-on: windows-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: '20'
23
+ - run: npm ci
24
+ - name: Lint code
25
+ run: npx eslint src/**/*.js
26
+
27
+ test:
28
+ runs-on: windows-latest
29
+ needs: lint
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - name: Setup Node.js
33
+ uses: actions/setup-node@v4
34
+ with:
35
+ node-version: '20'
36
+ - run: npm ci
37
+ - name: Run test suite
38
+ run: npm test
@@ -0,0 +1,106 @@
1
+ # Release workflow
2
+ #
3
+ # Fully automatic release pipeline. Triggered when a commit to main matches
4
+ # the `chore(release): v*` pattern produced by `npm run release:patch|minor|major`.
5
+ #
6
+ # Flow:
7
+ # 1. Detect the new version in package.json.
8
+ # 2. Lint + test + stress benchmark (must pass before continuing).
9
+ # 3. Create an annotated git tag v<version> and push it.
10
+ # 4. Create a GitHub Release with notes from .github/release-notes/v<version>.md
11
+ # (or a generic message if the file is missing).
12
+ # 5. Publish the package to npm using the NPM_TOKEN secret.
13
+ #
14
+ # Local usage:
15
+ # npm run release:patch # 1.3.3 → 1.3.4
16
+ # npm run release:minor # 1.3.3 → 1.4.0
17
+ # npm run release:major # 1.3.3 → 2.0.0
18
+ # That bumps package.json, creates a commit, pushes to main, and the rest
19
+ # happens here.
20
+
21
+ name: Release
22
+
23
+ on:
24
+ push:
25
+ branches: [main]
26
+
27
+ permissions:
28
+ contents: write
29
+
30
+ jobs:
31
+ release:
32
+ # Only run for the explicit "chore(release):" commits. Any other
33
+ # push to main is ignored so contributors can edit package.json
34
+ # without accidentally publishing.
35
+ if: startsWith(github.event.head_commit.message, 'chore(release):')
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ with:
40
+ fetch-depth: 0
41
+ token: ${{ secrets.GITHUB_TOKEN }}
42
+
43
+ - uses: actions/setup-node@v4
44
+ with:
45
+ node-version: '20'
46
+ registry-url: 'https://registry.npmjs.org'
47
+
48
+ - name: Install dependencies
49
+ run: npm ci
50
+
51
+ - name: Lint
52
+ run: npx eslint src/**/*.js
53
+
54
+ - name: Test
55
+ run: npm test
56
+
57
+ - name: Stress benchmark
58
+ run: node benchmarks/stress.mjs
59
+
60
+ - name: Read package version
61
+ id: ver
62
+ run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
63
+
64
+ - name: Check tag does not already exist
65
+ run: |
66
+ if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
67
+ echo "::error::Tag v${{ steps.ver.outputs.version }} already exists."
68
+ exit 1
69
+ fi
70
+
71
+ - name: Create and push git tag
72
+ run: |
73
+ git config user.name "github-actions[bot]"
74
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
75
+ git tag -a "v${{ steps.ver.outputs.version }}" -m "v${{ steps.ver.outputs.version }}"
76
+ git push origin "v${{ steps.ver.outputs.version }}"
77
+
78
+ - name: Build release body
79
+ id: notes
80
+ env:
81
+ NOTES_FILE: .github/release-notes/v${{ steps.ver.outputs.version }}.md
82
+ VERSION: ${{ steps.ver.outputs.version }}
83
+ run: |
84
+ {
85
+ echo "body<<EOF_RELEASE"
86
+ if [ -f "$NOTES_FILE" ]; then
87
+ cat "$NOTES_FILE"
88
+ else
89
+ echo "## v${VERSION}"
90
+ echo ""
91
+ echo "See [CHANGELOG.md](../../blob/main/CHANGELOG.md) for details."
92
+ fi
93
+ echo "EOF_RELEASE"
94
+ } >> "$GITHUB_OUTPUT"
95
+
96
+ - name: Create GitHub Release
97
+ uses: softprops/action-gh-release@v2
98
+ with:
99
+ tag_name: v${{ steps.ver.outputs.version }}
100
+ name: v${{ steps.ver.outputs.version }}
101
+ body: ${{ steps.notes.outputs.body }}
102
+
103
+ - name: Publish to npm
104
+ run: npm publish --access public
105
+ env:
106
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/AGENT.md CHANGED
@@ -1,71 +1,71 @@
1
- # AGENT.md - Agent Instructions, Guardrails, & Architecture Rules
2
-
3
- This repository implements a Django-Style Template Engine for Node.js/Express. To ensure high code quality, consistent architecture, and full feature parity with Django without guesswork, all AI agents and developers must strictly follow these instructions and guardrails.
4
-
5
- ---
6
-
7
- ## 🎯 Core Objectives
8
- 1. **Full Feature Parity**: Match Django's template engine rules for rendering variables, dotted lookups, built-in tags, filters, and template inheritance.
9
- 2. **Deterministic & Secure**: Escape output by default using a safe-string mechanism.
10
- 3. **No Spaghetti Code**: Maintain a strict separation of Lexer, Parser, Renderer, Context, and Tag/Filter registries.
11
-
12
- ---
13
-
14
- ## ⚠️ Strict Guardrails & Anti-Hallucination Rules
15
-
16
- > [!IMPORTANT]
17
- > **No Guesswork / Assumptions**
18
- > - If a tag or filter's behavior is not clearly defined in [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) or Django documentation, **DO NOT guess**.
19
- > - If you notice ambiguities (e.g., how to handle circular extends or nested cycles), raise a question to the user immediately or throw a descriptive compilation error.
20
-
21
- > [!WARNING]
22
- > **No Regex-Based Global Search & Replace**
23
- > - You **must not** implement tags or filters using global regex replacements on the final string. All templates must go through:
24
- > `Lexer (String -> Tokens) -> Parser (Tokens -> AST) -> Renderer (AST + Context -> HTML)`.
25
- > - Direct regex replacement on HTML bypasses nesting, escaping, and blocks, which introduces severe bugs.
26
-
27
- > [!CAUTION]
28
- > **Security Guardrails**
29
- > - All variable output must be HTML-escaped by default.
30
- > - A variable is only safe from escaping if it is marked as a `SafeString` (e.g., using the `safe` filter or internally flagged).
31
- > - Dynamic expression evaluation (such as in `if` tags) **must not** use direct JS `eval()`. Use a safe AST evaluator or sandboxed parser to prevent remote code execution.
32
-
33
- ---
34
-
35
- ## 🧩 Architectural Guidelines
36
-
37
- ### 1. Lexing (`src/lexer.js`)
38
- * Tokens must be generated for three primary types:
39
- - `TEXT`: Plain HTML/text.
40
- - `VAR`: Variables wrapped in `{{ ... }}`.
41
- - `BLOCK`: Structural tags wrapped in `{% ... %}`.
42
- * The Lexer must correctly handle verbatim blocks `{% verbatim %}` and comments `{% comment %}` by suppressing token output or passing raw text tokens.
43
-
44
- ### 2. Parsing (`src/parser.js`)
45
- * The Parser takes a stream of tokens and constructs an AST of nodes.
46
- * Every node must implement a `.render(context)` method (async or sync).
47
- * For tags that have closing tags (e.g., `{% if %}`...`{% endif %}`), the parser must parse nested tokens recursively until the matching close tag is encountered.
48
-
49
- ### 3. Context & Scope Isolation (`src/context.js`)
50
- * Context must support a stack-like structure: `.push()` to create a new scope, and `.pop()` to revert.
51
- * **Variable Lookups**: Support dotted lookups:
52
- - Example: `{{ user.profile.name }}` should search `user` in context, then resolve property `profile`, then property `name`.
53
- - If a resolved value is a function, call it (without arguments, just like Django).
54
- - If a lookup fails, return an empty string `""` by default, unless configured otherwise.
55
-
56
- ### 4. Template Inheritance (`src/tags/inheritance.js`)
57
- * Inheritance works by having a child template load a parent template via `{% extends "parent.html" %}`.
58
- * The child overrides blocks defined as `{% block block_name %}`.
59
- * During render, the parent's AST is evaluated, but block nodes are replaced by the child's corresponding block nodes.
60
- * Support block nesting and the `{{ block.super }}` variable to render parent block content.
61
-
62
- ---
63
-
64
- ## 🛠️ Step-by-Step Task Execution Protocol
65
-
66
- When executing tasks:
67
- 1. **Analyze Requirements**: Check [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) and [component.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/component.md).
68
- 2. **Check Existing Tests**: Run current test suite using `npm test` or `node --test` to ensure a green state.
69
- 3. **Write Tests First**: For any new filter, tag, or parser logic, write a corresponding unit test in `tests/` showing the expected template string and its expected output.
70
- 4. **Implement Modular Code**: Put tag handlers in `src/tags/`, filters in `src/filters.js`, and maintain registry isolation.
71
- 5. **Verify**: Ensure the test suite passes, check for escaping vulnerabilities, and check that no scope leaks occur.
1
+ # AGENT.md - Agent Instructions, Guardrails, & Architecture Rules
2
+
3
+ This repository implements a Django-Style Template Engine for Node.js/Express. To ensure high code quality, consistent architecture, and full feature parity with Django without guesswork, all AI agents and developers must strictly follow these instructions and guardrails.
4
+
5
+ ---
6
+
7
+ ## 🎯 Core Objectives
8
+ 1. **Full Feature Parity**: Match Django's template engine rules for rendering variables, dotted lookups, built-in tags, filters, and template inheritance.
9
+ 2. **Deterministic & Secure**: Escape output by default using a safe-string mechanism.
10
+ 3. **No Spaghetti Code**: Maintain a strict separation of Lexer, Parser, Renderer, Context, and Tag/Filter registries.
11
+
12
+ ---
13
+
14
+ ## ⚠️ Strict Guardrails & Anti-Hallucination Rules
15
+
16
+ > [!IMPORTANT]
17
+ > **No Guesswork / Assumptions**
18
+ > - If a tag or filter's behavior is not clearly defined in [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) or Django documentation, **DO NOT guess**.
19
+ > - If you notice ambiguities (e.g., how to handle circular extends or nested cycles), raise a question to the user immediately or throw a descriptive compilation error.
20
+
21
+ > [!WARNING]
22
+ > **No Regex-Based Global Search & Replace**
23
+ > - You **must not** implement tags or filters using global regex replacements on the final string. All templates must go through:
24
+ > `Lexer (String -> Tokens) -> Parser (Tokens -> AST) -> Renderer (AST + Context -> HTML)`.
25
+ > - Direct regex replacement on HTML bypasses nesting, escaping, and blocks, which introduces severe bugs.
26
+
27
+ > [!CAUTION]
28
+ > **Security Guardrails**
29
+ > - All variable output must be HTML-escaped by default.
30
+ > - A variable is only safe from escaping if it is marked as a `SafeString` (e.g., using the `safe` filter or internally flagged).
31
+ > - Dynamic expression evaluation (such as in `if` tags) **must not** use direct JS `eval()`. Use a safe AST evaluator or sandboxed parser to prevent remote code execution.
32
+
33
+ ---
34
+
35
+ ## 🧩 Architectural Guidelines
36
+
37
+ ### 1. Lexing (`src/lexer.js`)
38
+ * Tokens must be generated for three primary types:
39
+ - `TEXT`: Plain HTML/text.
40
+ - `VAR`: Variables wrapped in `{{ ... }}`.
41
+ - `BLOCK`: Structural tags wrapped in `{% ... %}`.
42
+ * The Lexer must correctly handle verbatim blocks `{% verbatim %}` and comments `{% comment %}` by suppressing token output or passing raw text tokens.
43
+
44
+ ### 2. Parsing (`src/parser.js`)
45
+ * The Parser takes a stream of tokens and constructs an AST of nodes.
46
+ * Every node must implement a `.render(context)` method (async or sync).
47
+ * For tags that have closing tags (e.g., `{% if %}`...`{% endif %}`), the parser must parse nested tokens recursively until the matching close tag is encountered.
48
+
49
+ ### 3. Context & Scope Isolation (`src/context.js`)
50
+ * Context must support a stack-like structure: `.push()` to create a new scope, and `.pop()` to revert.
51
+ * **Variable Lookups**: Support dotted lookups:
52
+ - Example: `{{ user.profile.name }}` should search `user` in context, then resolve property `profile`, then property `name`.
53
+ - If a resolved value is a function, call it (without arguments, just like Django).
54
+ - If a lookup fails, return an empty string `""` by default, unless configured otherwise.
55
+
56
+ ### 4. Template Inheritance (`src/tags/inheritance.js`)
57
+ * Inheritance works by having a child template load a parent template via `{% extends "parent.html" %}`.
58
+ * The child overrides blocks defined as `{% block block_name %}`.
59
+ * During render, the parent's AST is evaluated, but block nodes are replaced by the child's corresponding block nodes.
60
+ * Support block nesting and the `{{ block.super }}` variable to render parent block content.
61
+
62
+ ---
63
+
64
+ ## 🛠️ Step-by-Step Task Execution Protocol
65
+
66
+ When executing tasks:
67
+ 1. **Analyze Requirements**: Check [prd.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/prd.md) and [component.md](file:///c:/Users/Coder%20Miki/Desktop/miki-template/context/component.md).
68
+ 2. **Check Existing Tests**: Run current test suite using `npm test` or `node --test` to ensure a green state.
69
+ 3. **Write Tests First**: For any new filter, tag, or parser logic, write a corresponding unit test in `tests/` showing the expected template string and its expected output.
70
+ 4. **Implement Modular Code**: Put tag handlers in `src/tags/`, filters in `src/filters.js`, and maintain registry isolation.
71
+ 5. **Verify**: Ensure the test suite passes, check for escaping vulnerabilities, and check that no scope leaks occur.