@xn-intenton-z2a/agentic-lib 7.2.11 → 7.2.13

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.
@@ -2,15 +2,12 @@
2
2
  # Copyright (C) 2025-2026 Polycode Limited
3
3
  # .github/workflows/agentic-lib-init.yml
4
4
  #
5
- # Self-contained init: update agentic-lib, purge, push to main [skip ci].
6
- # Does NOT create a PR. Does NOT trigger any other workflow.
7
- # Closes hanging init PRs before running.
8
- #
9
- # In agentic-lib: schedule trigger is commented out.
10
- # During init: #@dist restores the schedule.
5
+ # Init: delegates to agentic-lib-update.yml for the update step, then
6
+ # optionally reseeds or purges user content and configures schedule/model.
7
+ # Commits and pushes to main [skip ci].
11
8
 
12
9
  name: agentic-lib-init
13
- run-name: "agentic-lib-init [${{ github.ref_name }}]"
10
+ run-name: "agentic-lib-init ${{ inputs.mode || 'update' }} [${{ github.ref_name }}]"
14
11
 
15
12
  on:
16
13
  workflow_call:
@@ -120,49 +117,27 @@ on:
120
117
  permissions: write-all
121
118
 
122
119
  jobs:
123
- params:
124
- runs-on: ubuntu-latest
125
- steps:
126
- - name: Normalise params
127
- id: normalise
128
- shell: bash
129
- run: |
130
- MODE='${{ inputs.mode }}'
131
- echo "mode=${MODE:-update}" >> $GITHUB_OUTPUT
132
- DRY_RUN='${{ inputs.dry-run }}'
133
- echo "dry-run=${DRY_RUN:-false}" >> $GITHUB_OUTPUT
134
- MISSION_SEED='${{ inputs.mission-seed }}'
135
- echo "mission-seed=${MISSION_SEED:-hamming-distance}" >> $GITHUB_OUTPUT
136
- MISSION_TEXT='${{ inputs.mission-text }}'
137
- echo "mission-text=${MISSION_TEXT}" >> $GITHUB_OUTPUT
138
- SCHEDULE='${{ inputs.schedule }}'
139
- echo "schedule=${SCHEDULE}" >> $GITHUB_OUTPUT
140
- MODEL='${{ inputs.model }}'
141
- echo "model=${MODEL:-gpt-5-mini}" >> $GITHUB_OUTPUT
142
- PROFILE='${{ inputs.profile }}'
143
- echo "profile=${PROFILE}" >> $GITHUB_OUTPUT
144
- outputs:
145
- mode: ${{ steps.normalise.outputs.mode }}
146
- dry-run: ${{ steps.normalise.outputs.dry-run }}
147
- mission-seed: ${{ steps.normalise.outputs.mission-seed }}
148
- mission-text: ${{ steps.normalise.outputs.mission-text }}
149
- schedule: ${{ steps.normalise.outputs.schedule }}
150
- model: ${{ steps.normalise.outputs.model }}
151
- profile: ${{ steps.normalise.outputs.profile }}
120
+ # Step 1: Update agentic-lib and infrastructure (commits to main)
121
+ update:
122
+ uses: ./.github/workflows/agentic-lib-update.yml
123
+ with:
124
+ dry-run: ${{ inputs.dry-run || 'false' }}
125
+ secrets: inherit
152
126
 
127
+ # Step 2: Reseed/purge and configure (only if mode != update)
153
128
  init:
154
- needs: params
129
+ needs: update
130
+ if: inputs.mode == 'reseed' || inputs.mode == 'purge'
155
131
  runs-on: ubuntu-latest
156
132
  env:
157
- INIT_MODE: ${{ needs.params.outputs.mode }}
158
- INIT_DRY_RUN: ${{ needs.params.outputs.dry-run }}
159
- INIT_MISSION_SEED: ${{ needs.params.outputs.mission-seed }}
160
- INIT_MISSION_TEXT: ${{ needs.params.outputs.mission-text }}
133
+ INIT_MODE: ${{ inputs.mode }}
134
+ INIT_MISSION_SEED: ${{ inputs.mission-seed || 'hamming-distance' }}
135
+ INIT_MISSION_TEXT: ${{ inputs.mission-text }}
161
136
  steps:
162
137
  - uses: actions/checkout@v6
163
- if: needs.params.outputs.dry-run == 'true'
138
+ if: inputs.dry-run == 'true' || inputs.dry-run == true
164
139
  - uses: actions/checkout@v6
165
- if: needs.params.outputs.dry-run != 'true'
140
+ if: inputs.dry-run != 'true' && inputs.dry-run != true
166
141
  with:
167
142
  ref: main
