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/opencode.mjs
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenCode-native extras, in Node — everything `emit_opencode` writes beyond the
|
|
3
|
+
* agents and skills that `js/native.mjs` already owns.
|
|
4
|
+
*
|
|
5
|
+
* A translation of the second half of `_build_emit.py`: the branded and curated colour
|
|
6
|
+
* themes, the opt-in primary agent, the opt-in slash-command layer plus the always-on
|
|
7
|
+
* `/ponytail` switch, the verbatim plugin and workflow copies, and the git-ignored
|
|
8
|
+
* `agent-overrides.json` stub with its staleness notice.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS THE UNIT THAT SETTLES JSON. `jsonDumps` shipped string-only on purpose,
|
|
11
|
+
* because nothing on the Node side wrote a JSON *container* yet and its signature would
|
|
12
|
+
* have been a guess. This is that code. Measured across all 27 `json.dumps` call sites in
|
|
13
|
+
* the generator, the render half needs exactly two container shapes — `indent=2`, and
|
|
14
|
+
* `indent=2, ensure_ascii=False` — and both are `jsonDumpsIndent`, because passing an
|
|
15
|
+
* indent is what switches Python's separators to the ones `JSON.stringify` already uses.
|
|
16
|
+
*
|
|
17
|
+
* TWO STREAMS, AND THE SPLIT IS NOT TIDY. `_warn_if_overrides_stale` prints to STDOUT
|
|
18
|
+
* while every warning in `js/native.mjs` goes to stderr. That is the Python's own
|
|
19
|
+
* inconsistency and it is reproduced, not corrected: the parity gate compares both
|
|
20
|
+
* streams, so "fixing" it here would be a divergence. It is safe because this is the
|
|
21
|
+
* generator's CLI, which prints progress to stdout by design — unlike a hook path, where
|
|
22
|
+
* a stray stdout byte silently disarms a blocking gate.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import {
|
|
27
|
+
writeText, readText, copyFile, jsonDumps, jsonDumpsIndent, parseJson, comparePaths,
|
|
28
|
+
} from './lib/fs.mjs';
|
|
29
|
+
import { descOf, stripSkillBodyLinks, pushOverrideLines } from './native.mjs';
|
|
30
|
+
|
|
31
|
+
/** ANSI colour-name -> integer (0-7). Mirrors `_build_emit._ANSI`. */
|
|
32
|
+
const ANSI = {
|
|
33
|
+
black: 0, red: 1, green: 2, yellow: 3, blue: 4, magenta: 5, cyan: 6, white: 7,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `_build_emit._SLOT_ROLE` — OpenCode theme slot -> palette role.
|
|
38
|
+
*
|
|
39
|
+
* KEY ORDER IS OUTPUT. `_color_theme_json` builds its dict by iterating this map, and the
|
|
40
|
+
* emitted JSON preserves insertion order in both languages, so a reordering here is a
|
|
41
|
+
* byte-level diff in ten shipped theme files.
|
|
42
|
+
*/
|
|
43
|
+
const SLOT_ROLE = {
|
|
44
|
+
primary: 'accent', secondary: 'secondary', accent: 'accent',
|
|
45
|
+
error: 'err', warning: 'warn', success: 'ok', info: 'accent',
|
|
46
|
+
text: 'fg', textMuted: 'fgMuted',
|
|
47
|
+
background: 'bg', backgroundPanel: 'bgPanel', backgroundElement: 'bgElement',
|
|
48
|
+
border: 'border', borderActive: 'accent', borderSubtle: 'border',
|
|
49
|
+
diffAdded: 'ok', diffRemoved: 'err', diffContext: 'fgMuted',
|
|
50
|
+
diffHunkHeader: 'accent', diffHighlightAdded: 'ok', diffHighlightRemoved: 'err',
|
|
51
|
+
diffAddedBg: 'addBg', diffRemovedBg: 'delBg', diffContextBg: 'bgPanel',
|
|
52
|
+
diffLineNumber: 'fgMuted', diffAddedLineNumberBg: 'addBg', diffRemovedLineNumberBg: 'delBg',
|
|
53
|
+
markdownText: 'fg', markdownHeading: 'accent', markdownLink: 'secondary',
|
|
54
|
+
markdownLinkText: 'accent', markdownCode: 'ok', markdownBlockQuote: 'fgMuted',
|
|
55
|
+
markdownEmph: 'warn', markdownStrong: 'warn', markdownHorizontalRule: 'border',
|
|
56
|
+
markdownListItem: 'accent', markdownListEnumeration: 'accent', markdownImage: 'secondary',
|
|
57
|
+
markdownImageText: 'accent', markdownCodeBlock: 'bgElement',
|
|
58
|
+
syntaxComment: 'comment', syntaxKeyword: 'kw', syntaxFunction: 'fn',
|
|
59
|
+
syntaxVariable: 'fg', syntaxString: 'str', syntaxNumber: 'num',
|
|
60
|
+
syntaxType: 'type', syntaxOperator: 'kw', syntaxPunctuation: 'fgMuted',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Slots that become the terminal default in the transparent flavour. */
|
|
64
|
+
const TRANSPARENT_NONE = new Set([
|
|
65
|
+
'background', 'backgroundPanel', 'backgroundElement', 'diffContextBg', 'markdownCodeBlock',
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/** Mirrors `_build_emit.AGENT_OVERRIDES_STUB`. Key order is emitted order. */
|
|
69
|
+
const AGENT_OVERRIDES_STUB = {
|
|
70
|
+
_comment: (
|
|
71
|
+
'Per-agent OpenCode overrides. EMPTY = every agent inherits OpenCode\'s current '
|
|
72
|
+
+ 'model as-is (the default — nothing changes). Add entries keyed by agent name; '
|
|
73
|
+
+ 'supported keys: model, temperature, variant (reasoning effort, e.g. "high"), '
|
|
74
|
+
+ 'steps (max tool-iterations — a runaway-loop cap). e.g. '
|
|
75
|
+
+ '"reviewer": {"model": "anthropic/claude-haiku-4-5", "temperature": 0.1, '
|
|
76
|
+
+ '"variant": "high", "steps": 20}. '
|
|
77
|
+
+ 'Host-specific; git-ignored. A future TUI screen edits this — rebuild to apply.'
|
|
78
|
+
),
|
|
79
|
+
agents: {},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Mirrors `_build_emit.COMMAND_SET` — the hot skills worth a one-keystroke trigger. */
|
|
83
|
+
const COMMAND_SET = ['commit', 'plan', 'code-review', 'review-response',
|
|
84
|
+
'ship', 'debug', 'research'];
|
|
85
|
+
|
|
86
|
+
/** Mirrors `_build_emit.PONYTAIL_COMMAND_BODY`. */
|
|
87
|
+
const PONYTAIL_COMMAND_BODY =
|
|
88
|
+
'Ponytail level requested: $ARGUMENTS\n\n'
|
|
89
|
+
+ 'The geneseed-ponytail plugin records this level and, from your next turn, appends '
|
|
90
|
+
+ 'the matching "laziest solution that works" ruleset to your system prompt — honour '
|
|
91
|
+
+ 'it going forward, not just this message. An empty argument means `full`; `off` '
|
|
92
|
+
+ 'disables ponytail. Acknowledge the new level in one line, then continue.\n';
|
|
93
|
+
|
|
94
|
+
/** `_build_emit._truthy_env`. */
|
|
95
|
+
function truthyEnv(name) {
|
|
96
|
+
// `str.lower()` and `toLowerCase()` disagree on a few code points (dotted/dotless I
|
|
97
|
+
// among them); the accepted values are ASCII, so the sets coincide here.
|
|
98
|
+
return ['1', 'on', 'true', 'yes'].includes((process.env[name] || '').toLowerCase());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** `_build_core.source_release_version`. Belongs to core; travels in `cfg` until core lands. */
|
|
102
|
+
export function sourceReleaseVersion(cfg) {
|
|
103
|
+
try {
|
|
104
|
+
const data = parseJson(readText(cfg.config));
|
|
105
|
+
const v = data && typeof data === 'object' && !Array.isArray(data) ? data.version : null;
|
|
106
|
+
if (typeof v === 'string' && v) return v;
|
|
107
|
+
} catch {
|
|
108
|
+
// OSError / JSONDecodeError — degrade to "not newer" rather than raising.
|
|
109
|
+
}
|
|
110
|
+
return '0.0.0';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** `_build_emit._theme_json` — a COMPLETE terminal-native theme tinted by ACCENT. */
|
|
114
|
+
export function themeJson(theme) {
|
|
115
|
+
const raw = theme && theme.ACCENT !== undefined ? theme.ACCENT : 'cyan';
|
|
116
|
+
const key = String(raw).toLowerCase();
|
|
117
|
+
const acc = Object.prototype.hasOwnProperty.call(ANSI, key) ? ANSI[key] : 6;
|
|
118
|
+
const GRAY = 8;
|
|
119
|
+
const GREEN = 2;
|
|
120
|
+
const RED = 1;
|
|
121
|
+
const YEL = 3;
|
|
122
|
+
const MAG = 5;
|
|
123
|
+
const NONE = 'none';
|
|
124
|
+
const t = {
|
|
125
|
+
primary: acc, secondary: MAG, accent: acc,
|
|
126
|
+
error: RED, warning: YEL, success: GREEN, info: acc,
|
|
127
|
+
text: NONE, textMuted: GRAY,
|
|
128
|
+
background: NONE, backgroundPanel: NONE, backgroundElement: NONE,
|
|
129
|
+
border: GRAY, borderActive: acc, borderSubtle: GRAY,
|
|
130
|
+
diffAdded: GREEN, diffRemoved: RED, diffContext: GRAY,
|
|
131
|
+
diffHunkHeader: acc, diffHighlightAdded: GREEN, diffHighlightRemoved: RED,
|
|
132
|
+
diffAddedBg: NONE, diffRemovedBg: NONE, diffContextBg: NONE,
|
|
133
|
+
diffLineNumber: GRAY, diffAddedLineNumberBg: NONE, diffRemovedLineNumberBg: NONE,
|
|
134
|
+
markdownText: NONE, markdownHeading: acc, markdownLink: MAG,
|
|
135
|
+
markdownLinkText: acc, markdownCode: GREEN, markdownBlockQuote: GRAY,
|
|
136
|
+
markdownEmph: YEL, markdownStrong: YEL, markdownHorizontalRule: GRAY,
|
|
137
|
+
markdownListItem: acc, markdownListEnumeration: acc, markdownImage: MAG,
|
|
138
|
+
markdownImageText: acc, markdownCodeBlock: NONE,
|
|
139
|
+
syntaxComment: GRAY, syntaxKeyword: MAG, syntaxFunction: acc,
|
|
140
|
+
syntaxVariable: NONE, syntaxString: GREEN, syntaxNumber: MAG,
|
|
141
|
+
syntaxType: acc, syntaxOperator: MAG, syntaxPunctuation: NONE,
|
|
142
|
+
};
|
|
143
|
+
return { $schema: 'https://opencode.ai/theme.json', theme: t };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** `_build_emit._write_theme` — `<themes_dir>/geneseed-<theme>.json`. */
|
|
147
|
+
export function writeTheme(themesDir, themeName, theme) {
|
|
148
|
+
mkdirSync(themesDir, { recursive: true });
|
|
149
|
+
const dest = path.join(themesDir, `geneseed-${themeName}.json`);
|
|
150
|
+
writeText(dest, `${jsonDumpsIndent(themeJson(theme))}\n`);
|
|
151
|
+
return dest;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** `_build_emit._color_theme_json`. */
|
|
155
|
+
/**
|
|
156
|
+
* `_build_emit._PALETTE_ROLES` — every role some slot reads, as a set.
|
|
157
|
+
*
|
|
158
|
+
* Derived from `SLOT_ROLE` rather than listed, exactly as the Python derives it from
|
|
159
|
+
* `_SLOT_ROLE.values()`: a second literal list would be a second thing to keep in agreement,
|
|
160
|
+
* and `harness theme` prints the roles a user's palette is MISSING, so a drift between the
|
|
161
|
+
* two would refuse a complete palette or accept an incomplete one.
|
|
162
|
+
*/
|
|
163
|
+
export const PALETTE_ROLES = new Set(Object.values(SLOT_ROLE));
|
|
164
|
+
|
|
165
|
+
export function colorThemeJson(palette, transparent) {
|
|
166
|
+
const t = {};
|
|
167
|
+
for (const [slot, role] of Object.entries(SLOT_ROLE)) {
|
|
168
|
+
if (transparent && TRANSPARENT_NONE.has(slot)) {
|
|
169
|
+
t[slot] = 'none';
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// `palette[role]` is a plain subscript in Python: a palette missing a role raises
|
|
173
|
+
// KeyError and aborts the emit. A bare JS lookup would yield undefined, which
|
|
174
|
+
// JSON.stringify DROPS — silently emitting a theme with missing slots.
|
|
175
|
+
if (!Object.prototype.hasOwnProperty.call(palette, role)) {
|
|
176
|
+
throw new Error(`palette has no role '${role}' (slot '${slot}')`);
|
|
177
|
+
}
|
|
178
|
+
t[slot] = palette[role];
|
|
179
|
+
}
|
|
180
|
+
return { $schema: 'https://opencode.ai/theme.json', theme: t };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** `_build_emit.color_theme_files` — shipped sources, `_`-prefixed scaffolds excluded. */
|
|
184
|
+
export function colorThemeFiles(cfg) {
|
|
185
|
+
if (!existsSync(cfg.colorThemes) || !statSync(cfg.colorThemes).isDirectory()) return [];
|
|
186
|
+
return readdirSync(cfg.colorThemes)
|
|
187
|
+
.filter((n) => n.endsWith('.json') && !n.startsWith('_'))
|
|
188
|
+
.map((n) => path.join(cfg.colorThemes, n))
|
|
189
|
+
.sort(comparePaths);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** `_build_emit._write_color_themes` — each curated palette in both flavours. */
|
|
193
|
+
export function writeColorThemes(cfg, themesDir) {
|
|
194
|
+
mkdirSync(themesDir, { recursive: true });
|
|
195
|
+
const written = [];
|
|
196
|
+
for (const src of colorThemeFiles(cfg)) {
|
|
197
|
+
const spec = parseJson(readText(src));
|
|
198
|
+
const palette = spec.palette;
|
|
199
|
+
for (const [flavour, transparent] of [['solid', false], ['transparent', true]]) {
|
|
200
|
+
const dest = path.join(themesDir, `geneseed-${spec.name}-${flavour}.json`);
|
|
201
|
+
writeText(dest, `${jsonDumpsIndent(colorThemeJson(palette, transparent))}\n`);
|
|
202
|
+
written.push(dest);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return written;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** `_build_emit._load_agent_overrides`'s sibling: has this install's stub gone stale? */
|
|
209
|
+
function warnIfOverridesStale(cfg, dest) {
|
|
210
|
+
let data;
|
|
211
|
+
try {
|
|
212
|
+
data = parseJson(readText(dest));
|
|
213
|
+
} catch {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return;
|
|
217
|
+
// Python's `not data.get("agents")` — an EMPTY override map is never worth flagging,
|
|
218
|
+
// drift or not.
|
|
219
|
+
const agents = data.agents;
|
|
220
|
+
if (!agents || (typeof agents === 'object' && Object.keys(agents).length === 0)) return;
|
|
221
|
+
const stamped = data._version;
|
|
222
|
+
const current = sourceReleaseVersion(cfg);
|
|
223
|
+
if (stamped !== current) {
|
|
224
|
+
// STDOUT, matching the Python. See the module header: not a hook path.
|
|
225
|
+
process.stdout.write('[geneseed] agent-overrides.json was written for Geneseed '
|
|
226
|
+
+ `${typeof stamped === 'string' && stamped ? stamped : 'unknown version'}, `
|
|
227
|
+
+ `current is ${current} — review your overrides against the updated `
|
|
228
|
+
+ 'agent specs\n');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** `_build_emit.ensure_agent_overrides_stub` — drop once, never overwrite. */
|
|
233
|
+
export function ensureAgentOverridesStub(cfg, base) {
|
|
234
|
+
const dest = path.join(base, 'agent-overrides.json');
|
|
235
|
+
if (existsSync(dest)) {
|
|
236
|
+
warnIfOverridesStale(cfg, dest);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// `dict(AGENT_OVERRIDES_STUB)` then `stub["_version"] = ...` — a shallow copy with the
|
|
240
|
+
// new key appended LAST, which is the order it is written in.
|
|
241
|
+
const stub = { ...AGENT_OVERRIDES_STUB, _version: sourceReleaseVersion(cfg) };
|
|
242
|
+
writeText(dest, `${jsonDumpsIndent(stub, { ensureAscii: false })}\n`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** `_build_emit._write_primary_agent` — the opt-in `mode: primary` orchestrator. */
|
|
246
|
+
export function writePrimaryAgent(cfg, agentsDir, overrides) {
|
|
247
|
+
if (!truthyEnv('GENESEED_PRIMARY') || !existsSync(cfg.primaryAgentSrc)
|
|
248
|
+
|| !statSync(cfg.primaryAgentSrc).isFile()) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const body = readText(cfg.primaryAgentSrc).replace(/^\n+/, '');
|
|
252
|
+
const desc = 'Primary orchestrator — works by the harness Rules and delegates to the '
|
|
253
|
+
+ 'capability subagents.';
|
|
254
|
+
const fm = [`description: ${jsonDumps(desc)}`, 'mode: primary', 'color: primary'];
|
|
255
|
+
const ov = (Object.prototype.hasOwnProperty.call(overrides, 'orchestrator')
|
|
256
|
+
&& overrides.orchestrator) || {};
|
|
257
|
+
pushOverrideLines(fm, ov);
|
|
258
|
+
const dest = path.join(agentsDir, 'orchestrator.md');
|
|
259
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
260
|
+
writeText(dest, `---\n${fm.join('\n')}\n---\n\n${body}`);
|
|
261
|
+
return dest;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** `_build_emit._write_command_layer` — the opt-in /slash commands. */
|
|
265
|
+
export function writeCommandLayer(cfg, items, commandDir) {
|
|
266
|
+
if (!truthyEnv('GENESEED_COMMANDS')) return [];
|
|
267
|
+
const byName = new Map();
|
|
268
|
+
for (const { text, src } of items) {
|
|
269
|
+
if (text === null) continue;
|
|
270
|
+
const sp = path.relative(cfg.src, src).split(path.sep);
|
|
271
|
+
if (sp.length === 2 && sp[0] === 'skills' && sp[1].endsWith('.md')
|
|
272
|
+
&& !sp[1].startsWith('_')) {
|
|
273
|
+
byName.set(sp[1].slice(0, -3), text);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const written = [];
|
|
277
|
+
for (const name of COMMAND_SET) {
|
|
278
|
+
const text = byName.get(name);
|
|
279
|
+
if (text === undefined) continue;
|
|
280
|
+
const desc = descOf(text);
|
|
281
|
+
const body = stripSkillBodyLinks(text.replace(/^\n+/, ''));
|
|
282
|
+
const dest = path.join(commandDir, `${name}.md`);
|
|
283
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
284
|
+
writeText(dest, `---\ndescription: ${jsonDumps(desc)}\n---\n\n${body}`);
|
|
285
|
+
written.push(dest);
|
|
286
|
+
}
|
|
287
|
+
return written;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** `_build_emit._write_ponytail_command` — registered UNCONDITIONALLY. */
|
|
291
|
+
export function writePonytailCommand(commandDir) {
|
|
292
|
+
const dest = path.join(commandDir, 'ponytail.md');
|
|
293
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
294
|
+
const desc = 'Set the ponytail minimal-code level for the session: lite | full | ultra | off';
|
|
295
|
+
writeText(dest, `---\ndescription: ${jsonDumps(desc)}\n---\n\n${PONYTAIL_COMMAND_BODY}`);
|
|
296
|
+
return dest;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** `_copy_plugins` / `_copy_workflows` — maintained files, copied verbatim (copy2, so LF). */
|
|
300
|
+
function copyJsDir(srcDir, dst, owned, prefix) {
|
|
301
|
+
let n = 0;
|
|
302
|
+
if (existsSync(srcDir) && statSync(srcDir).isDirectory()) {
|
|
303
|
+
mkdirSync(dst, { recursive: true });
|
|
304
|
+
const files = readdirSync(srcDir)
|
|
305
|
+
.filter((name) => name.endsWith('.js'))
|
|
306
|
+
.map((name) => path.join(srcDir, name))
|
|
307
|
+
.sort(comparePaths);
|
|
308
|
+
for (const js of files) {
|
|
309
|
+
copyFile(js, path.join(dst, path.basename(js)));
|
|
310
|
+
if (owned !== null && owned !== undefined) owned.push(`${prefix}/${path.basename(js)}`);
|
|
311
|
+
n += 1;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return n;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** `_build_emit._copy_plugins`. */
|
|
318
|
+
export function copyPlugins(cfg, dst, owned = null) {
|
|
319
|
+
return copyJsDir(cfg.pluginSrc, dst, owned, 'plugins');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** `_build_emit._copy_workflows`. */
|
|
323
|
+
export function copyWorkflows(cfg, dst, owned = null) {
|
|
324
|
+
return copyJsDir(cfg.workflowSrc, dst, owned, 'workflows');
|
|
325
|
+
}
|
package/js/registry.mjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `_install_registry.py` — the persistent list of folders a harness was deployed into.
|
|
3
|
+
*
|
|
4
|
+
* MOVED OUT OF `bin/geneseed.mjs` IN P5f, for the third time the same arithmetic has decided
|
|
5
|
+
* a module boundary in this port (P5c took the host resolvers, P5d the checkout paths). The
|
|
6
|
+
* driver WRITES the registry, one row per per-repo emit; `harness rebuild-all` READS it to
|
|
7
|
+
* find every install it must re-emit, and `bin/geneseed-cli.mjs` may not reach into a driver
|
|
8
|
+
* for a reader. `js/generate.mjs` importing `main` from `bin/geneseed.mjs` is not a
|
|
9
|
+
* precedent for it either: `harness build` IS the driver, and a registry reader is not.
|
|
10
|
+
*
|
|
11
|
+
* The move is licensed the same way the two before it were: `tests/golden.py` drives
|
|
12
|
+
* `bin/geneseed.mjs` over 259 cells with `$XDG_CONFIG_HOME` redirected into the sandbox and
|
|
13
|
+
* compares `installs.json` byte-for-byte, so a changed path or a dropped prune fails cells
|
|
14
|
+
* rather than passing quietly.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync } from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
|
|
20
|
+
import { expanduser } from './hosts.mjs';
|
|
21
|
+
import { writeText, parseJson, jsonDumpsIndent, pyPathStr } from './lib/fs.mjs';
|
|
22
|
+
|
|
23
|
+
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
|
|
24
|
+
const isFile = (p) => { try { return statSync(p).isFile(); } catch { return false; } };
|
|
25
|
+
|
|
26
|
+
/** `_install_registry._path()` — `$XDG_CONFIG_HOME/geneseed/installs.json`. */
|
|
27
|
+
export function registryPath() {
|
|
28
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
29
|
+
return path.join(base, 'geneseed', 'installs.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registryLoad() {
|
|
33
|
+
try {
|
|
34
|
+
const data = parseJson(readFileSync(registryPath(), 'utf8'));
|
|
35
|
+
return Array.isArray(data) ? data.map(String) : [];
|
|
36
|
+
} catch { return []; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function registrySave(items) {
|
|
40
|
+
try {
|
|
41
|
+
const file = registryPath();
|
|
42
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
43
|
+
const tmp = `${file}.tmp`;
|
|
44
|
+
// `jsonDumpsIndent` for the same reason as the manifest: these are absolute paths, and
|
|
45
|
+
// a machine whose username carries an accent writes one here.
|
|
46
|
+
writeText(tmp, `${jsonDumpsIndent(items)}\n`);
|
|
47
|
+
renameSync(tmp, file);
|
|
48
|
+
} catch { /* best-effort: a registry hiccup must never break a deploy */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `_install_registry.record` — idempotent, best-effort, and it must NEVER raise into a build.
|
|
53
|
+
*
|
|
54
|
+
* `realpathSync` rather than `path.resolve`, because Python's `Path.resolve()` returns the
|
|
55
|
+
* filesystem's own casing and follows links; `path.resolve` only makes a path absolute. The
|
|
56
|
+
* registry is compared byte-for-byte by `tests/golden.py` (XDG is redirected into the
|
|
57
|
+
* sandbox), so a differently-cased entry is a failing cell.
|
|
58
|
+
*/
|
|
59
|
+
export function registryRecord(dir) {
|
|
60
|
+
try {
|
|
61
|
+
let root;
|
|
62
|
+
try { root = realpathSync.native(dir); } catch { root = path.resolve(dir); }
|
|
63
|
+
const cur = registryLoad();
|
|
64
|
+
if (!cur.includes(root)) registrySave([...cur, root]);
|
|
65
|
+
} catch { /* a registry hiccup must never fail a build */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `_install_registry.roots()` — live registered deploy roots, pruning the file as it reads.
|
|
70
|
+
*
|
|
71
|
+
* The prune is a WRITE, and reproducing it matters: `installs.json` is compared byte-for-byte,
|
|
72
|
+
* so a read that failed to drop a dead row would leave the two implementations holding
|
|
73
|
+
* different registries. The snapshot is taken once on purpose (its own comment says why):
|
|
74
|
+
* comparing `kept` against a SECOND read could clobber a root a concurrent `record()`
|
|
75
|
+
* appended between the two.
|
|
76
|
+
*/
|
|
77
|
+
export function registryRoots() {
|
|
78
|
+
const original = registryLoad();
|
|
79
|
+
const out = [];
|
|
80
|
+
const kept = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const s of original) {
|
|
83
|
+
// `Path(s).expanduser()`, and `pyPathStr` is the `str()` around it — which is where the
|
|
84
|
+
// NORMALISATION happens. Python turns `C:/x/other` into `C:\x\other` before it is
|
|
85
|
+
// compared, kept, returned and printed, so a registry entry spelled with forward slashes
|
|
86
|
+
// (routine: every tool that writes one from Git Bash) makes Python rewrite the file and
|
|
87
|
+
// print a backslash path while a raw `expanduser` prints the entry verbatim and leaves
|
|
88
|
+
// installs.json alone. Invisible to every cell before P5h, because `rebuild-all`'s
|
|
89
|
+
// registry cells seed native spellings; `uninstall`'s inventory prints the root.
|
|
90
|
+
// `expanduser` now REFUSES a `~user` entry (js/hosts.mjs's docblock) instead of passing
|
|
91
|
+
// it through. This loop scans STORED registry rows, not a fresh CLI argument — a single
|
|
92
|
+
// bad row (hand-edited, or written before this refusal existed) must not abort the scan
|
|
93
|
+
// for every other install, so it is dropped exactly like any other dead row rather than
|
|
94
|
+
// propagated. `rebuild-all`'s contract is "one broken install never blocks the rest".
|
|
95
|
+
let root;
|
|
96
|
+
try { root = pyPathStr(expanduser(s)); } catch { continue; }
|
|
97
|
+
let key;
|
|
98
|
+
try { key = existsSync(root) ? realpathSync.native(root) : root; } catch { key = root; }
|
|
99
|
+
if (seen.has(key)) continue;
|
|
100
|
+
if (isDir(root) && isFile(path.join(root, '.geneseed-emit'))) {
|
|
101
|
+
seen.add(key);
|
|
102
|
+
kept.push(root);
|
|
103
|
+
out.push(root);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (kept.length !== original.length || kept.some((v, i) => v !== original[i])) {
|
|
107
|
+
registrySave(kept);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|