geneseed 2.0.0
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/CHANGELOG.md +938 -0
- package/DESIGN.md +1423 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +48 -0
- package/README.md +336 -0
- package/SETUP.md +1027 -0
- package/SHIPPED.md +70 -0
- package/adapters/bob/README.md +100 -0
- package/adapters/claude-code/README.md +101 -0
- package/adapters/claude-code/settings.json +47 -0
- package/adapters/claude-code/skills/geneseed/SKILL.md +72 -0
- package/adapters/copilot/README.md +76 -0
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +434 -0
- package/adapters/opencode/HOW-OPENCODE-LOADS.md +158 -0
- package/adapters/opencode/README.md +582 -0
- package/adapters/opencode/agents/orchestrator.md +25 -0
- package/adapters/opencode/opencode.json +44 -0
- package/adapters/opencode/plugins/geneseed-activity.js +430 -0
- package/adapters/opencode/plugins/geneseed-context.js +876 -0
- package/adapters/opencode/plugins/geneseed-guard.js +290 -0
- package/adapters/opencode/plugins/geneseed-learn.js +396 -0
- package/adapters/opencode/plugins/geneseed-notify.js +142 -0
- package/adapters/opencode/plugins/geneseed-ponytail.js +148 -0
- package/adapters/opencode/plugins/geneseed-workflow.js +183 -0
- package/adapters/opencode/workflows/_runtime.js +277 -0
- package/adapters/opencode/workflows/council.js +64 -0
- package/adapters/opencode/workflows/dispatch.js +89 -0
- package/adapters/opencode/workflows/research-plan-implement.js +43 -0
- package/adapters/opencode/workflows/review.js +57 -0
- package/bin/geneseed-cli.mjs +412 -0
- package/bin/geneseed-hook.mjs +129 -0
- package/bin/geneseed.mjs +1086 -0
- package/docs/declined.md +149 -0
- package/docs/limits.md +270 -0
- package/docs/opencode-plugin-setup.md +64 -0
- package/docs/token-footprint.md +68 -0
- package/docs/web/_groups.json +45 -0
- package/docs/web/about.md +6 -0
- package/docs/web/adapters-claude-code.md +8 -0
- package/docs/web/adapters-opencode-loads.md +8 -0
- package/docs/web/adapters-opencode-spec.md +8 -0
- package/docs/web/adapters-opencode.md +8 -0
- package/docs/web/agents.md +8 -0
- package/docs/web/authoring.md +11 -0
- package/docs/web/autostart.md +9 -0
- package/docs/web/claude-hooks.md +36 -0
- package/docs/web/cli.md +6 -0
- package/docs/web/collaboration.md +27 -0
- package/docs/web/configure-wiki.md +9 -0
- package/docs/web/design.md +7 -0
- package/docs/web/env-knobs.md +9 -0
- package/docs/web/exclusions.md +26 -0
- package/docs/web/first-session.md +28 -0
- package/docs/web/footprint.md +31 -0
- package/docs/web/foreman-mode.md +33 -0
- package/docs/web/glossary.md +6 -0
- package/docs/web/harnesses.md +28 -0
- package/docs/web/headless.md +9 -0
- package/docs/web/ingest-docs.md +9 -0
- package/docs/web/install-paths.md +9 -0
- package/docs/web/install-quick.md +35 -0
- package/docs/web/lsp-overview.md +37 -0
- package/docs/web/mcp-claude-code.md +10 -0
- package/docs/web/mcp-filesystem.md +9 -0
- package/docs/web/mcp-gitlab.md +9 -0
- package/docs/web/mcp-markitdown.md +9 -0
- package/docs/web/mcp-overview.md +34 -0
- package/docs/web/mcp-trouble.md +9 -0
- package/docs/web/mcp-verify.md +9 -0
- package/docs/web/memory.md +7 -0
- package/docs/web/migrate.md +39 -0
- package/docs/web/model.md +20 -0
- package/docs/web/notebook.md +7 -0
- package/docs/web/pipelines.md +24 -0
- package/docs/web/plugin-activity.md +23 -0
- package/docs/web/plugin-context.md +26 -0
- package/docs/web/plugin-guard.md +24 -0
- package/docs/web/plugin-learn.md +27 -0
- package/docs/web/plugin-notify.md +24 -0
- package/docs/web/plugin-ponytail.md +23 -0
- package/docs/web/plugin-setup.md +7 -0
- package/docs/web/plugin-workflow.md +24 -0
- package/docs/web/plugins.md +17 -0
- package/docs/web/project-context.md +9 -0
- package/docs/web/rules.md +8 -0
- package/docs/web/run-anywhere.md +9 -0
- package/docs/web/self-improve.md +8 -0
- package/docs/web/setup-choices.md +39 -0
- package/docs/web/skills.md +8 -0
- package/docs/web/themes-author.md +8 -0
- package/docs/web/themes.md +9 -0
- package/docs/web/token-footprint.md +7 -0
- package/docs/web/trouble.md +38 -0
- package/docs/web/upgrade.md +9 -0
- package/docs/web/verify.md +30 -0
- package/docs/web/wiki.md +7 -0
- package/docs/web/worktree.md +10 -0
- package/docs/web-ui.md +144 -0
- package/docs/wiki.md +43 -0
- package/geneseed +39 -0
- package/geneseed.cmd +34 -0
- package/harness.config.json +6 -0
- package/js/anim.mjs +326 -0
- package/js/catalog.mjs +112 -0
- package/js/checkout.mjs +88 -0
- package/js/cli-table.json +1034 -0
- package/js/cli.mjs +498 -0
- package/js/diff.mjs +384 -0
- package/js/doctor.mjs +1364 -0
- package/js/emit.mjs +1373 -0
- package/js/excludes.mjs +271 -0
- package/js/generate.mjs +379 -0
- package/js/hooks.mjs +1026 -0
- package/js/hosts.mjs +253 -0
- package/js/installs.mjs +378 -0
- package/js/inventory.mjs +175 -0
- package/js/lib/fs.mjs +913 -0
- package/js/lib/proc.mjs +28 -0
- package/js/lib/udiff.mjs +275 -0
- package/js/link.mjs +286 -0
- package/js/mcp.mjs +335 -0
- package/js/memory.mjs +108 -0
- package/js/menu.mjs +118 -0
- package/js/migrate.mjs +251 -0
- package/js/native.mjs +426 -0
- package/js/opencode.mjs +325 -0
- package/js/registry.mjs +110 -0
- package/js/render.mjs +282 -0
- package/js/settings.mjs +924 -0
- package/js/setup.mjs +492 -0
- package/js/status.mjs +281 -0
- package/js/themes.mjs +195 -0
- package/js/tui.mjs +444 -0
- package/js/uninstall.mjs +1137 -0
- package/js/update.mjs +910 -0
- package/js/web/actions.mjs +868 -0
- package/js/web/activity.mjs +284 -0
- package/js/web/api.mjs +873 -0
- package/js/web/docs.mjs +531 -0
- package/js/web/graph.mjs +0 -0
- package/js/web/jobs.mjs +480 -0
- package/js/web/server.mjs +1135 -0
- package/package.json +54 -0
- package/registry.json +474 -0
- package/src/AGENT.md.tmpl +390 -0
- package/src/agents/_template.md +76 -0
- package/src/agents/advocate.md +36 -0
- package/src/agents/architect.md +55 -0
- package/src/agents/developer.md +66 -0
- package/src/agents/docs.md +54 -0
- package/src/agents/empiricist.md +39 -0
- package/src/agents/explorer.md +54 -0
- package/src/agents/framer.md +36 -0
- package/src/agents/historian.md +51 -0
- package/src/agents/operator.md +37 -0
- package/src/agents/pragmatist.md +37 -0
- package/src/agents/reviewer.md +52 -0
- package/src/agents/security.md +57 -0
- package/src/agents/skeptic.md +48 -0
- package/src/agents/steward.md +36 -0
- package/src/agents/tester.md +54 -0
- package/src/agents/user-advocate.md +36 -0
- package/src/agents/visionary.md +36 -0
- package/src/laws/universal.md +387 -0
- package/src/memory/README.md +109 -0
- package/src/memory/gitignore +6 -0
- package/src/modes/README.md +26 -0
- package/src/modes/direct.md +2 -0
- package/src/modes/foreman.md +19 -0
- package/src/notebook/README.md +39 -0
- package/src/notebook/gitignore +7 -0
- package/src/postures/README.md +31 -0
- package/src/postures/artisan.md +14 -0
- package/src/postures/assistant.md +12 -0
- package/src/postures/expert.md +11 -0
- package/src/postures/mentor.md +11 -0
- package/src/postures/peer.md +12 -0
- package/src/skills/_template.md +47 -0
- package/src/skills/brainstorm.md +28 -0
- package/src/skills/clarify.md +28 -0
- package/src/skills/codebase-design.md +44 -0
- package/src/skills/commit.md +48 -0
- package/src/skills/council.md +34 -0
- package/src/skills/crash-course.md +28 -0
- package/src/skills/daydream/.claude-plugin/plugin.json +9 -0
- package/src/skills/daydream/SKILL.md +74 -0
- package/src/skills/daydream/VENDOR.md +25 -0
- package/src/skills/daydream/critic-prompt.md +38 -0
- package/src/skills/daydream/instructions.md +422 -0
- package/src/skills/daydream/synthesizer-prompt.md +41 -0
- package/src/skills/debug.md +37 -0
- package/src/skills/decode.md +28 -0
- package/src/skills/develop.md +29 -0
- package/src/skills/document-project.md +79 -0
- package/src/skills/domain-modeling.md +38 -0
- package/src/skills/drill.md +28 -0
- package/src/skills/feynman.md +26 -0
- package/src/skills/forge-mcp.md +48 -0
- package/src/skills/fresh-eyes.md +29 -0
- package/src/skills/frontend-design.md +58 -0
- package/src/skills/gap-detector.md +27 -0
- package/src/skills/geneseed-code-review.md +33 -0
- package/src/skills/geneseed.md +29 -0
- package/src/skills/git-archaeology.md +47 -0
- package/src/skills/git-rescue.md +68 -0
- package/src/skills/handoff.md +29 -0
- package/src/skills/herdr.md +164 -0
- package/src/skills/ingest.md +49 -0
- package/src/skills/learning-path.md +26 -0
- package/src/skills/migrate.md +45 -0
- package/src/skills/opencode-theme.md +52 -0
- package/src/skills/parallel-agents.md +28 -0
- package/src/skills/pipeline.md +52 -0
- package/src/skills/plan.md +45 -0
- package/src/skills/ponytail.md +29 -0
- package/src/skills/profile.md +58 -0
- package/src/skills/prose.md +42 -0
- package/src/skills/prototype.md +44 -0
- package/src/skills/react-view-transitions/AGENTS.md +955 -0
- package/src/skills/react-view-transitions/README.md +42 -0
- package/src/skills/react-view-transitions/SKILL.md +320 -0
- package/src/skills/react-view-transitions/VENDOR.md +16 -0
- package/src/skills/react-view-transitions/metadata.json +12 -0
- package/src/skills/react-view-transitions/references/css-recipes.md +242 -0
- package/src/skills/react-view-transitions/references/implementation.md +182 -0
- package/src/skills/react-view-transitions/references/nextjs.md +176 -0
- package/src/skills/react-view-transitions/references/patterns.md +262 -0
- package/src/skills/refactor.md +32 -0
- package/src/skills/release.md +43 -0
- package/src/skills/repo-map.md +36 -0
- package/src/skills/research.md +42 -0
- package/src/skills/review-response.md +36 -0
- package/src/skills/roast-me.md +28 -0
- package/src/skills/rule.md +100 -0
- package/src/skills/ship.md +49 -0
- package/src/skills/tdd.md +31 -0
- package/src/skills/tickets.md +44 -0
- package/src/skills/token-report/SKILL.md +76 -0
- package/src/skills/token-report/VENDOR.md +23 -0
- package/src/skills/token-report/scripts/token_report.mjs +1301 -0
- package/src/skills/wayfinder.md +49 -0
- package/src/skills/wiki.md +68 -0
- package/src/skills/workflow.md +38 -0
- package/themes/README.md +69 -0
- package/themes/_TEMPLATE.json +147 -0
- package/themes/biker.json +147 -0
- package/themes/commentator.json +147 -0
- package/themes/cyberpunk.json +147 -0
- package/themes/gamer.json +147 -0
- package/themes/imperial.json +147 -0
- package/themes/joker.json +147 -0
- package/themes/marvin.json +147 -0
- package/themes/mean.json +147 -0
- package/themes/military.json +147 -0
- package/themes/neutral.json +147 -0
- package/themes/opencode/README.md +58 -0
- package/themes/opencode/catppuccin.json +27 -0
- package/themes/opencode/dracula.json +27 -0
- package/themes/opencode/everforest.json +27 -0
- package/themes/opencode/gruvbox.json +27 -0
- package/themes/opencode/kanagawa.json +27 -0
- package/themes/opencode/neon.json +27 -0
- package/themes/opencode/nord.json +27 -0
- package/themes/opencode/rosepine.json +27 -0
- package/themes/opencode/tokyonight.json +27 -0
- package/themes/pirate.json +147 -0
- package/themes/sports.json +147 -0
- package/themes/verstappen.json +147 -0
- package/themes/wizard.json +147 -0
- package/web/dist/assets/About-DX1-VTBP.js +1 -0
- package/web/dist/assets/Activity-Dp9NaXOn.js +1 -0
- package/web/dist/assets/ActivityDetail-HpOBtrXS.js +1 -0
- package/web/dist/assets/Diff-Ct4G3Se8.js +5 -0
- package/web/dist/assets/Doctor-DGjkZFuA.js +1 -0
- package/web/dist/assets/Graph-b8o-S6kv.js +1 -0
- package/web/dist/assets/Harnesses-gr3-aHBF.js +1 -0
- package/web/dist/assets/Laws-DTwF5tTC.js +1 -0
- package/web/dist/assets/Library-BEZscyaQ.js +1 -0
- package/web/dist/assets/Markdown-k_LSLBGq.js +1 -0
- package/web/dist/assets/Profile-DydukTxP.js +1 -0
- package/web/dist/assets/Rules-CIRG4Ldt.js +1 -0
- package/web/dist/assets/Skills-Buoi1IBr.js +1 -0
- package/web/dist/assets/StatusBadge-CE_AJttl.js +1 -0
- package/web/dist/assets/Themes-CI4o3gJC.js +2 -0
- package/web/dist/assets/activity-BCW7e6Cy.js +1 -0
- package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-400-italic-Bserb8X5.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-400-normal-BG6hkEXj.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-500-italic-BGfwwhek.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-500-normal-DrDcrrxK.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-600-normal-CIXX6EOa.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-700-normal-CeQ8H3UY.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
- package/web/dist/assets/index-B1PIx7RM.js +1 -0
- package/web/dist/assets/index-BnTQUSUz.js +12 -0
- package/web/dist/assets/index-Ck2iCdaC.js +2 -0
- package/web/dist/assets/index-DUCsDv7z.css +1 -0
- package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/web/dist/assets/marked.esm-JKvyS0Pp.js +46 -0
- package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
- package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
- package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
- package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/web/dist/assets/react-DOpiTn5v.js +32 -0
- package/web/dist/assets/roman-DkYN0LX9.js +1 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-400-italic-YylcN9Ay.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-400-normal-Rg4St2Dn.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-700-italic-vNvENeTh.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-700-normal-mWgeinG7.woff2 +0 -0
- package/web/dist/icon-maskable.svg +10 -0
- package/web/dist/icon.svg +8 -0
- package/web/dist/index.html +21 -0
- package/web/dist/manifest.webmanifest +14 -0
- package/web/dist/sw.js +40 -0
- package/web/src/pages/Laws.jsx +230 -0
package/js/render.mjs
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generator's render core, in Node — the first half of the port.
|
|
3
|
+
*
|
|
4
|
+
* This is a faithful translation of `_build_render.py`'s pure text pipeline: load a
|
|
5
|
+
* theme, lay the fixed neutral STRUCTURE over it, inline INCLUDE directives, resolve
|
|
6
|
+
* AGENT.md's CATALOG blocks, substitute {{TOKENS}}, and hand back every source file with
|
|
7
|
+
* its themed output path. It writes nothing and reads nothing but `src/` and `themes/`.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS HALF AND NOT MORE. The emits split into five stages —
|
|
10
|
+
* RENDER* -> WIRE* -> PRUNE -> MANIFEST -> VERIFY (see `_build_global._emit_claude_core`
|
|
11
|
+
* and tests/test_emit_phase_order.py). RENDER produces files Geneseed owns wholesale;
|
|
12
|
+
* WIRE reconciles files the user co-owns (settings.json, opencode.json, the CLAUDE.md
|
|
13
|
+
* managed block) and stays in Python for now, in `_build_settings.py`. Node takes RENDER
|
|
14
|
+
* because it is the only stage whose output is a pure function of the source tree.
|
|
15
|
+
*
|
|
16
|
+
* PARITY IS THE CONTRACT, NOT THE INTENT. Every function here has a named Python
|
|
17
|
+
* counterpart and must produce byte-identical output for it; tests/test_render_parity.py
|
|
18
|
+
* drives both over every theme x footprint x posture x mode and byte-compares the
|
|
19
|
+
* written trees. Where Python and JS semantics differ silently — path sort order,
|
|
20
|
+
* `.suffix`, `splitlines`, universal newlines — the difference is spelled out at the
|
|
21
|
+
* call site rather than left to the reader.
|
|
22
|
+
*
|
|
23
|
+
* NO MODULE-LEVEL MUTABLE CONFIG. `_build_core` is the single owner of the source/theme
|
|
24
|
+
* roots and the posture/mode selection precisely because copies of those names made
|
|
25
|
+
* redirects half-work. The JS side keeps that property structurally: the four values
|
|
26
|
+
* travel in one explicit `cfg` object, so there is nothing to copy and nothing to
|
|
27
|
+
* redirect.
|
|
28
|
+
*/
|
|
29
|
+
import { readdirSync, statSync, existsSync } from 'node:fs';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { readText, parseJson, normcase, comparePaths } from './lib/fs.mjs';
|
|
32
|
+
|
|
33
|
+
/** Document STRUCTURE is theme-INDEPENDENT — mirrors `_build_render.STRUCTURE`. */
|
|
34
|
+
export const STRUCTURE = {
|
|
35
|
+
HARNESS: 'Geneseed', CHARTER: 'Charter', CONTEXT: 'Context',
|
|
36
|
+
SCRIPT: 'Script', SCRIPTS: 'Scripts',
|
|
37
|
+
DIR_LAWS: 'laws', DIR_AGENTS: 'agents', DIR_SKILLS: 'skills', DIR_MEMORY: 'memory',
|
|
38
|
+
DIR_NOTEBOOK: 'notebook',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Source top-level dirs whose OUTPUT name is themed. Mirrors `SRC_DIR_TOKENS`.
|
|
43
|
+
* Exported because `js/emit.mjs`'s `build` resolves the same tokens to decide which
|
|
44
|
+
* dirs it owns and wipes — one owner, so the two cannot disagree about a folder name.
|
|
45
|
+
*/
|
|
46
|
+
export const SRC_DIR_TOKENS = {
|
|
47
|
+
laws: 'DIR_LAWS', agents: 'DIR_AGENTS', skills: 'DIR_SKILLS',
|
|
48
|
+
memory: 'DIR_MEMORY', notebook: 'DIR_NOTEBOOK',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const TEXT_SUFFIXES = new Set(['.md', '.tmpl', '.json', '.txt', '.yml', '.yaml']);
|
|
52
|
+
|
|
53
|
+
const TOKEN_RE = /\{\{([A-Z_]+)\}\}/g;
|
|
54
|
+
const INCLUDE_RE = /^[ \t]*<!--[ \t]*INCLUDE:[ \t]*(?<path>[^ \t]+)[ \t]*-->[ \t]*$/gm;
|
|
55
|
+
const CATALOG_BLOCK_RE =
|
|
56
|
+
/[ \t]*<!-- CATALOG:begin -->\n(?<table>[\s\S]*?)[ \t]*<!-- CATALOG:else -->\n(?<pointer>[\s\S]*?)[ \t]*<!-- CATALOG:end -->\n/g;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `PurePath.suffix`. Not `path.extname`: Python returns '' for a name whose only dot is
|
|
60
|
+
* leading (`.gitignore`) or trailing (`foo.`), where `extname` returns '' and '.'
|
|
61
|
+
* respectively. Neither spelling is in TEXT_SUFFIXES, so the divergence is invisible
|
|
62
|
+
* today — which is exactly why it is written out rather than relied upon.
|
|
63
|
+
*/
|
|
64
|
+
function suffixOf(name) {
|
|
65
|
+
const i = name.lastIndexOf('.');
|
|
66
|
+
return 0 < i && i < name.length - 1 ? name.slice(i) : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `Path.rglob("*")` restricted to files, then `sorted()`. */
|
|
70
|
+
function sortedSourceFiles(src) {
|
|
71
|
+
const out = [];
|
|
72
|
+
(function walk(dir) {
|
|
73
|
+
for (const name of readdirSync(dir)) {
|
|
74
|
+
const full = path.join(dir, name);
|
|
75
|
+
if (name === '__pycache__') continue; // `"__pycache__" in path.parts`
|
|
76
|
+
if (statSync(full).isDirectory()) walk(full); // rglob yields dirs; render_all skips them
|
|
77
|
+
else out.push(full);
|
|
78
|
+
}
|
|
79
|
+
})(src);
|
|
80
|
+
return out.sort(comparePaths); // `sorted()` — case-folded on Windows only
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `_build_render.load_theme`. Throws where Python calls `sys.exit`. */
|
|
84
|
+
export function loadTheme(cfg, name) {
|
|
85
|
+
const p = path.join(cfg.themes, `${name}.json`);
|
|
86
|
+
if (!existsSync(p)) {
|
|
87
|
+
const available = readdirSync(cfg.themes)
|
|
88
|
+
.filter((f) => f.endsWith('.json') && !f.startsWith('_'))
|
|
89
|
+
.map((f) => f.slice(0, -5)).sort().join(', ');
|
|
90
|
+
// `sys.exit(<str>)` — the message on stderr, exit 1 — spelled the way
|
|
91
|
+
// `assertSourceComplete` spells it: written at the raise site, and the throw carries
|
|
92
|
+
// only the marker. It reads as an unreachable nicety under `bin/geneseed.mjs`, whose
|
|
93
|
+
// `--theme` is validated against `choices` before anything renders. It is NOT
|
|
94
|
+
// unreachable under `harness status`, which takes its theme from a `.geneseed-theme`
|
|
95
|
+
// marker — an unvalidated file in a user's install. A bare `throw` there printed a Node
|
|
96
|
+
// stack trace where Python printed one line, and the acceptance matrix is what said so.
|
|
97
|
+
process.stderr.write(`[geneseed] unknown theme '${name}'. available: ${available}\n`);
|
|
98
|
+
const e = new Error(`unknown theme '${name}'`);
|
|
99
|
+
e.exitCode = 1;
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
// parseJson, not JSON.parse: one rule for every JSON the generator reads, so a numeric
|
|
103
|
+
// theme value keeps the int/float distinction Python's `str()` and `repr()` render
|
|
104
|
+
// differently. No shipped theme has one today; `_theme_json`'s ACCENT and the
|
|
105
|
+
// AGENT_COLORS validation warning are both one authoring typo away from needing it.
|
|
106
|
+
return parseJson(readText(p));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `_build_render.substitute`. */
|
|
110
|
+
export function substitute(text, theme) {
|
|
111
|
+
return text.replace(TOKEN_RE, (whole, key) => {
|
|
112
|
+
if (!Object.prototype.hasOwnProperty.call(theme, key)) return whole; // visible for debugging
|
|
113
|
+
const v = theme[key];
|
|
114
|
+
// Python renders `str(theme[key])`, which agrees with JS only for strings —
|
|
115
|
+
// `str(True)`/`String(true)` and `str({...})`/`String({...})` do not. Every one of
|
|
116
|
+
// the 145 tokens the source tree actually uses resolves to a string today
|
|
117
|
+
// (AGENT_COLORS is a dict but is never a token), so a non-string here means the
|
|
118
|
+
// source or a theme grew a case nobody has decided the semantics for. Fail loudly
|
|
119
|
+
// rather than emit `[object Object]` into somebody's AGENT.md.
|
|
120
|
+
if (typeof v !== 'string') {
|
|
121
|
+
throw new TypeError(`theme token {{${key}}} is ${typeof v}, not a string — Python's `
|
|
122
|
+
+ `str() and JS's String() disagree here; decide the rendering explicitly`);
|
|
123
|
+
}
|
|
124
|
+
return v;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** `_build_render._resolve_catalogs`. */
|
|
129
|
+
export function resolveCatalogs(text, nativeCatalog) {
|
|
130
|
+
return text.replace(CATALOG_BLOCK_RE, (_m, table, pointer) => (nativeCatalog ? pointer : table));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `re.split(r"(?m)^(?=### )", text)`.
|
|
135
|
+
*
|
|
136
|
+
* Hand-rolled rather than `String.split` with a lookahead: splitting on a zero-width
|
|
137
|
+
* match is a corner both engines implement, and the leading '' Python produces when the
|
|
138
|
+
* text starts with a heading is load-bearing (it becomes `blocks[0]`, the preamble).
|
|
139
|
+
*/
|
|
140
|
+
function splitAtLawHeadings(text) {
|
|
141
|
+
const out = [];
|
|
142
|
+
let start = 0;
|
|
143
|
+
for (let i = 0; i < text.length; i++) {
|
|
144
|
+
if ((i === 0 || text[i - 1] === '\n') && text.startsWith('### ', i)) {
|
|
145
|
+
out.push(text.slice(start, i));
|
|
146
|
+
start = i;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
out.push(text.slice(start));
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** `_build_render._terse_laws`. */
|
|
154
|
+
export function terseLaws(text, theme, lawsPrefix = '') {
|
|
155
|
+
const blocks = splitAtLawHeadings(text);
|
|
156
|
+
const out = [blocks[0].trimEnd()];
|
|
157
|
+
for (const b of blocks.slice(1)) {
|
|
158
|
+
// Python's `splitlines()` also breaks on VT, FF, FS-RS, NEL, LINE SEPARATOR and
|
|
159
|
+
// PARAGRAPH SEPARATOR, where `split('\n')` does not. The law corpus is plain
|
|
160
|
+
// markdown and readText has already folded CRLF and CR to LF, so the sets coincide.
|
|
161
|
+
// (Spelling those out by name rather than by literal is not fussiness: the first
|
|
162
|
+
// draft of this comment contained the actual LINE/PARAGRAPH SEPARATOR characters,
|
|
163
|
+
// which are JS line terminators, and they ended the comment mid-sentence.)
|
|
164
|
+
const lines = b.split('\n');
|
|
165
|
+
const heading = lines[0].trimEnd();
|
|
166
|
+
const body = lines.slice(1).map((l) => l.trim()).join(' ').trim();
|
|
167
|
+
const m = /^([\s\S]+?[.!?])(?:\s|$)/.exec(body);
|
|
168
|
+
out.push(`${heading}\n${m ? m[1].trim() : body}`);
|
|
169
|
+
}
|
|
170
|
+
const law = theme.LAW ?? 'Law';
|
|
171
|
+
const lawsDir = theme.DIR_LAWS ?? 'laws';
|
|
172
|
+
const pointer =
|
|
173
|
+
`> Each ${law} above is given in brief — the rule, not its reasoning. The `
|
|
174
|
+
+ `complete, binding text of every ${law} is in \`${lawsPrefix}${lawsDir}/universal.md\`; `
|
|
175
|
+
+ `read it whenever a ${law}'s application is unclear, and before any act touching `
|
|
176
|
+
+ `secrets, deletion, git history, scope, or untrusted content.`;
|
|
177
|
+
return out.join('\n\n') + '\n\n' + pointer;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** `_build_render.render_file`. */
|
|
181
|
+
export function renderFile(cfg, filePath, theme, footprint = 'full', lawsPrefix = '',
|
|
182
|
+
visiting = new Set(), nativeCatalog = false) {
|
|
183
|
+
const here = path.resolve(filePath);
|
|
184
|
+
let text = readText(filePath);
|
|
185
|
+
|
|
186
|
+
text = text.replace(INCLUDE_RE, (_whole, rel) => {
|
|
187
|
+
const target = path.resolve(cfg.src, rel);
|
|
188
|
+
if (!existsSync(target)) return `<!-- MISSING INCLUDE: ${rel} -->`;
|
|
189
|
+
if (normcase(target) === normcase(here) || visiting.has(normcase(target))) {
|
|
190
|
+
return `<!-- CIRCULAR INCLUDE: ${rel} -->`;
|
|
191
|
+
}
|
|
192
|
+
let inner = renderFile(cfg, target, theme, footprint, lawsPrefix,
|
|
193
|
+
new Set(visiting).add(normcase(here)), nativeCatalog)
|
|
194
|
+
.replace(/\n+$/, ''); // `.rstrip("\n")`
|
|
195
|
+
if (footprint === 'lean' && rel === 'laws/universal.md') {
|
|
196
|
+
inner = terseLaws(inner, theme, lawsPrefix);
|
|
197
|
+
}
|
|
198
|
+
return inner;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
text = resolveCatalogs(text, nativeCatalog);
|
|
202
|
+
return substitute(text, theme);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** `_build_render._posture_body` / `_mode_body` — one helper, both registers. */
|
|
206
|
+
function registerBody(cfg, theme, dir, selected, fallback) {
|
|
207
|
+
for (const name of [selected, fallback]) {
|
|
208
|
+
const p = path.join(cfg.src, dir, `${name}.md`);
|
|
209
|
+
if (existsSync(p) && statSync(p).isFile()) return substitute(readText(p), theme).trim();
|
|
210
|
+
}
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* `_build_render.effective_theme`.
|
|
216
|
+
*
|
|
217
|
+
* `cfg.structure` overrides the constant below when the driver supplies one, and it
|
|
218
|
+
* always does. STRUCTURE is a module-level dict on the Python side that tests MUTATE to
|
|
219
|
+
* simulate a themed DIR_* rename (`SrcDirRenameOrphanTests`); in-process that reaches
|
|
220
|
+
* every reader because the splice shares one dict object, and across a process boundary
|
|
221
|
+
* it reaches nothing at all unless it travels. The constant stays as the default for the
|
|
222
|
+
* parity harnesses, which drive this module directly.
|
|
223
|
+
*/
|
|
224
|
+
export function effectiveTheme(cfg, themeName) {
|
|
225
|
+
const theme = { ...loadTheme(cfg, themeName), ...(cfg.structure ?? STRUCTURE) };
|
|
226
|
+
theme.POSTURE_BODY = registerBody(cfg, theme, 'postures', cfg.posture ?? 'peer', 'peer');
|
|
227
|
+
theme.MODE_BODY = registerBody(cfg, theme, 'modes', cfg.mode ?? 'direct', 'direct');
|
|
228
|
+
return theme;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** `_build_render.themed_rel` — rename the top-level folder of an output path per theme. */
|
|
232
|
+
export function themedRel(rel, theme) {
|
|
233
|
+
const parts = rel.split(path.sep);
|
|
234
|
+
if (parts.length && Object.prototype.hasOwnProperty.call(SRC_DIR_TOKENS, parts[0])) {
|
|
235
|
+
parts[0] = theme[SRC_DIR_TOKENS[parts[0]]] ?? parts[0];
|
|
236
|
+
}
|
|
237
|
+
return parts.join(path.sep);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* `_build_render.dest_rel` — AGENT.md.tmpl -> AGENT.md; gitignore -> .gitignore;
|
|
242
|
+
* everything else keeps its name.
|
|
243
|
+
*
|
|
244
|
+
* WHY THE IGNORE FILES ARE STORED WITHOUT THE DOT — the Python twin's comment carries the
|
|
245
|
+
* full argument. In short: `src/memory/.gitignore` and `src/notebook/.gitignore` are
|
|
246
|
+
* product content, and under their real names `npm install` renamed them to `.npmignore`
|
|
247
|
+
* on extraction (so an npm-installed Geneseed emitted bundles with NO `.gitignore` and
|
|
248
|
+
* leaked the user's agent memory into their repo) while `npm pack` read the notebook's
|
|
249
|
+
* `*` as a live ignore rule and dropped its sibling README. A file named `gitignore` is
|
|
250
|
+
* invisible to all three; the dot goes back on here. Emitted bytes unchanged.
|
|
251
|
+
*/
|
|
252
|
+
export function destRel(rel) {
|
|
253
|
+
// path.join normalises the '.' that dirname returns for a bare filename, so a
|
|
254
|
+
// top-level AGENT.md.tmpl comes back as 'AGENT.md', not './AGENT.md'.
|
|
255
|
+
const name = path.basename(rel);
|
|
256
|
+
if (name === 'AGENT.md.tmpl') return path.join(path.dirname(rel), 'AGENT.md');
|
|
257
|
+
if (name === 'gitignore') return path.join(path.dirname(rel), '.gitignore');
|
|
258
|
+
return rel;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* `_build_render.render_all`.
|
|
263
|
+
*
|
|
264
|
+
* Returns `{ theme, items }` where each item is `{ rel, text, src }` — `rel` is the
|
|
265
|
+
* themed POSIX output path, `text` is the rendered text or `null` for a binary that the
|
|
266
|
+
* caller copies from `src`. Python returns a tuple of tuples; the object form is the
|
|
267
|
+
* same data with the field names the Python docstring already uses.
|
|
268
|
+
*/
|
|
269
|
+
export function renderAll(cfg, themeName, {
|
|
270
|
+
footprint = 'full', lawsPrefix = '', nativeCatalog = false,
|
|
271
|
+
} = {}) {
|
|
272
|
+
const theme = effectiveTheme(cfg, themeName);
|
|
273
|
+
const items = [];
|
|
274
|
+
for (const file of sortedSourceFiles(cfg.src)) {
|
|
275
|
+
const rel = path.relative(cfg.src, file);
|
|
276
|
+
const outRel = destRel(themedRel(rel, theme)).split(path.sep).join('/'); // as_posix()
|
|
277
|
+
items.push(TEXT_SUFFIXES.has(suffixOf(path.basename(file)))
|
|
278
|
+
? { rel: outRel, text: renderFile(cfg, file, theme, footprint, lawsPrefix, new Set(), nativeCatalog), src: file }
|
|
279
|
+
: { rel: outRel, text: null, src: file });
|
|
280
|
+
}
|
|
281
|
+
return { theme, items };
|
|
282
|
+
}
|