168
143
  token: ${{ secrets.WORKFLOW_TOKEN }}
@@ -172,11 +147,10 @@ jobs:
172
147
  node-version: "24"
173
148
 
174
149
  - name: Close hanging init PRs
175
- if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.INIT_MODE == 'purge' && needs.params.outputs.dry-run != 'true'
150
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.INIT_MODE == 'purge' && inputs.dry-run != 'true' && inputs.dry-run != true
176
151
  env:
177
152
  GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
178
153
  run: |
179
- # Close any open PRs from previous init runs
180
154
  gh pr list --state open --json number,headRefName \
181
155
  --jq '.[] | select(.headRefName | startswith("agentic-lib-init-")) | .number' \
182
156
  | while read -r pr_num; do
@@ -184,18 +158,15 @@ jobs:
184
158
  gh pr close "$pr_num" --delete-branch || true
185
159
  done
186
160
 
187
- - name: Update agentic-lib to latest
188
- run: npm update @xn-intenton-z2a/agentic-lib
189
-
190
- - name: Run init
161
+ - name: Run init (reseed/purge)
191
162
  env:
192
163
  GH_TOKEN: ${{ github.token }}
193
164
  run: |
194
165
  FLAGS="init"
195
166
  if [ "$INIT_MODE" = "reseed" ]; then FLAGS="$FLAGS --reseed"; fi
196
167
  if [ "$INIT_MODE" = "purge" ]; then FLAGS="$FLAGS --purge --mission $INIT_MISSION_SEED"; fi
197
- if [ "$INIT_DRY_RUN" = "true" ]; then FLAGS="$FLAGS --dry-run"; fi
198
- # Use local CLI if available (agentic-lib dev), otherwise npx
168
+ DRY_RUN='${{ inputs.dry-run }}'
169
+ if [ "$DRY_RUN" = "true" ]; then FLAGS="$FLAGS --dry-run"; fi
199
170
  if [ -f "bin/agentic-lib.js" ]; then
200
171
  node bin/agentic-lib.js $FLAGS
201
172
  else
@@ -203,7 +174,7 @@ jobs:
203
174
  fi
204
175
 
205
176
  - name: Write mission text (if provided, overrides seed)
206
- if: env.INIT_MISSION_TEXT != '' && needs.params.outputs.dry-run != 'true'
177
+ if: env.INIT_MISSION_TEXT != '' && inputs.dry-run != 'true' && inputs.dry-run != true
207
178
  run: |
208
179
  printf '# Mission\n\n%s\n' "$INIT_MISSION_TEXT" > MISSION.md
209
180
  echo "Wrote custom mission text to MISSION.md"
@@ -221,18 +192,16 @@ jobs:
221
192
  - run: npm test
222
193
 
223
194
  - name: Update model and profile (if requested)
224
- if: github.repository != 'xn-intenton-z2a/agentic-lib' && needs.params.outputs.dry-run != 'true' && (needs.params.outputs.model != '' || needs.params.outputs.profile != '')
195
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && inputs.dry-run != 'true' && inputs.dry-run != true && (inputs.model != '' || inputs.profile != '')
225
196
  uses: actions/github-script@v8
226
197
  with:
227
198
  script: |
228
199
  const fs = require('fs');
229
- const model = '${{ needs.params.outputs.model }}';
230
- const profile = '${{ needs.params.outputs.profile }}';
200
+ const model = '${{ inputs.model }}';
201
+ const profile = '${{ inputs.profile }}';
231
202
  const tomlPath = 'agentic-lib.toml';
232
203
  if (!fs.existsSync(tomlPath)) return;
233
204
  let toml = fs.readFileSync(tomlPath, 'utf8');
