@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.
- package/.github/workflows/agentic-lib-init.yml +31 -66
- package/.github/workflows/agentic-lib-update.yml +100 -0
- package/bin/agentic-lib.js +1 -0
- package/package.json +1 -1
- package/src/actions/agentic-step/tasks/transform.js +1 -1
- package/src/agents/agent-apply-fix.md +3 -3
- package/src/agents/agent-issue-resolution.md +14 -11
- package/src/scripts/build-web.cjs +52 -51
- package/src/seeds/zero-README.md +14 -12
- package/src/seeds/zero-index.html +4 -4
- package/src/seeds/zero-lib.js +4 -0
- package/src/seeds/zero-main.js +20 -7
- package/src/seeds/zero-package.json +2 -2
- package/src/seeds/zero-playwright.config.js +2 -2
- package/src/seeds/zero-web.test.js +8 -2
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
# Copyright (C) 2025-2026 Polycode Limited
|
|
3
3
|
# .github/workflows/agentic-lib-init.yml
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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:
|
|
129
|
+
needs: update
|
|
130
|
+
if: inputs.mode == 'reseed' || inputs.mode == 'purge'
|
|
155
131
|
runs-on: ubuntu-latest
|
|
156
132
|
env:
|
|
157
|
-
INIT_MODE: ${{
|
|
158
|
-
|
|
159
|
-
|
|
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:
|
|
138
|
+
if: inputs.dry-run == 'true' || inputs.dry-run == true
|
|
164
139
|
- uses: actions/checkout@v6
|
|
165
|
-
if:
|
|
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' &&
|
|
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:
|
|
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
|
-
|
|
198
|
-
|
|
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 != '' &&
|
|
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' &&
|
|
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 = '${{
|
|
230
|
-
const 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' &&
|
|
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 = '${{
|
|
265
|
-
const model = '${{
|
|
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' &&
|
|
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' &&
|
|
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
|
package/bin/agentic-lib.js
CHANGED
|
@@ -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
|
@@ -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
|
-
"
|
|
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
|
|
22
|
-
**NEVER duplicate library functions inline in the web page** —
|
|
23
|
-
|
|
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
|
|
83
|
-
- `
|
|
84
|
-
- `src/web/index.html` imports
|
|
85
|
-
- The website
|
|
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
|
|
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
|
|
98
|
-
|
|
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
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
|
10
|
-
//
|
|
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
|
|
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
|
|
40
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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");
|
package/src/seeds/zero-README.md
CHANGED
|
@@ -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 (
|
|
53
|
-
src/web/index.html ← web page (
|
|
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 (
|
|
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
|
|
59
|
+
docs/lib.js ← generated: self-contained module for GitHub Pages
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
These files form a **coupled unit**.
|
|
62
|
+
These files form a **coupled unit**. The library works in both Node and the browser:
|
|
62
63
|
|
|
63
|
-
- `src/lib/main.js`
|
|
64
|
-
- `
|
|
65
|
-
- `src/web/index.html` imports
|
|
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
|
|
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
|
|
76
|
-
| **Behaviour tests** (`tests/behaviour/`) | End-to-end: page renders,
|
|
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.
|
|
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
|
|
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 = '(
|
|
44
|
-
document.getElementById('lib-description').textContent =
|
|
45
|
-
document.getElementById('demo-output').textContent = '
|
|
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>
|
package/src/seeds/zero-main.js
CHANGED
|
@@ -3,11 +3,21 @@
|
|
|
3
3
|
// Copyright (C) 2025-2026 Polycode Limited
|
|
4
4
|
// src/lib/main.js
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
import { fileURLToPath } from "url";
|
|
6
|
+
const isNode = typeof process !== "undefined" && !!process.versions?.node;
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
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 (
|
|
33
|
-
const
|
|
34
|
-
|
|
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": "
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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", () => {
|