234
- // Extract the [tuning] section, then replace within it.
235
- // Previous regex [^\[]*? failed when comments contained '[' characters.
236
205
  const tuningSectionRegex = /(\[tuning\])([\s\S]*?)(?=\n\[|$)/;
237
206
  const tuningMatch = toml.match(tuningSectionRegex);
238
207
  if (tuningMatch) {
@@ -256,13 +225,13 @@ jobs:
256
225
  fs.writeFileSync(tomlPath, toml);
257
226
 
258
227
  - name: Update schedule (if requested)
259
- if: github.repository != 'xn-intenton-z2a/agentic-lib' && needs.params.outputs.schedule != '' && needs.params.outputs.dry-run != 'true'
228
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && inputs.schedule != '' && inputs.dry-run != 'true' && inputs.dry-run != true
260
229
  uses: actions/github-script@v8
261
230
  with:
262
231
  script: |
263
232
  const fs = require('fs');
264
- const frequency = '${{ needs.params.outputs.schedule }}';
265
- const model = '${{ needs.params.outputs.model || 'gpt-5-mini' }}';
233
+ const frequency = '${{ inputs.schedule }}';
234
+ const model = '${{ inputs.model || 'gpt-5-mini' }}';
266
235
  const workflowPath = '.github/workflows/agentic-lib-workflow.yml';
267
236
  const tomlPath = 'agentic-lib.toml';
268
237
 
@@ -308,7 +277,7 @@ jobs:
308
277
  }
309
278
 
310
279
  - name: Commit and push to main [skip ci]
311
- if: github.repository != 'xn-intenton-z2a/agentic-lib' && needs.params.outputs.dry-run != 'true'
280
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && inputs.dry-run != 'true' && inputs.dry-run != true
312
281
  env:
313
282
  GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
314
283
  run: |
@@ -334,9 +303,8 @@ jobs:
334
303
  fi
335
304
  done
336
305
 
337
- # W8: Create initial seed issues after purge so the pipeline has work to do
338
306
  - name: Create initial seed issues
339
- if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.INIT_MODE == 'purge' && needs.params.outputs.dry-run != 'true'
307
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.INIT_MODE == 'purge' && inputs.dry-run != 'true' && inputs.dry-run != true
340
308
  uses: actions/github-script@v8
341
309
  with:
342
310
  script: |
@@ -345,7 +313,6 @@ jobs:
345
313
  ? fs.readFileSync('MISSION.md', 'utf8')
346
314
  : '(no MISSION.md found)';
347
315
 
348
- // Ensure labels exist
349
316
  for (const label of ['automated', 'ready']) {
350
317
  try {
351
318
  await github.rest.issues.createLabel({
@@ -356,7 +323,6 @@ jobs:
356
323
  } catch (e) { /* label already exists */ }
357
324
  }
358
325
 
359
- // W8a: Initial unit tests issue
360
326
  const unitTestBody = [
361
327
  'Create a unit test file for each of the major features in the mission ',
362
328
  'and put a TODO in a trivial empty passing test in each.',
@@ -373,7 +339,6 @@ jobs:
373
339
  });
374
340
  core.info(`Created issue #${issue1.number}: Initial unit tests`);
375
341
 
376
- // W8b: Initial web layout issue
377
342
  const webLayoutBody = [
378
343
  'Create the home page layout to showcase each of the major features in the mission ',
379
344
  'and put a TODO in a trivial empty passing test in each.',
@@ -0,0 +1,100 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2025-2026 Polycode Limited
3
+ # .github/workflows/agentic-lib-update.yml
4
+ #
5
+ # Update agentic-lib to latest, extract workflows/actions/scripts, run tests,
6
+ # commit and push to main [skip ci]. Does NOT reseed or purge user content.
7
+ #
8
+ # In agentic-lib: schedule trigger is commented out.
9
+ # During init: #@dist restores the schedule.
10
+
11
+ name: agentic-lib-update
12
+ run-name: "agentic-lib-update [${{ github.ref_name }}]"
13
+
14
+ on:
15
+ workflow_call:
16
+ inputs:
17
+ dry-run:
18
+ type: string
19
+ required: false
20
+ default: "false"
21
+ #@dist schedule:
22
+ #@dist - cron: "15 6 * * *"
23
+ workflow_dispatch:
24
+ inputs:
25
+ dry-run:
26
+ description: "Preview changes without committing"
27
+ type: boolean
28
+ required: false
29
+ default: false
30
+
31
+ permissions: write-all
32
+
33
+ jobs:
34
+ update:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v6
38
+ if: inputs.dry-run == 'true' || inputs.dry-run == true
39
+ - uses: actions/checkout@v6
40
+ if: inputs.dry-run != 'true' && inputs.dry-run != true
41
+ with:
42
+ ref: main
43
+ token: ${{ secrets.WORKFLOW_TOKEN }}
44
+
45
+ - uses: actions/setup-node@v6
46
+ with:
47
+ node-version: "24"
48
+
49
+ - name: Update agentic-lib to latest
50
+ run: npm update @xn-intenton-z2a/agentic-lib
51
+
52
+ - name: Run init (update mode)
53
+ env:
54
+ GH_TOKEN: ${{ github.token }}
55
+ run: |
56
+ # Use local CLI if available (agentic-lib dev), otherwise npx
57
+ if [ -f "bin/agentic-lib.js" ]; then
58
+ node bin/agentic-lib.js init
59
+ else
60
+ npx @xn-intenton-z2a/agentic-lib init
61
+ fi
62
+
63
+ - run: npm install
64
+
65
+ - name: Install agentic-step deps
66
+ if: hashFiles('.github/agentic-lib/actions/agentic-step/package.json') != ''
67
+ run: cd .github/agentic-lib/actions/agentic-step && npm ci
68
+
69
+ - name: Install sub-project dependencies (agentic-lib dev only)
70
+ if: hashFiles('src/actions/agentic-step/package.json') != ''
71
+ run: cd src/actions/agentic-step && npm ci
72
+
73
+ - run: npm test
74
+
75
+ - name: Commit and push to main [skip ci]
76
+ if: github.repository != 'xn-intenton-z2a/agentic-lib' && inputs.dry-run != 'true' && inputs.dry-run != true
77
+ env:
78
+ GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
79
+ run: |
80
+ TOKEN_USER=$(gh api /user --jq '.login')
81
+ TOKEN_ID=$(gh api /user --jq '.id')
82
+ git config user.name "${TOKEN_USER}"
83
+ git config user.email "${TOKEN_ID}+${TOKEN_USER}@users.noreply.github.com"
84
+ git add -A
85
+ git diff --cached --quiet && echo "No changes" && exit 0
86
+ VERSION=$(npx @xn-intenton-z2a/agentic-lib version 2>/dev/null || echo "latest")
87
+ git commit -m "update agentic-lib@${VERSION} [skip ci]"
88
+ for attempt in 1 2 3; do
89
+ git push origin main && break
90
+ echo "Push failed (attempt $attempt) — pulling and retrying"
91
+ git pull --rebase origin main || {
92
+ echo "Rebase conflict — aborting rebase and retrying"
93
+ git rebase --abort 2>/dev/null || true
94
+ }
95
+ sleep $((attempt * 2))
96
+ if [ "$attempt" -eq 3 ]; then
97
+ echo "::error::Failed to push after 3 attempts"
98
+ exit 1
99
+ fi
100
+ done
@@ -720,6 +720,7 @@ function initPurge(seedsDir, missionName, initTimestamp) {
720
720
  "zero-main.js": "src/lib/main.js",
721
721
  "zero-main.test.js": "tests/unit/main.test.js",
722
722
  "zero-index.html": "src/web/index.html",
723
+ "zero-lib.js": "src/web/lib.js",
723
724
  "zero-web.test.js": "tests/unit/web.test.js",
724
725
  "zero-behaviour.test.js": "tests/behaviour/homepage.test.js",
725
726
  "zero-playwright.config.js": "playwright.config.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.2.11",
3
+ "version": "7.2.13",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -120,7 +120,7 @@ export async function transform(context) {
120
120
  `## Website Files (${webFiles.length})`,
121
121
  "The website in `src/web/` uses the JS library.",
122
122
  "When transforming source code, also update the website to use the library's new/changed features.",
123
- "The `lib-meta.js` file (generated by `npm run build:web` from `package.json`) provides `name`, `version`, and `description` exports to the browser.",
123
+ "`src/web/lib.js` re-exports from `../lib/main.js` so the page imports the real library directly. Keep `main.js` browser-safe (guard Node APIs behind `typeof process` checks). NEVER duplicate library functions inline in the web page.",
124
124
  ...webFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
125
125
  "",
126
126
  ]
@@ -18,9 +18,9 @@ A fix is never just one file. These layers form a single unit — if you change
18
18
 
19
19
  - **Library source** (`src/lib/main.js`) — the core implementation
20
20
  - **Unit tests** (`tests/unit/`) — test every function at the API level with exact values and edge cases
21
- - **Website** (`src/web/index.html` and related files) — imports and calls the library to demonstrate features.
22
- **NEVER duplicate library functions inline in the web page** — use the build pipeline (`lib-meta.js`, or
23
- a generated browser module) to share code. Inline copies cause behaviour tests to test a simulation, not the real library.
21
+ - **Website** (`src/web/index.html` and `src/web/lib.js`) — `lib.js` re-exports from `../lib/main.js`, so the
22
+ page imports the **real library** directly. **NEVER duplicate library functions inline in the web page** —
23
+ add exports to `main.js` and they are automatically available via `lib.js`.
24
24
  - **Website unit tests** (`tests/unit/web.test.js`) — verify HTML structure and library wiring
25
25
  - **Behaviour tests** (`tests/behaviour/`) — Playwright tests that load the website in a real browser
26
26
  and verify features work at a high navigational level (demo output visible, interactive elements work).
@@ -79,23 +79,26 @@ automatically (from `docs/` via `npm run build:web`).
79
79
 
80
80
  ### How the library connects to the website
81
81
 
82
- - `src/lib/main.js` is the JS library it exports functions, `name`, `version`, `description`, and `getIdentity()`
83
- - `npm run build:web` copies `src/web/*` to `docs/` and generates `docs/lib-meta.js` from `package.json`
84
- - `src/web/index.html` imports `lib-meta.js` via `<script type="module">` to display the library's identity
85
- - The website should import and call library functions not just describe them
82
+ - `src/lib/main.js` is browser-safe in Node it reads `package.json` via `createRequire`, in the browser via `fetch`
83
+ - `src/web/lib.js` re-exports from `../lib/main.js` the page imports the **real library**
84
+ - `src/web/index.html` imports `./lib.js` via `<script type="module">` to access the library directly
85
+ - The website works at rest (no build step needed for development)
86
+ - `npm run build:web` generates `docs/lib.js` as a self-contained module for production (GitHub Pages)
86
87
 
87
88
  ### What to do with the website
88
89
 
89
- - **Use the library**: Import the library (or its browser-compatible parts) and call its functions on the page
90
+ - **Use the library**: Import from `./lib.js` which re-exports from `../lib/main.js` call library functions directly
90
91
  - **Show real results**: Display actual output from library functions, not placeholder text
91
92
  - When you add or change library functions, update the website to reflect them
92
93
  - **Demonstrate features**: Each significant library feature should be visible and usable on the website.
93
94
  Behaviour tests will verify these demonstrations work, so make them testable (use IDs, structured output).
95
+ - **Keep main.js browser-safe**: Do not add Node-only top-level code (e.g. bare `require`, `process.argv` checks)
96
+ without guarding them behind `typeof process !== "undefined"` checks
94
97
 
95
98
  ### CRITICAL: Never duplicate library code in the web page
96
99
 
97
- **Do NOT copy or recreate library functions inline in `src/web/index.html`.** The web page must consume
98
- the library through the build pipeline not by reimplementing functions in a `<script>` block.
100
+ **Do NOT copy or recreate library functions inline in `src/web/index.html`.** The web page imports the
101
+ library directly via `src/web/lib.js` `src/lib/main.js`. There is no reason to duplicate.
99
102
 
100
103
  Why this matters:
101
104
  - Unit tests test `src/lib/main.js` directly
@@ -104,11 +107,11 @@ Why this matters:
104
107
  - The two copies can diverge silently, and tests pass even when the real library is broken
105
108
 
106
109
  How to share code between library and web:
107
- - Identity (name, version, description) flows via `lib-meta.js` (generated by `build:web` from `package.json`)
108
- - If the web page needs to call mission-specific functions, make them available through the build pipeline
109
- (e.g. generate a browser-compatible module in `docs/` or add a build step that packages the pure functions)
110
+ - `src/web/lib.js` re-exports everything from `../lib/main.js` add exports to `main.js` and they are
111
+ automatically available to the web page
112
+ - If adding a new exported function to `main.js`, make sure it is browser-safe (no bare Node API calls)
110
113
  - The behaviour test imports `getIdentity()` from `src/lib/main.js` and asserts the page displays the same
111
- version — this coupling test proves the pipeline is wired up correctly
114
+ version — this coupling test proves the import chain is wired up correctly
112
115
 
113
116
  ### Guidelines
114
117
 
@@ -6,8 +6,10 @@
6
6
  //
7
7
  // Builds the docs/ directory for web serving:
8
8
  // 1. Creates docs/ and copies src/web/* into it
9
- // 2. Generates docs/lib-meta.js with package metadata exports
10
- // 3. Generates docs/lib-browser.js from src/lib/main.js (if it has named exports)
9
+ // 2. Generates docs/lib.js a self-contained browser module with:
10
+ // - Package identity (name, version, description) from package.json
11
+ // - Browser-safe exported functions extracted from src/lib/main.js
12
+ // This overwrites the src/web/lib.js re-export (which only works in dev).
11
13
  //
12
14
  // This file is part of the Example Suite for `agentic-lib` see: https://github.com/xn-intenton-z2a/agentic-lib
13
15
  // This file is licensed under the MIT License. For details, see LICENSE-MIT
@@ -34,64 +36,63 @@ if (fs.existsSync(webDir)) {
34
36
  }
35
37
  }
36
38
 
37
- // Step 2: Generate docs/lib-meta.js with package metadata
39
+ // Step 2: Generate docs/lib.js self-contained browser module
40
+ // This overwrites the re-export copied from src/web/lib.js above,
41
+ // replacing it with a standalone module that works on GitHub Pages
42
+ // (where src/lib/main.js and package.json are not available).
38
43
  const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
39
- const metaLines = ["name", "version", "description"].map(
40
- (k) => `export const ${k} = ${JSON.stringify(pkg[k])};`
44
+ const sections = [];
45
+
46
+ // Identity exports from package.json
47
+ sections.push(
48
+ `export const name = ${JSON.stringify(pkg.name)};`,
49
+ `export const version = ${JSON.stringify(pkg.version)};`,
50
+ `export const description = ${JSON.stringify(pkg.description)};`,
51
+ "",
52
+ "export function getIdentity() {",
53
+ " return { name, version, description };",
54
+ "}"
41
55
  );
42
- fs.writeFileSync(path.join(docsDir, "lib-meta.js"), metaLines.join("\n") + "\n");
43
- console.log("Wrote docs/lib-meta.js");
44
56
 
45
- // Step 3: Generate docs/lib-browser.js from src/lib/main.js named exports
46
- // Parse the main module for `export function` and `export async function` declarations
47
- // and re-emit them as a standalone browser-compatible module.
57
+ // Extract browser-safe exported functions from src/lib/main.js
48
58
  const mainPath = path.resolve("src", "lib", "main.js");
49
59
  if (fs.existsSync(mainPath)) {
50
60
  const mainSrc = fs.readFileSync(mainPath, "utf8");
51
- // Match named export functions: export function foo(...) { ... } or export async function foo(...)
52
- const exportRegex = /^export\s+(async\s+)?function\s+(\w+)/gm;
53
- const exportNames = [];
54
- let match;
55
- while ((match = exportRegex.exec(mainSrc)) !== null) {
56
- exportNames.push(match[2]);
57
- }
61
+ const lines = mainSrc.split("\n");
58
62
 
59
- if (exportNames.length > 0) {
60
- // Extract the function bodies by finding each export function and copying it verbatim
61
- const lines = mainSrc.split("\n");
62
- const functionBodies = [];
63
- for (let i = 0; i < lines.length; i++) {
64
- if (/^export\s+(async\s+)?function\s+\w+/.test(lines[i])) {
65
- // Collect lines until we find the closing brace at column 0
66
- const fnLines = [lines[i]];
67
- let braceDepth = 0;
68
- for (let j = i; j < lines.length; j++) {
69
- for (const ch of lines[j]) {
70
- if (ch === "{") braceDepth++;
71
- if (ch === "}") braceDepth--;
72
- }
73
- if (j > i) fnLines.push(lines[j]);
74
- if (braceDepth === 0 && j >= i) {
75
- break;
76
- }
63
+ // Skip identity-related exports and the main/CLI function — only extract
64
+ // mission-specific exported functions (e.g. hammingDistance, etc.)
65
+ const SKIP_FUNCTIONS = new Set(["getIdentity", "main"]);
66
+
67
+ for (let i = 0; i < lines.length; i++) {
68
+ const fnMatch = lines[i].match(/^export\s+(async\s+)?function\s+(\w+)/);
69
+ if (fnMatch && !SKIP_FUNCTIONS.has(fnMatch[2])) {
70
+ // Collect the full function body by tracking brace depth
71
+ const fnLines = [lines[i]];
72
+ let braceDepth = 0;
73
+ for (let j = i; j < lines.length; j++) {
74
+ for (const ch of lines[j]) {
75
+ if (ch === "{") braceDepth++;
76
+ if (ch === "}") braceDepth--;
77
+ }
78
+ if (j > i) fnLines.push(lines[j]);
79
+ if (braceDepth === 0 && j >= i) {
80
+ break;
77
81
  }
78
- functionBodies.push(fnLines.join("\n"));
79
82
  }
83
+ sections.push("");
84
+ sections.push(fnLines.join("\n"));
80
85
  }
81
-
82
- const header = [
83
- "// docs/lib-browser.js",
84
- "// Generated browser-compatible module exposing named exports from src/lib/main.js.",
85
- "// Auto-generated by build-web.cjs — do not edit manually.",
86
- "",
87
- ].join("\n");
88
-
89
- fs.writeFileSync(
90
- path.join(docsDir, "lib-browser.js"),
91
- header + functionBodies.join("\n\n") + "\n"
92
- );
93
- console.log(
94
- `Wrote docs/lib-browser.js (${exportNames.length} exports: ${exportNames.join(", ")})`
95
- );
96
86
  }
97
87
  }
88
+
89
+ const header = [
90
+ "// docs/lib.js",
91
+ "// Self-contained browser module generated by build-web.cjs.",
92
+ "// Identity from package.json; functions extracted from src/lib/main.js.",
93
+ "// Do not edit — regenerate with: npm run build:web",
94
+ "",
95
+ ].join("\n");
96
+
97
+ fs.writeFileSync(path.join(docsDir, "lib.js"), header + sections.join("\n") + "\n");
98
+ console.log("Wrote docs/lib.js");
@@ -49,33 +49,35 @@ The pipeline runs as GitHub Actions workflows. An LLM supervisor gathers reposit
49
49
  ## File Layout
50
50
 
51
51
  ```
52
- src/lib/main.js ← library (Node entry point: identity + mission functions)
53
- src/web/index.html ← web page (browser: imports lib-meta.js, demonstrates library)
52
+ src/lib/main.js ← library (browser-safe: identity + mission functions)
53
+ src/web/index.html ← web page (imports ./lib.js)
54
+ src/web/lib.js ← browser entry point (re-exports from ../lib/main.js)
54
55
  tests/unit/main.test.js ← unit tests (import main.js directly, test API-level detail)
55
56
  tests/unit/web.test.js ← web structure tests (read index.html as text, verify wiring)
56
- tests/behaviour/ ← Playwright E2E (run page in browser, import main.js for coupling)
57
+ tests/behaviour/ ← Playwright E2E (serve from project root, import main.js for coupling)
57
58
  docs/ ← build output (generated by npm run build:web)
58
- docs/lib-meta.js ← generated: exports name, version, description from package.json
59
+ docs/lib.js ← generated: self-contained module for GitHub Pages
59
60
  ```
60
61
 
61
- These files form a **coupled unit**. Changes to the library must flow through to the web page, and tests verify this coupling:
62
+ These files form a **coupled unit**. The library works in both Node and the browser:
62
63
 
63
- - `src/lib/main.js` exports `getIdentity()` returns `{ name, version, description }` from `package.json`
64
- - `npm run build:web` copies `src/web/*` to `docs/` and generates `docs/lib-meta.js` from `package.json`
65
- - `src/web/index.html` imports `lib-meta.js` at runtime → displays library identity on the page
64
+ - `src/lib/main.js` is browser-safe — in Node it reads `package.json` via `createRequire`, in the browser via `fetch`
65
+ - `src/web/lib.js` re-exports from `../lib/main.js` the page imports the **real library**, not a generated copy
66
+ - `src/web/index.html` imports `./lib.js` → displays library identity on the page
66
67
  - The behaviour test imports `getIdentity()` from `main.js` AND reads `#lib-version` from the rendered page → asserts they match
68
+ - `npm run build:web` generates `docs/lib.js` as a self-contained module for production (GitHub Pages)
67
69
 
68
- This coupling test proves the web page is consuming the real library via the build pipeline. Mission-specific functions should follow the same path — never duplicate library logic inline in the web page.
70
+ This coupling proves the web page consumes the real library. Mission-specific functions should follow the same path — never duplicate library logic inline in the web page.
69
71
 
70
72
  ## Test Strategy
71
73
 
72
74
  | Test layer | What it tests | How it binds |
73
75
  |------------|--------------|--------------|
74
76
  | **Unit tests** (`tests/unit/main.test.js`) | Library API: return values, error types, edge cases | Imports directly from `src/lib/main.js` |
75
- | **Web structure tests** (`tests/unit/web.test.js`) | HTML structure: expected elements, `lib-meta.js` import | Reads `src/web/index.html` as text |
76
- | **Behaviour tests** (`tests/behaviour/`) | End-to-end: page renders, interactive elements work | Playwright loads the built site; coupling test imports `getIdentity()` from `main.js` and asserts the page displays the same version |
77
+ | **Web structure tests** (`tests/unit/web.test.js`) | HTML structure: expected elements, `lib.js` re-export | Reads `src/web/index.html` and `src/web/lib.js` as text |
78
+ | **Behaviour tests** (`tests/behaviour/`) | End-to-end: page renders, displays real library values | Playwright serves from project root; coupling test imports `getIdentity()` from `main.js` and asserts the page displays the same version |
77
79
 
78
- The **coupling test** in the behaviour test is the key invariant: it proves the web page displays values from the actual library, not hardcoded or duplicated values. If the build pipeline breaks, this test fails.
80
+ The **coupling test** in the behaviour test is the key invariant: it proves the web page displays values from the actual library, not hardcoded or duplicated values. The page imports `lib.js` which re-exports from `main.js` which reads `package.json` — the same chain the unit tests exercise.
79
81
 
80
82
  ## Configuration
81
83
 
@@ -33,16 +33,16 @@
33
33
  <p>This website uses the library. See <a href="https://github.com/xn-intenton-z2a/repository0">the repository</a> for source code and mission details.</p>
34
34
  <script type="module">
35
35
  try {
36
- const { name, version, description } = await import('./lib-meta.js');
36
+ const { name, version, description } = await import('./lib.js');
37
37
  document.getElementById('lib-name').textContent = name;
38
38
  document.getElementById('lib-version').textContent = version;
39
39
  document.getElementById('lib-description').textContent = description || '(no description)';
40
40
  document.getElementById('demo-output').textContent = JSON.stringify({ name, version, description }, null, 2);
41
41
  document.title = name + ' v' + version;
42
42
  } catch (e) {
43
- document.getElementById('lib-version').textContent = '(build required)';
44
- document.getElementById('lib-description').textContent = 'Run npm run build:web to generate library metadata';
45
- document.getElementById('demo-output').textContent = 'Library not yet built. Run: npm run build:web';
43
+ document.getElementById('lib-version').textContent = '(error)';
44
+ document.getElementById('lib-description').textContent = e.message;
45
+ document.getElementById('demo-output').textContent = 'Failed to load library: ' + e.message;
46
46
  }
47
47
  </script>
48
48
  </body>
@@ -0,0 +1,4 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2025-2026 Polycode Limited
3
+ // src/web/lib.js — Browser entry point, re-exports from the library.
4
+ export { name, version, description, getIdentity } from "../lib/main.js";
@@ -3,11 +3,21 @@
3
3
  // Copyright (C) 2025-2026 Polycode Limited
4
4
  // src/lib/main.js
5
5
 
6
- import { createRequire } from "module";
7
- import { fileURLToPath } from "url";
6
+ const isNode = typeof process !== "undefined" && !!process.versions?.node;
8
7
 
9
- const require = createRequire(import.meta.url);
10
- const pkg = require("../../package.json");
8
+ let pkg;
9
+ if (isNode) {
10
+ const { createRequire } = await import("module");
11
+ const requireFn = createRequire(import.meta.url);
12
+ pkg = requireFn("../../package.json");
13
+ } else {
14
+ try {
15
+ const resp = await fetch(new URL("../../package.json", import.meta.url));
16
+ pkg = await resp.json();
17
+ } catch {
18
+ pkg = { name: document.title, version: "0.0.0", description: "" };
19
+ }
20
+ }
11
21
 
12
22
  export const name = pkg.name;
13
23
  export const version = pkg.version;
@@ -29,7 +39,10 @@ export function main(args) {
29
39
  console.log(`${name}@${version}`);
30
40
  }
31
41
 
32
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
33
- const args = process.argv.slice(2);
34
- main(args);
42
+ if (isNode) {
43
+ const { fileURLToPath } = await import("url");
44
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
45
+ const args = process.argv.slice(2);
46
+ main(args);
47
+ }
35
48
  }
@@ -9,7 +9,7 @@
9
9
  "build:web": "node .github/agentic-lib/scripts/build-web.cjs",
10
10
  "test": "vitest --run tests/unit/*.test.js",
11
11
  "test:unit": "vitest --run --coverage tests/unit/*.test.js",
12
- "test:behaviour": "npm run build:web && npx playwright test --config playwright.config.js",
12
+ "test:behaviour": "npx playwright test --config playwright.config.js",
13
13
  "start": "npm run build:web && npx serve docs",
14
14
  "start:cli": "node src/lib/main.js"
15
15
  },
@@ -17,7 +17,7 @@
17
17
  "author": "",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@xn-intenton-z2a/agentic-lib": "^7.2.11"
20
+ "@xn-intenton-z2a/agentic-lib": "^7.2.13"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@playwright/test": "^1.58.0",
@@ -7,10 +7,10 @@ export default defineConfig({
7
7
  timeout: 30000,
8
8
  retries: 2,
9
9
  use: {
10
- baseURL: "http://localhost:3000",
10
+ baseURL: "http://localhost:3000/src/web/",
11
11
  },
12
12
  webServer: {
13
- command: "npx serve docs -l 3000",
13
+ command: "npx serve . -l 3000",
14
14
  port: 3000,
15
15
  reuseExistingServer: true,
16
16
  },
@@ -15,9 +15,15 @@ describe("Website", () => {
15
15
  expect(html).toContain("</html>");
16
16
  });
17
17
 
18
- test("index.html imports the library via lib-meta.js", () => {
18
+ test("index.html imports the library via lib.js", () => {
19
19
  const html = readFileSync("src/web/index.html", "utf8");
20
- expect(html).toContain("lib-meta.js");
20
+ expect(html).toContain("lib.js");
21
+ });
22
+
23
+ test("src/web/lib.js re-exports from the library", () => {
24
+ expect(existsSync("src/web/lib.js")).toBe(true);
25
+ const lib = readFileSync("src/web/lib.js", "utf8");
26
+ expect(lib).toContain("../lib/main.js");
21
27
  });
22
28
 
23
29
  test("index.html displays library identity elements", () => {