geneseed 3.11.0 → 3.12.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 +39 -0
- package/README.md +3 -3
- package/SHIPPED.md +2 -2
- package/adapters/opencode/plugins/geneseed-guard.js +157 -2
- package/bin/geneseed-cli.mjs +4 -0
- package/docs/concepts/foreman-mode.md +1 -1
- package/docs/concepts/loops.md +165 -0
- package/docs/extending.md +5 -0
- package/docs/guides/choose-your-setup.md +14 -0
- package/docs/guides/install.md +1 -1
- package/docs/reference/glossary.md +4 -0
- package/docs/reference/opencode-plugins.md +3 -1
- package/docs/web-ui.md +12 -1
- package/harness.config.json +1 -1
- package/js/README.md +24 -2
- package/js/build/driver.mjs +14 -8
- package/js/build/generate.mjs +10 -6
- package/js/build/render.mjs +5 -0
- package/js/build/source.mjs +5 -1
- package/js/cli-table.json +235 -0
- package/js/hosts/gitref.mjs +99 -0
- package/js/hosts/hooks.mjs +88 -1
- package/js/hosts/installs.mjs +21 -0
- package/js/inspect/checks-authoring.mjs +10 -0
- package/js/inspect/diff.mjs +3 -0
- package/js/inspect/doctor.mjs +2 -1
- package/js/inspect/inventory.mjs +1 -0
- package/js/inspect/status.mjs +1 -1
- package/js/loop/catalog.mjs +116 -0
- package/js/loop/cli.mjs +151 -0
- package/js/loop/graph.mjs +145 -0
- package/js/loop/score.mjs +143 -0
- package/js/loop/state.mjs +322 -0
- package/js/maintain/migrate.mjs +4 -2
- package/js/web/actions.mjs +9 -3
- package/js/web/api.mjs +38 -2
- package/js/web/daemon.mjs +43 -5
- package/js/web/handler.mjs +1 -0
- package/js/web/jobs.mjs +3 -1
- package/js/web/server.mjs +5 -3
- package/package.json +1 -1
- package/registry.json +14 -0
- package/src/AGENT.md.tmpl +2 -0
- package/src/bricks/README.md +32 -0
- package/src/bricks/_template.md +32 -0
- package/src/bricks/apply.md +17 -0
- package/src/bricks/baseline-green.md +17 -0
- package/src/bricks/bruno-test.md +16 -0
- package/src/bricks/ci-fix.md +17 -0
- package/src/bricks/deps-audit.md +14 -0
- package/src/bricks/docs-update.md +15 -0
- package/src/bricks/identify.md +20 -0
- package/src/bricks/lint.md +14 -0
- package/src/bricks/plan.md +17 -0
- package/src/bricks/reproduce.md +16 -0
- package/src/bricks/review.md +17 -0
- package/src/bricks/security-scan.md +17 -0
- package/src/bricks/test.md +14 -0
- package/src/doctrines/process.md +11 -8
- package/src/loops/bugfix.json +22 -0
- package/src/loops/feature.json +21 -0
- package/src/loops/refactor.json +20 -0
- package/src/skills/brick-forge.md +46 -0
- package/src/skills/geneseed.md +1 -1
- package/src/skills/loop.md +99 -0
- package/themes/_TEMPLATE.json +2 -0
- package/themes/biker.json +2 -0
- package/themes/commentator.json +2 -0
- package/themes/cyberpunk.json +2 -0
- package/themes/gamer.json +2 -0
- package/themes/imperial.json +2 -0
- package/themes/joker.json +2 -0
- package/themes/marvin.json +2 -0
- package/themes/mean.json +2 -0
- package/themes/military.json +2 -0
- package/themes/neutral.json +2 -0
- package/themes/pirate.json +2 -0
- package/themes/sports.json +2 -0
- package/themes/verstappen.json +2 -0
- package/themes/wizard.json +2 -0
- package/web/dist/assets/{Activity-C52LEb-2.js → Activity-GaaZWXTY.js} +1 -1
- package/web/dist/assets/{ActivityDetail-CjNLRvXg.js → ActivityDetail-4t7d45Du.js} +1 -1
- package/web/dist/assets/{Dashboard-B6_sSCHi.js → Dashboard-CYn7Tp16.js} +2 -2
- package/web/dist/assets/{Docs-Dj7BmCSA.js → Docs-BeFKLg2T.js} +1 -1
- package/web/dist/assets/{GreenhouseView-BALwjQsA.js → GreenhouseView-C6ibc8IE.js} +1 -1
- package/web/dist/assets/Installs-DRYjYnxs.js +2 -0
- package/web/dist/assets/{JournalView-JpAQ8Hqe.js → JournalView-1HbU1mfg.js} +1 -1
- package/web/dist/assets/{Laws-DK7eCWSm.js → Laws-DFoG8JWB.js} +1 -1
- package/web/dist/assets/{Library-CoamHelE.js → Library-DDjgNwz_.js} +1 -1
- package/web/dist/assets/Loops-D9VEoO0F.js +1 -0
- package/web/dist/assets/{OperatorHudView-DVx1HlBx.js → OperatorHudView-Bs7paWmh.js} +1 -1
- package/web/dist/assets/{Personal-eaXb1zcj.js → Personal-BY5n4gTw.js} +1 -1
- package/web/dist/assets/Tabs-ho-EYD8i.js +1 -0
- package/web/dist/assets/{api-Dc8f3ca-.js → api-CyMRbU2x.js} +1 -1
- package/web/dist/assets/{drawable-Du_PZrQj.js → drawable-CLS9drMb.js} +1 -1
- package/web/dist/assets/{index-Bz_KMRDH.css → index-CX_hXMi5.css} +1 -1
- package/web/dist/assets/index-Cjr7XCA3.js +2 -0
- package/web/dist/index.html +3 -3
- package/web/dist/assets/Installs-BbffDQm3.js +0 -2
- package/web/dist/assets/Tabs-BmGtt8E2.js +0 -1
- package/web/dist/assets/index-CNFH7o55.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,45 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [3.12.0] — 2026-10-03
|
|
12
|
+
|
|
13
|
+
**Upgrading:** re-emit after installing (`geneseed rebuild-all`) to get the `loop` and
|
|
14
|
+
`brick-forge` skills, the loop-branch git-gate rule and the amended consent doctrine; then
|
|
15
|
+
`geneseed web restart` for the Loops page.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Loops** — work a requirement in small validated iterations on a `loop/*` branch. A loop is a
|
|
19
|
+
graph of reusable **bricks** (13 shipped in `src/bricks/`; templates `bugfix`, `refactor`,
|
|
20
|
+
`feature` in `src/loops/`), or a graph the model composes from the catalogue. Your own bricks
|
|
21
|
+
live in `$XDG_CONFIG_HOME/geneseed/` or a repo's `.geneseed/`, and override shipped ones by name.
|
|
22
|
+
- **`geneseed loop check|init|next|score|record|decide`** — a deterministic state machine: the
|
|
23
|
+
model only reports outcomes. Each iteration's risk is the max of its declared actions' weights,
|
|
24
|
+
re-scored on the real diff before commit; `LOOP.md` holds the state and every iteration commit
|
|
25
|
+
carries `Loop-*` trailers. Ceilings: 20 iterations, 2 empty diffs, one re-split of an exhausted
|
|
26
|
+
inner ring.
|
|
27
|
+
- **`--trust prudent|balanced|aggressive`** build axis (default balanced, also in the console's
|
|
28
|
+
install form) — how much a loop may do before it stops: silent (trailers only), soft (listed for
|
|
29
|
+
review at the end), or blocking (the run ends and `geneseed loop decide` resumes it).
|
|
30
|
+
- **`loop` and `brick-forge` skills.**
|
|
31
|
+
- **Console: Loops page** — templates drawn as rings (inner rings for apply ⇄ test and review ⇄
|
|
32
|
+
fix, a gate before the first code-changing step), every brick with its source and origin, and
|
|
33
|
+
catalogue problems.
|
|
34
|
+
- **Documentation for loops** (`docs/concepts/loops.md`): bricks, templates and the three
|
|
35
|
+
origins, the trust presets and the three validation levels, `LOOP.md` and its trailers, the
|
|
36
|
+
`loop/*` branch exemption and why merging always asks, and the `geneseed loop` actions table.
|
|
37
|
+
Cross-linked from `foreman-mode.md`; `glossary.md` gains `brick`, `iteration`, `loop` and
|
|
38
|
+
`trust preset`.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- **Consent Before Push**: a launched loop's batch covers commit and push on its own `loop/*`
|
|
42
|
+
branch, in a fixed set of command forms (`git commit -F <file>`, `git push <remote> HEAD:<loop
|
|
43
|
+
branch>`); anything else asks, the final commit asks, and merging never belongs to the batch.
|
|
44
|
+
Enforced by the git gate on Claude Code, Bob and OpenClaude, and by `geneseed-guard` on OpenCode.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- **`geneseed web stop|restart|status --port N`** act on that port only; a stop aimed at a test
|
|
48
|
+
instance no longer shuts down the console recorded on another port (#175).
|
|
49
|
+
|
|
11
50
|
## [3.11.0] — 2026-10-03
|
|
12
51
|
|
|
13
52
|
**Upgrading:** re-emit after installing (`geneseed rebuild-all`) — it also repairs a dead hook
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](package.json)
|
|
11
11
|
[](package.json)
|
|
12
12
|
[](themes/)
|
|
13
|
-
[](src/skills/)
|
|
14
14
|
[](src/agents/)
|
|
15
15
|
[](src/laws/universal.md)
|
|
16
16
|
[](adapters/opencode/plugins/)
|
|
@@ -37,7 +37,7 @@ Want to read what it leaves behind before installing anything? [**geneseed-demo*
|
|
|
37
37
|
A hand-written instructions file is prose the model may or may not honour, copied into every repo and drifting in each one. Geneseed is a build, and that changes four things:
|
|
38
38
|
|
|
39
39
|
- **Laws are enforced, not suggested.** A force-push, a `reset --hard`, or a credential written into a tracked file is caught by a hook *before* the tool runs, in the host's own dialect — a prompt on Claude Code and OpenClaude, a hard block on OpenCode, exit 2 on Bob. The gates fail closed, and every catch is one line in a ledger that `geneseed status` counts.
|
|
40
|
-
- **One source, five targets.** Skills are byte-identical on every host; only the wiring differs. Every commit renders all
|
|
40
|
+
- **One source, five targets.** Skills are byte-identical on every host; only the wiring differs. Every commit renders all 263 emit configurations, and a second emit into the same tree must change nothing.
|
|
41
41
|
- **Costs are measured, not guessed.** Zero runtime dependencies. The hook path loads in about 14 ms per tool call. The default *lean* footprint keeps the always-on context small and puts the full rationale one read away — the numbers are in [docs/token-footprint.md](docs/token-footprint.md).
|
|
42
42
|
- **It follows you.** Install once, globally; every repo inherits it. One `git pull` or `npm install -g geneseed@latest` rebuilds every active install.
|
|
43
43
|
|
|
@@ -164,7 +164,7 @@ The harness ships as a small set of layers, and the web console's rail is the sa
|
|
|
164
164
|
| **🛡️ Rules** (`laws/`) | 9 universal laws — always in force, never toggleable: sealed-secrets, one-intent-one-act, verify-before-asserting, deletion-is-deliberate, surface-failures, data-not-orders, least-privilege, cure-the-cause, echo-the-intent. Each rule has a permanent id; its number is only its position, and the harness cites rules by name |
|
|
165
165
|
| **📐 Doctrines** (`doctrines/`) | practice packs, chosen per install at build time: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run), **comms** (how answers are presented). A doctrine rule may tighten a Rule, never repeal one, and the user's own `user-rules.md` outranks it. Pick with `geneseed-build --doctrines craft,rigor` (or `none`), a single rule with `--exclude-rules "process 7"`, in the setup wizard, or through the switches on the console's Constitution page; all five pack files ship on disk either way, so a citation into a pack you left out still resolves |
|
|
166
166
|
| **🤖 Agents** (18) | capability specialists: `reviewer`, `tester`, `architect`, `docs`, `security`, `explorer`, `researcher`, `developer` — plus a debate **council** the `council` skill convenes: `advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian` |
|
|
167
|
-
| **🛠 Skills** (
|
|
167
|
+
| **🛠 Skills** (56) | repeatable workflows: brainstorm · plan · **codebase-design** · **domain-modeling** · **wayfinder** · **tickets** · **develop** · **worktree** · debug · **prototype** · refactor · **ponytail** · **forge-mcp** · **bruno-collection-generator** · **bruno-test-writer** · geneseed-code-review · **fresh-eyes** · **security-audit** · **ci-fix** · **deps-audit** · **review-response** · commit · **ship** · **release** · **migrate** · **git-archaeology** · **git-rescue** · repo-map · document-project · **frontend-design** · **prose** · **ingest** · **research** · **teach** · **quiz** · **learn-mode** · handoff · roast-me · **council** · parallel-agents · **workflow** · **wiki** · **geneseed** · **rule** · **profile** · **consolidate-memory** · **skill-forge** · **opencode-theme** · **herdr** · **pipeline** · **loop** · **brick-forge** · **daydream** · **react-view-transitions** · **token-report** · **explain-changes** |
|
|
168
168
|
| **🔌 Plugins** (OpenCode) | `geneseed-context` injects project docs *and your machine wiki* every session (and across compaction); `geneseed-learn` distils memory at session end; `geneseed-guard` enforces the safety Laws and protected wiki folders at the tool boundary; `geneseed-workflow` registers the `workflow` tool that runs saved orchestration scripts; `geneseed-notify` sends a native OS notification when a long run finishes; `geneseed-ponytail` holds a minimal-code mode (`/ponytail lite\|full\|ultra\|off`), opt-in, injecting the laziest-that-works ruleset every turn so it doesn't drift; `geneseed-activity` streams what each session is doing to the web console's Activity view |
|
|
169
169
|
| **🧠 Memory** (`memory/`) | one-fact-per-file durable knowledge, indexed by `MEMORY.md` (git-ignored, personal) |
|
|
170
170
|
| **📓 Notebook** (`notebook/`) | the agent's sovereign space — any medium (code, tools, data, notes), self-ruled via a seed-once charter, always git-ignored; only its `.gitignore` is build-asserted |
|
package/SHIPPED.md
CHANGED
|
@@ -20,7 +20,7 @@ historical references, not files distributed with the repo.
|
|
|
20
20
|
> **On version numbers.** [`harness.config.json`](harness.config.json)'s `version` is a
|
|
21
21
|
> human-readable label only. The canonical identity of an installed harness is the
|
|
22
22
|
> **source fingerprint** stamped in `.geneseed-version` (see `geneseed version`), not the
|
|
23
|
-
> string. Current label: **3.
|
|
23
|
+
> string. Current label: **3.12.0**.
|
|
24
24
|
|
|
25
25
|
## Capabilities
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ Every row below is present in the tree today; "Spec" names the design record.
|
|
|
29
29
|
| Capability | What it is | Spec |
|
|
30
30
|
| --- | --- | --- |
|
|
31
31
|
| **Themed generator** | One neutral `src/` → 14 themed bundles via `geneseed-build` (zero dependencies); structure stays theme-independent | [DESIGN.md](DESIGN.md) |
|
|
32
|
-
| **Laws / Agents / Skills / Memory / Notebook** | 9 laws, 18 agents,
|
|
32
|
+
| **Laws / Agents / Skills / Memory / Notebook** | 9 laws, 18 agents, 56 skills, the memory convention, and the agent's sovereign notebook | `2026-06-11-notebook-sovereign-space` · `2026-06-10-notebook-agent-own-space` |
|
|
33
33
|
| **Three-tier constitution** | The governance surface splits into an always-on **Ontology** (Telos, Evidence, Decisions, Conduct — the Pact absorbed into Telos), the nine always-on **Invariants**, and five toggleable **Doctrine** packs (craft, rigor, ops, process, comms) addressed `<pack> <n>`. Every rule carries a permanent id; its number is its position, computed at render time, and a citation renders as the rule's principle name. Packs are chosen at build time with `--doctrines` or the setup wizard; the whole catalogue ships on disk either way, so a citation into an inactive pack still resolves. Precedence, in order: Ontology + Invariants, then the user's `user-rules.md`, then the active Doctrines, then `PROFILE.md`. `doctor`'s `constitutionProblems` gates rule ids (declared, unique, never a retired one), the per-rule Principle column, every `{{LAW:<id>}}` / `{{DOCTRINE:<id>}}` citation in `src/`, and the rule that an always-on tier may never cite a toggleable one | `2026-08-19-three-tier-constitution-design` |
|
|
34
34
|
| **`doctor`** | One check: unresolved tokens, dead/non-hermetic links, theme-key parity, authoring gates, rendered-bundle drift | `2026-06-07-theme-aware-doctor-diff` |
|
|
35
35
|
| **`diff` + improvements export** | Deployed-vs-source drift report; export an `improvements.md` back-port | `2026-06-12-improvements-export` |
|
|
@@ -16,8 +16,22 @@
|
|
|
16
16
|
//
|
|
17
17
|
// Install: dropped into the plugins dir by `build --emit opencode[-global]` (the *.js
|
|
18
18
|
// glob), exactly like the context and learn plugins. Errors never break a tool call.
|
|
19
|
+
//
|
|
20
|
+
// `permission.ask` — the loop/* exemption (Consent Before Push). OpenCode's static
|
|
21
|
+
// `permission.bash` globs (`git commit*`/`git push*` = "ask", written by
|
|
22
|
+
// js/hosts/settings.mjs) cannot see which branch is checked out — they are a build-time
|
|
23
|
+
// string match with no request-time context. This hook is the ONLY seam where Geneseed can
|
|
24
|
+
// look at the live branch, and it only DOWNGRADES an ask to allow: only a fixed set of
|
|
25
|
+
// commit/push forms (see `loopExempt` below, the twin of js/hosts/hooks.mjs's) on a `loop/*`
|
|
26
|
+
// branch a loop was actually LAUNCHED on — not just named `loop/...`. It is a WHITELIST, not
|
|
27
|
+
// a blacklist: a false ask costs one prompt, a false allow publishes. It never raises the
|
|
28
|
+
// bar: anything not on the whitelist, anything off a launched loop branch, a status this hook
|
|
29
|
+
// did not itself leave as the default "ask", or any non-bash permission leaves `output.status`
|
|
30
|
+
// untouched and OpenCode's own ask (or another hook's deny) stands. A host version that never
|
|
31
|
+
// fires `permission.ask` simply never reaches this hook — the static ask still fires, so the
|
|
32
|
+
// failure mode is one extra prompt, never a silent allow.
|
|
19
33
|
|
|
20
|
-
import { promises as fs } from "node:fs"
|
|
34
|
+
import { promises as fs, existsSync, statSync, lstatSync, readFileSync, openSync, readSync, closeSync } from "node:fs"
|
|
21
35
|
import * as path from "node:path"
|
|
22
36
|
import { fileURLToPath } from "node:url"
|
|
23
37
|
import { homedir } from "node:os"
|
|
@@ -267,7 +281,131 @@ function ruleStoreTarget(p) {
|
|
|
267
281
|
// substring stance: over-matching the class is harmless (see WRITE_TOOLS note).
|
|
268
282
|
const WIKI_MUTATE_TOOLS = [...WRITE_TOOLS, "delete", "remove", "rename", "move", "trash"]
|
|
269
283
|
|
|
270
|
-
|
|
284
|
+
// ---- the loop/* exemption (Consent Before Push) --------------------------------
|
|
285
|
+
// Twin of js/hosts/gitref.mjs (currentBranch/loopLaunched) and js/hosts/hooks.mjs's
|
|
286
|
+
// git-gate (the rest) — keep in step. Node's native sync `fs` (not the `fs.promises`
|
|
287
|
+
// import above): a worktree's `.git` is read the same way the Claude/Bob hook reads it, so
|
|
288
|
+
// the two decide the exemption identically. This plugin cannot import from `js/` at all
|
|
289
|
+
// (it is copied whole into an OpenCode install, outside this repo's module graph), so it
|
|
290
|
+
// keeps its own standalone copy rather than the single shared owner the Node side has.
|
|
291
|
+
|
|
292
|
+
const GIT_GATE_RE = /\bgit\b[^\n]*\b(?:commit|push)\b/
|
|
293
|
+
|
|
294
|
+
// No newline, backtick, or `$(` substitution anywhere, no `<`/`>` redirect, and no single
|
|
295
|
+
// `&` (a shell runs `a & b` as two commands exactly like `a && b`) — these can smuggle a
|
|
296
|
+
// second command (or a shared-branch target) past every check below. `(?<!&)&(?!&)` matches
|
|
297
|
+
// a lone `&` without matching either half of a doubled `&&`.
|
|
298
|
+
const UNSAFE_CHARS_RE = /[\n`<>]|\$\(|(?<!&)&(?!&)/
|
|
299
|
+
|
|
300
|
+
// The only three git shapes a loop's own automation ever needs — see js/hosts/hooks.mjs's
|
|
301
|
+
// identically-named constants for the full rationale. The token class `[\w./:@^~=+,-]`
|
|
302
|
+
// excludes quotes, braces, `!`, `*`, `$` and `&` — a brace/glob expansion is exactly how a
|
|
303
|
+
// shell turns one whitelisted-looking token into several unknown ones, which is also why the
|
|
304
|
+
// `-F`/`--file` path uses this same class rather than the looser `[^\s'"]+` it once did: that
|
|
305
|
+
// loose class let `git commit -F {m,--amend}` brace-expand into an amend of the last commit.
|
|
306
|
+
const ARG_RE = "[\\w./:@^~=+,-]+"
|
|
307
|
+
const SEG_ADD_RE = new RegExp(`^git\\s+add(\\s+${ARG_RE})*$`)
|
|
308
|
+
const SEG_COMMIT_RE = new RegExp(`^git\\s+commit(\\s+-q)?\\s+(-F\\s+|--file[=\\s])${ARG_RE}(\\s+-q)?$`)
|
|
309
|
+
const SEG_READONLY_RE = new RegExp(`^git\\s+(status|diff|log|rev-parse|show)(\\s+${ARG_RE})*$`)
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* `git push`, exactly: `[-u|--set-upstream] <remote> HEAD:<branch>` or
|
|
313
|
+
* `HEAD:refs/heads/<branch>` — the ONLY exempt form, same as js/hosts/hooks.mjs's
|
|
314
|
+
* `pushSegmentOk`. An explicit refspec is immune to `push.default`/`remote.*.push`
|
|
315
|
+
* redirection and refuses to coexist with a mirror/`+`/`:`-prefixed form; nothing else is.
|
|
316
|
+
*/
|
|
317
|
+
function pushSegmentOk(seg, branch) {
|
|
318
|
+
const m = /^git\s+push(?:\s+(.*))?$/.exec(seg)
|
|
319
|
+
if (!m) return false
|
|
320
|
+
const rest = (m[1] || "").trim()
|
|
321
|
+
if (!rest) return false
|
|
322
|
+
const tokens = rest.split(/\s+/)
|
|
323
|
+
let i = 0
|
|
324
|
+
if (tokens[i] === "-u" || tokens[i] === "--set-upstream") i += 1
|
|
325
|
+
const remote = tokens[i]
|
|
326
|
+
if (remote === undefined || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(remote)) return false
|
|
327
|
+
i += 1
|
|
328
|
+
const ref = tokens[i]
|
|
329
|
+
i += 1
|
|
330
|
+
if (ref === undefined || i !== tokens.length) return false
|
|
331
|
+
return ref === `HEAD:${branch}` || ref === `HEAD:refs/heads/${branch}`
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const LOOP_STATE_MARKER = "<!-- loop-state:begin -->"
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Whether `<root>/LOOP.md` carries the engine's state marker — read at most 64 KB.
|
|
338
|
+
* `lstatSync` (never `statSync`, which follows a link) must find an ordinary file: a FIFO
|
|
339
|
+
* would block the read, a symlink could point anywhere outside the repo.
|
|
340
|
+
*/
|
|
341
|
+
function loopLaunched(gitRoot) {
|
|
342
|
+
let fd
|
|
343
|
+
try {
|
|
344
|
+
const p = path.join(gitRoot, "LOOP.md")
|
|
345
|
+
if (!lstatSync(p).isFile()) return false
|
|
346
|
+
fd = openSync(p, "r")
|
|
347
|
+
const buf = Buffer.alloc(65536)
|
|
348
|
+
const n = readSync(fd, buf, 0, buf.length, 0)
|
|
349
|
+
return buf.toString("utf8", 0, n).includes(LOOP_STATE_MARKER)
|
|
350
|
+
} catch {
|
|
351
|
+
return false
|
|
352
|
+
} finally {
|
|
353
|
+
if (fd !== undefined) { try { closeSync(fd) } catch { /* already gone */ } }
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The branch checked out in `cwd`, and the directory holding `.git` for it, read from
|
|
359
|
+
* .git/HEAD without spawning git — a worktree's `.git` is a FILE naming its gitdir, so both
|
|
360
|
+
* shapes are followed. `{ branch: null, root: null }` on anything unexpected (detached HEAD,
|
|
361
|
+
* no repo, unreadable): the caller then leaves the ask alone.
|
|
362
|
+
*/
|
|
363
|
+
function currentBranch(cwd) {
|
|
364
|
+
try {
|
|
365
|
+
let dir = path.resolve(cwd)
|
|
366
|
+
for (;;) {
|
|
367
|
+
const dotgit = path.join(dir, ".git")
|
|
368
|
+
if (existsSync(dotgit)) {
|
|
369
|
+
let gitdir = dotgit
|
|
370
|
+
if (statSync(dotgit).isFile()) {
|
|
371
|
+
const m = /^gitdir:\s*(.+?)\s*$/m.exec(readFileSync(dotgit, "utf8"))
|
|
372
|
+
if (!m) return { branch: null, root: null }
|
|
373
|
+
gitdir = path.resolve(dir, m[1])
|
|
374
|
+
}
|
|
375
|
+
const ref = /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(readFileSync(path.join(gitdir, "HEAD"), "utf8"))
|
|
376
|
+
return { branch: ref ? ref[1] : null, root: dir }
|
|
377
|
+
}
|
|
378
|
+
const up = path.dirname(dir)
|
|
379
|
+
if (up === dir) return { branch: null, root: null }
|
|
380
|
+
dir = up
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
return { branch: null, root: null }
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Consent Before Push's loop/* exemption — the twin of js/hosts/hooks.mjs's `loopExempt`.
|
|
389
|
+
* True only when the command carries none of the unsafe characters, every `&&`/`;`/`||`/`|`
|
|
390
|
+
* segment is one of the three whitelisted git shapes (every push segment also passing
|
|
391
|
+
* `pushSegmentOk`), and the branch checked out in `cwd` starts with `loop/` AND its git root
|
|
392
|
+
* carries a launched LOOP.md.
|
|
393
|
+
*/
|
|
394
|
+
function loopExempt(command, cwd) {
|
|
395
|
+
if (typeof command !== "string" || UNSAFE_CHARS_RE.test(command)) return false
|
|
396
|
+
const { branch, root } = currentBranch(cwd)
|
|
397
|
+
if (!branch || !branch.startsWith("loop/")) return false
|
|
398
|
+
if (!root || !loopLaunched(root)) return false
|
|
399
|
+
const segments = command.split(/&&|\|\||[;|]/).map((s) => s.trim()).filter(Boolean)
|
|
400
|
+
if (!segments.length) return false
|
|
401
|
+
return segments.every((seg) => SEG_ADD_RE.test(seg) || SEG_COMMIT_RE.test(seg)
|
|
402
|
+
|| SEG_READONLY_RE.test(seg) || pushSegmentOk(seg, branch))
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export const GeneseedGuard = async (ctx) => {
|
|
406
|
+
// The session's working directory, the same resolution the context plugin uses — a
|
|
407
|
+
// worktree session's `.git` is a FILE, which `currentBranch` already follows.
|
|
408
|
+
const root = () => ctx?.worktree || ctx?.directory || process.cwd()
|
|
271
409
|
return {
|
|
272
410
|
"tool.execute.before": async (input, output) => {
|
|
273
411
|
if (OFF) return
|
|
@@ -318,6 +456,23 @@ export const GeneseedGuard = async () => {
|
|
|
318
456
|
log(`inspect error (ignored): ${err?.message ?? err}`)
|
|
319
457
|
}
|
|
320
458
|
},
|
|
459
|
+
"permission.ask": async (input, output) => {
|
|
460
|
+
// Never throw from a permission hook — leave `output.status` untouched on any
|
|
461
|
+
// error, exactly as a crashed check here must default to the static ask standing.
|
|
462
|
+
try {
|
|
463
|
+
if (OFF) return
|
|
464
|
+
// Never loosen anything but the plain "ask" this hook itself would otherwise leave
|
|
465
|
+
// alone — a deny (or any other status another hook already set) is never overwritten.
|
|
466
|
+
if (output?.status && output.status !== "ask") return
|
|
467
|
+
if (input?.type !== "bash") return
|
|
468
|
+
// ONLY `input.metadata?.command` — no pattern/title fallback. A value this hook did
|
|
469
|
+
// not itself read in full is a value it must not reason about.
|
|
470
|
+
const command = input?.metadata?.command
|
|
471
|
+
if (typeof command !== "string" || !command || !GIT_GATE_RE.test(command)) return
|
|
472
|
+
if (await sovereignBypass(root())) return
|
|
473
|
+
if (loopExempt(command, root())) output.status = "allow"
|
|
474
|
+
} catch { /* leave output.status untouched */ }
|
|
475
|
+
},
|
|
321
476
|
}
|
|
322
477
|
}
|
|
323
478
|
|
package/bin/geneseed-cli.mjs
CHANGED
|
@@ -149,6 +149,10 @@ const VERBS = {
|
|
|
149
149
|
catalog: () => import('../js/build/catalog.mjs').then((m) => m.cmdCatalog),
|
|
150
150
|
mcp: () => import('../js/hosts/mcp.mjs').then((m) => m.cmdMcp),
|
|
151
151
|
memory: () => import('../js/maintain/memory.mjs').then((m) => m.cmdMemory),
|
|
152
|
+
// Task 5. A fourth native verb: there was never a Python subparser for a state machine that
|
|
153
|
+
// lives entirely inside this port, so — like the three above — it has no recorded cell to
|
|
154
|
+
// compare against, only the absolute gates named beside it in `NATIVE`.
|
|
155
|
+
loop: () => import('../js/loop/cli.mjs').then((m) => m.cmdLoop),
|
|
152
156
|
};
|
|
153
157
|
|
|
154
158
|
function die(code, msg) {
|
|
@@ -9,7 +9,7 @@ link: {"hash": "#/harness", "label": "Set the mode in Harness →"}
|
|
|
9
9
|
|
|
10
10
|
> **You already know this:** a tech lead with a team. Quick questions get answered on the spot; a real feature becomes a ticket that someone builds on a branch, and the lead reviews the result before it is merged.
|
|
11
11
|
|
|
12
|
-
Mode is chosen at build time, so it does not drift mid-session, and kept across every rebuild and re-theme. It is separate from [posture](collaboration.md) (the relationship register), the doctrine packs ([Rules](rules.md)) and [footprint](footprint.md).
|
|
12
|
+
Mode is chosen at build time, so it does not drift mid-session, and kept across every rebuild and re-theme. It is separate from [posture](collaboration.md) (the relationship register), the doctrine packs ([Rules](rules.md)) and [footprint](footprint.md). It is also separate from a [loop](loops.md) — a pipeline is a one-shot, uncommitted crew for a single task; a loop is a long-running state machine that commits each validated iteration on its own branch.
|
|
13
13
|
|
|
14
14
|
### direct — the default
|
|
15
15
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
group: concepts
|
|
3
|
+
order: 11
|
|
4
|
+
title: "Loops"
|
|
5
|
+
kind: "concept"
|
|
6
|
+
link: {"hash": "#/docs/reference/cli", "label": "geneseed loop --help →"}
|
|
7
|
+
---
|
|
8
|
+
A **loop** is a requirement worked as small, validated, committed iterations instead of one long
|
|
9
|
+
turn. `geneseed loop` is the state machine: it decides every transition, ceiling, score and stop,
|
|
10
|
+
and hands the agent one node at a time to run. The `loop` [skill](skills.md) is the only thing
|
|
11
|
+
that talks to it — the agent runs the node it is given and reports the outcome, never its own
|
|
12
|
+
reading of the graph.
|
|
13
|
+
|
|
14
|
+
> **You already know this:** a checklist a tech lead walks through on a ticket, where some steps
|
|
15
|
+
> need a sign-off before continuing and others don't. The lead doesn't improvise the order; the
|
|
16
|
+
> checklist decides it, and only the risky steps stop for you.
|
|
17
|
+
|
|
18
|
+
## The ring
|
|
19
|
+
|
|
20
|
+
A loop is a graph of **bricks** — one node each — wired by **edges**, one per outcome a brick can
|
|
21
|
+
report. One cycle through the graph back to its head is an **iteration**; the graph's `loops`
|
|
22
|
+
array names every cycle that is allowed to exist, each with a `max` number of times a node inside
|
|
23
|
+
it may be re-entered. Picture it as rings: the outer ring is the iteration itself (plan, apply,
|
|
24
|
+
test, review, back to plan), and inner rings sit inside it for retries (apply failed its test, try
|
|
25
|
+
`apply` again without going all the way back around). The three shipped templates —
|
|
26
|
+
`bugfix`, `feature`, `refactor` — all nest a short `apply`↔`test` retry ring inside the iteration
|
|
27
|
+
ring, and `bugfix` and `feature` nest a second, wider `apply`↔`test`↔`review` ring
|
|
28
|
+
(`review-fix`) around that.
|
|
29
|
+
|
|
30
|
+
Work closes into a commit whenever the graph re-enters the iteration loop's head — so any setup
|
|
31
|
+
work before the loop starts (reproducing a bug, say) is its own unit, numbered iteration 0, kept
|
|
32
|
+
out of iteration 1's diff. For the same reason the iteration has exactly one way in: its
|
|
33
|
+
**head** is the first node listed in the iteration loop (normally `identify`), and every edge that
|
|
34
|
+
enters the iteration from outside it — and `start`, when `start` is inside it — must target that
|
|
35
|
+
head. `geneseed loop check` refuses a graph that enters anywhere else.
|
|
36
|
+
|
|
37
|
+
**A node's re-entry budget is not each loop's `max` added up.** Every node inside one or more
|
|
38
|
+
inner loops gets a single shared counter: `1 + the largest max among the inner loops that contain
|
|
39
|
+
it`. A node in both a `max: 5` and a `max: 3` loop gets 6 re-entries total, not 8 — the loops don't
|
|
40
|
+
stack, the larger one wins and the `+1` is the node's own first run. A node inside no inner loop at
|
|
41
|
+
all gets exactly 1 — no re-entry — which is also why **every cycle inside an iteration must be a
|
|
42
|
+
named inner loop** in the graph JSON: `geneseed loop check` (and `loop init`, which runs the same
|
|
43
|
+
check) refuses a graph with an undeclared cycle inside an iteration, before a single node runs.
|
|
44
|
+
|
|
45
|
+
## Bricks, templates, and the three origins
|
|
46
|
+
|
|
47
|
+
A **brick** is a markdown file: YAML-ish frontmatter (`name`, `description`, `effect` — `read` or
|
|
48
|
+
`mutate` — exactly one of `agent` or `skill`, and `outcomes`) plus a 4-10 line prose body. The
|
|
49
|
+
model never sees the file; `geneseed loop next` hands it the body as the prompt. A **loop
|
|
50
|
+
template** (`bugfix`, `feature`, `refactor`, or one composed by hand) is the graph JSON that wires
|
|
51
|
+
bricks together.
|
|
52
|
+
|
|
53
|
+
Both are looked up across three origins, each able to override the one before it **by name**:
|
|
54
|
+
|
|
55
|
+
| origin | path | who writes it |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| shipped | `src/bricks/`, `src/loops/` | Geneseed itself — read by the CLI from its own source, never emitted into an install |
|
|
58
|
+
| global | `$XDG_CONFIG_HOME/geneseed/{bricks,loops}` (default `~/.config/geneseed/…`) | the user, for every project on this machine |
|
|
59
|
+
| project | `<repo>/.geneseed/{bricks,loops}` | a team, committed and shared |
|
|
60
|
+
|
|
61
|
+
Precedence is project > global > shipped. A project or global brick named `apply` replaces the
|
|
62
|
+
shipped `apply` everywhere a template references it, without the template changing.
|
|
63
|
+
`geneseed loop check` lists every override it finds, never silently.
|
|
64
|
+
|
|
65
|
+
A per-install skill exclusion is invisible to the CLI — it has no way to know which skills your
|
|
66
|
+
install carries. The `loop` skill covers the gap itself: when composing a graph it skips a brick
|
|
67
|
+
whose `skill` it doesn't have.
|
|
68
|
+
|
|
69
|
+
To add or adapt a brick, use the `brick-forge` skill — ask it, or see
|
|
70
|
+
[the bricks README](https://github.com/geneseed-dev/geneseed/blob/main/src/bricks/README.md) in
|
|
71
|
+
the repository.
|
|
72
|
+
|
|
73
|
+
## Risk, trust, and the three validation levels
|
|
74
|
+
|
|
75
|
+
Before a `mutate` brick runs, the loop scores the card the brick's setup declared: one risk weight
|
|
76
|
+
per action kind it named, and the score is the **max**, not a sum — one contract change outweighs
|
|
77
|
+
ten trivial renames, and ten trivial renames never add up to one. After the brick runs, the loop
|
|
78
|
+
re-scores against the real diff, and the diff can only raise the score, never lower it:
|
|
79
|
+
|
|
80
|
+
- a file touched outside the declared write set raises the score to at least **api** (`0.8`);
|
|
81
|
+
- more than 20 deleted lines raises it to at least the **delete** weight;
|
|
82
|
+
- touching a file listed in the loop's `contracts` raises it to at least the **api** weight.
|
|
83
|
+
|
|
84
|
+
`LOOP.md` itself is excluded from every score and every diff check — it's the engine's own
|
|
85
|
+
bookkeeping, rewritten on every action, never part of the work being judged.
|
|
86
|
+
|
|
87
|
+
The score is checked against a **trust preset** — `--preset` on `geneseed loop init`, editable
|
|
88
|
+
afterward in `LOOP.md`'s `preset` field, applying from the next score on:
|
|
89
|
+
|
|
90
|
+
| preset | silent ≤ | soft ≤ | above soft |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| prudent | 0.1 | 0.3 | blocking |
|
|
93
|
+
| balanced (default) | 0.2 | 0.6 | blocking |
|
|
94
|
+
| aggressive | 0.4 | 0.8 | blocking |
|
|
95
|
+
|
|
96
|
+
Three levels follow from where the score lands:
|
|
97
|
+
|
|
98
|
+
- **silent** — nothing more than the commit trailers below.
|
|
99
|
+
- **soft** — listed under "to review" in the run's final summary; no notification during the run.
|
|
100
|
+
- **blocking** — the run ends. `LOOP.md` is written with `status: awaiting`, and a person has to
|
|
101
|
+
answer with `geneseed loop decide --verdict ok|no|amend`. Amending a verdict marks the decision
|
|
102
|
+
`blocking (amended)` in the trailers rather than silently rewriting it to something else.
|
|
103
|
+
|
|
104
|
+
## `LOOP.md` and its trailers
|
|
105
|
+
|
|
106
|
+
`LOOP.md`, at the worktree root, is both the human-readable record (title, requirement, and the
|
|
107
|
+
rule that you may edit `preset` or `contracts` there) and the engine's own JSON state block. Every
|
|
108
|
+
closing commit carries seven trailers, read straight off that state:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Loop-Iteration: <n>
|
|
112
|
+
Loop-Bricks: <every brick name visited this unit, deduplicated>
|
|
113
|
+
Loop-Risk-Declared: <score>
|
|
114
|
+
Loop-Risk-Actual: <score>
|
|
115
|
+
Loop-Threshold: <preset> <silent>/<soft>
|
|
116
|
+
Loop-Decision: silent | soft | blocking[ (amended)]
|
|
117
|
+
Loop-Tests: <what the loop was told about test state>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## The `loop/*` branch, and why merge always asks
|
|
121
|
+
|
|
122
|
+
A loop runs in its own worktree, on a branch named `loop/<slug>` — the `loop` skill creates both
|
|
123
|
+
with `git worktree add -b loop/<slug>` before `geneseed loop init` writes `LOOP.md` there.
|
|
124
|
+
While that branch is checked out and `LOOP.md` carries the engine's state marker, the git gate lets
|
|
125
|
+
exactly one commit/push form through without asking:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
git add -A && git commit -F <message_file> && git push [-u] <remote> HEAD:<loop branch>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Nothing else is exempt — not `git commit -m`, not a quoted refspec, not a plain `git push` with no
|
|
132
|
+
explicit `HEAD:<ref>` — any other shape still asks. The exemption only ever covers a `loop/*`
|
|
133
|
+
branch, and never a shared one (`main`, `master`, `develop`, `development`, `release/*`,
|
|
134
|
+
`hotfix/*`). The **final** commit of a run — the one that removes `LOOP.md` on `$close` — is
|
|
135
|
+
outside the exemption too, because the loop is no longer running when it lands; that ask is your
|
|
136
|
+
consent to close it. **Merging a loop branch into anything else always asks, with no exception.**
|
|
137
|
+
The loop reports the branch to merge and the iterations that landed in "to review" — it never
|
|
138
|
+
merges for you.
|
|
139
|
+
|
|
140
|
+
## `geneseed loop` actions
|
|
141
|
+
|
|
142
|
+
| action | what it does |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `check` | lists templates and every brick (available, unavailable, overridden); validates one graph file (`--graph`) or one brick file (`--brick`) |
|
|
145
|
+
| `init` | writes a fresh `LOOP.md` from a template or graph file, under a title, requirement and trust preset |
|
|
146
|
+
| `next` | the only driver of the cycle — returns the next thing to do: run a node, score a card, answer an `awaiting`, or a terminal |
|
|
147
|
+
| `score` | scores a declared card (`--declared`) before a mutate node runs, or the real diff (`--diff`, numstat piped on stdin) before a commit |
|
|
148
|
+
| `record` | records a brick's reported outcome and advances the graph |
|
|
149
|
+
| `decide` | answers an `awaiting` validation with a verdict and a note |
|
|
150
|
+
|
|
151
|
+
Run `geneseed loop --help` for the full flag list.
|
|
152
|
+
|
|
153
|
+
## Limits
|
|
154
|
+
|
|
155
|
+
- **No parallel nodes.** The graph runs one node at a time; there is no fan-out.
|
|
156
|
+
- **A per-install skill exclusion is invisible to the CLI** (see above) — the `loop` skill is
|
|
157
|
+
responsible for skipping bricks it can't run, not the engine.
|
|
158
|
+
|
|
159
|
+
## Not foreman mode
|
|
160
|
+
|
|
161
|
+
A loop and a [foreman-mode](foreman-mode.md) pipeline are both "hand work off and keep going," but
|
|
162
|
+
they solve different problems. A pipeline is a one-shot crew for a single task, uncommitted until
|
|
163
|
+
you accept it. A loop is a long-running, self-scoring state machine that commits each validated
|
|
164
|
+
iteration on its own branch as it goes, and only stops for a person when the risk crosses a
|
|
165
|
+
threshold.
|
package/docs/extending.md
CHANGED
|
@@ -748,6 +748,11 @@ handler falls back to `index.html` at 200 with `text/html`, and the browser repo
|
|
|
748
748
|
"no running server recorded" and exits 0, and `web restart` starts a *second* daemon. Recovery is
|
|
749
749
|
to kill every pid on the port, then `geneseed web start`.
|
|
750
750
|
|
|
751
|
+
`--port N` on `web stop|restart|status` names *which* daemon: the verb acts only if the record is
|
|
752
|
+
on N, and refuses (exit 1, nothing sent) when it names another port. A test instance on another
|
|
753
|
+
port is therefore stopped with `--port`, never with a bare `web stop` — that one stops whatever
|
|
754
|
+
the record names, usually the live console on 4747.
|
|
755
|
+
|
|
751
756
|
---
|
|
752
757
|
|
|
753
758
|
## 10 — What to settle before the next wave
|
|
@@ -52,6 +52,20 @@ geneseed build --emit opencode-global --mode direct # back to the default
|
|
|
52
52
|
|
|
53
53
|
More in [Foreman mode](../concepts/foreman-mode.md).
|
|
54
54
|
|
|
55
|
+
## Loop trust: how far a loop goes before it asks
|
|
56
|
+
|
|
57
|
+
The `loop` skill scores every change it validates and stops to ask once the score passes a threshold. `--trust` sets the preset the skill starts a loop with; the user can still name another one per loop.
|
|
58
|
+
|
|
59
|
+
- **prudent**: asks early, on anything past a rename.
|
|
60
|
+
- **balanced** *(default)*: logic changes and new files pass with a note; API changes and deletions stop.
|
|
61
|
+
- **aggressive**: only an architecture-level change stops the loop.
|
|
62
|
+
|
|
63
|
+
The preset lives in the `loop` skill only, so it costs nothing in the always-on instructions.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
geneseed build --emit opencode-global --trust prudent
|
|
67
|
+
```
|
|
68
|
+
|
|
55
69
|
## Doctrine packs: which practices bind
|
|
56
70
|
|
|
57
71
|
The harness's rules come in tiers (see [Rules](../concepts/rules.md)). The Ethos and the Rules are always on. The **doctrines** are practices rather than principles, and they ship as packs you choose:
|
package/docs/guides/install.md
CHANGED
|
@@ -66,7 +66,7 @@ From a clone, every `geneseed …` command on these pages is `./geneseed …` (W
|
|
|
66
66
|
geneseed build --emit opencode-global --theme neutral
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`geneseed build` takes the same flags as the generator `geneseed-build`. `--emit` picks the host and its scope: `files`, `opencode`, `opencode-global`, `claude`, `claude-global`, `bob`, `bob-global`, `openclaude` or `openclaude-global`. A `-global` emit writes into the tool's own config directory. A project emit writes into the repo you name with `--out <repo> --root <repo>`. All the other flags (`--theme`, `--footprint`, `--posture`, `--mode`, `--doctrines`, `--exclude-rules`) are covered in [Choose your setup](choose-your-setup.md).
|
|
69
|
+
`geneseed build` takes the same flags as the generator `geneseed-build`. `--emit` picks the host and its scope: `files`, `opencode`, `opencode-global`, `claude`, `claude-global`, `bob`, `bob-global`, `openclaude` or `openclaude-global`. A `-global` emit writes into the tool's own config directory. A project emit writes into the repo you name with `--out <repo> --root <repo>`. All the other flags (`--theme`, `--footprint`, `--posture`, `--mode`, `--trust`, `--doctrines`, `--exclude-rules`) are covered in [Choose your setup](choose-your-setup.md).
|
|
70
70
|
|
|
71
71
|
To test a build before writing anything, use `geneseed validate` with the same flags. See [Verify](verify.md).
|
|
72
72
|
|
|
@@ -26,6 +26,10 @@ Every word Geneseed uses that you would not find in a general programming glossa
|
|
|
26
26
|
| Posture | — | the relationship register the agent works in (peer, mentor, expert, assistant, artisan) | pairing style |
|
|
27
27
|
| Mode | — | how work gets executed — direct (the agent works every task itself) or foreman (substantial tasks spawn an isolated pipeline) | solo vs. delegating to a sub-team |
|
|
28
28
|
| Footprint | — | how much of the Rules loads inline each turn (full vs lean) | eager vs. lazy loading |
|
|
29
|
+
| Loop | — | a requirement worked as small, validated, committed iterations on their own branch, driven by `geneseed loop` | a state machine over a ticket's steps |
|
|
30
|
+
| Iteration | — | one cycle of a loop back to its head; closes into its own commit | one sprint-of-one through a checklist |
|
|
31
|
+
| Brick | — | one node of a loop graph: a prompt for one agent or skill, reporting one of a declared set of outcomes | a step in a runbook |
|
|
32
|
+
| Trust preset | — | the loop's risk tolerance — prudent, balanced (default) or aggressive — set at `loop init` and editable in `LOOP.md` | how much a reviewer lets through without a second look |
|
|
29
33
|
| Profile | — | who you are — seeded once, colours but never binds | your onboarding notes |
|
|
30
34
|
| Memory force | — | a memory's binding strength (constraint, choice, conviction, tempered) | MUST / SHOULD / MAY |
|
|
31
35
|
| Tagline | TAGLINE | the one-line essence of the theme | — |
|
|
@@ -42,7 +42,9 @@ Every plugin swallows its own errors — none can block or crash a session. Each
|
|
|
42
42
|
|
|
43
43
|
It ships whole on every install, whatever [doctrine packs](../concepts/rules.md) you chose. On Claude Code and Bob the same checks are hooks, which can *ask* you instead of refusing; OpenCode's tool hook has no ask tier. Protected wiki folders are enforced only here — on the other hosts they are an instruction.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
A `permission.ask` hook lets through only a fixed set of commit/push forms on a `loop/*` branch with a running loop — anything else still asks, same as the Claude/Bob git-gate's loop/* exemption.
|
|
46
|
+
|
|
47
|
+
**When it runs.** Before every tool call (`tool.execute.before`), and before a static `permission.bash` ask is shown to you (`permission.ask`).
|
|
46
48
|
|
|
47
49
|
**What you see.** A refused tool call: `[geneseed-guard] blocked: <reason> — set GENESEED_GUARD=off to allow`. Warnings go to stderr.
|
|
48
50
|
|
package/docs/web-ui.md
CHANGED
|
@@ -51,7 +51,7 @@ The left rail mirrors the harness's own shape:
|
|
|
51
51
|
| Group | Views |
|
|
52
52
|
| --- | --- |
|
|
53
53
|
| *(ungrouped)* | Dashboard |
|
|
54
|
-
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Docs |
|
|
54
|
+
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Loops · Docs |
|
|
55
55
|
| **🩺 Care** | Activity · Changes · Doctor |
|
|
56
56
|
| **🎨 Setup** | Harness · Settings |
|
|
57
57
|
|
|
@@ -111,6 +111,17 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
111
111
|
**Promote to rule** control turns a recurring lesson into a trial rule in
|
|
112
112
|
`user-rules.md` — provenance recorded, the source fact deleted so it isn't loaded
|
|
113
113
|
twice — landing you on the Rules page.
|
|
114
|
+
- **Loops** (`#/loops`) — the loop catalogue `geneseed loop` runs from, read-only.
|
|
115
|
+
**Templates** draws the selected template as a ring: the iteration turning clockwise from
|
|
116
|
+
its head, each inner retry loop hung outside on its head node, the ⛨ *validate* gate the
|
|
117
|
+
engine adds before the first mutate step, setup entering from the left and `done → close`
|
|
118
|
+
leaving from the head; a card per brick sits under it, and clicking a node scrolls to its
|
|
119
|
+
card. A graph a ring cannot draw cleanly (more than eight iteration steps, a node in more
|
|
120
|
+
than three loops) falls back to left-to-right boxes. **Bricks** lists every brick with its
|
|
121
|
+
origin (shipped / global / project), what it overrides, why it is unavailable, and its
|
|
122
|
+
source. **Active** (live runs) is announced for v2 and disabled. Reads `GET /api/loops`,
|
|
123
|
+
resolved from the selected install's root, so a project install shows its
|
|
124
|
+
`.geneseed/` bricks and templates.
|
|
114
125
|
- **Profile** (`#/profile`) — `PROFILE.md` beside the deployed AGENT.md: who you are and
|
|
115
126
|
how you like to work. Opens rendered; **Edit** is a whole-file editor. The save carries
|
|
116
127
|
the fingerprint it loaded — if an agent session changed the file first, your edit stays
|
package/harness.config.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
3
|
"theme": "neutral",
|
|
4
4
|
"product": "Geneseed",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.12.0"
|
|
6
6
|
}
|
package/js/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `js/` — the module map
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sixty-one modules in eight folders. This page is the address book: **what each one owns**, and
|
|
4
4
|
**which file to open first** for a given task. It is not documentation of behaviour — every module
|
|
5
5
|
has a docblock for that, and the docblock is the thing to read before editing.
|
|
6
6
|
|
|
@@ -20,6 +20,7 @@ js/build/ src/ + themes/ → rendered text → a bundle → a per-host
|
|
|
20
20
|
js/hosts/ where it stops being a renderer and touches the machine
|
|
21
21
|
js/inspect/ the read-only half: doctor, status, diff, validate, catalog
|
|
22
22
|
js/lib/ primitives, every one a deliberate divergence from the Node default
|
|
23
|
+
js/loop/ `geneseed loop` — the state machine over LOOP.md, and what it steps through
|
|
23
24
|
js/maintain/ lifecycle verbs that change an install that already exists
|
|
24
25
|
js/ui/ what it prints at a terminal
|
|
25
26
|
js/web/ the `geneseed web` console
|
|
@@ -68,10 +69,11 @@ user co-owns, and the hook verbs those emitted configs then run.
|
|
|
68
69
|
|---|---|
|
|
69
70
|
| `hosts.mjs` | The four host config dirs (opencode/claude/bob/openclaude), plus `resolvePath`/`expanduser` |
|
|
70
71
|
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `tool-gate` (the two gates fused, for Bob), `learn`. `--host` picks the verdict dialect |
|
|
72
|
+
| `gitref.mjs` | fs/path-only: `gitRootOf`, `gitDirOf`, `currentBranch`, `loopLaunched` — read straight off `.git`, never spawn git. Shared by `hooks.mjs`'s git-gate and `js/loop/cli.mjs`'s commit-message writer |
|
|
71
73
|
| `settings.mjs` | Merges into user-owned `settings.json`/`opencode.json`; owns the hook shim and the managed blocks |
|
|
72
74
|
| `native.mjs` | Capability specs → host-native subagents and skills. The impure half of the emit |
|
|
73
75
|
| `opencode.mjs` | OpenCode-only extras: colour themes, primary agent, `/commands`, plugins, overrides stub |
|
|
74
|
-
| `installs.mjs` | Detects deployed installs and reads back their theme, footprint, posture and
|
|
76
|
+
| `installs.mjs` | Detects deployed installs and reads back their theme, footprint, posture, mode and loop trust preset |
|
|
75
77
|
| `mcp.mjs` | MCP server presets, the per-host `mcpServers` config read/write, and toggle state |
|
|
76
78
|
| `link.mjs` | `geneseed link`/`unlink` — the PATH shim, and the only Windows USER-Path registry edit |
|
|
77
79
|
|
|
@@ -125,6 +127,26 @@ float `1.0` into one double, and that value gets written into a user's file. Do
|
|
|
125
127
|
library for `udiff.mjs`: it is not Myers, ties resolve to the earliest match. Every function in
|
|
126
128
|
`paths.mjs` branches on `process.platform`, so green on one OS proves nothing about the other.
|
|
127
129
|
|
|
130
|
+
## `js/loop/` — the loop engine
|
|
131
|
+
|
|
132
|
+
`geneseed loop` — a state machine over a brick graph, persisted as a JSON block in `LOOP.md` at
|
|
133
|
+
the git root. The model only ever runs the node it is handed and reports an outcome; every
|
|
134
|
+
transition, score and stop is computed here.
|
|
135
|
+
|
|
136
|
+
| module | owns |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `graph.mjs` | `checkGraph` — the rules a loop graph must satisfy before a node runs: nodes name real bricks, edges cover every outcome, `$close` is reachable, every cycle is inside a declared loop |
|
|
139
|
+
| `score.mjs` | The risk arithmetic: `WEIGHTS`, `PRESETS`, `declaredRisk`/`actualRisk`/`decide`, `parseNumstat`. Pure — no import but node builtins |
|
|
140
|
+
| `catalog.mjs` | `loadCatalog` — bricks and loop templates from shipped/global/project origins, later overrides earlier BY NAME |
|
|
141
|
+
| `state.mjs` | The engine itself: `initState`, `nextStep`, `scoreDeclared`/`scoreDiff`, `recordOutcome`, `decideAwaiting`, `renderLoopFile`/`parseLoopFile` |
|
|
142
|
+
| `cli.mjs` | `geneseed loop check\|init\|next\|score\|record\|decide` — the only face either a model or a human reaches this engine through |
|
|
143
|
+
|
|
144
|
+
**Before editing:** `score.mjs` is imported by the build driver (the `--trust` choices), so it must
|
|
145
|
+
stay free of every import but node builtins and `graph.mjs`. `state.mjs` excludes `LOOP.md` itself
|
|
146
|
+
from every score and every read-brick porcelain check — it is the engine's own bookkeeping, never
|
|
147
|
+
part of the work being scored. `cli.mjs` may not spawn: git output is piped in on stdin by the
|
|
148
|
+
model, never read by this entry.
|
|
149
|
+
|
|
128
150
|
## `js/maintain/` — lifecycle
|
|
129
151
|
|
|
130
152
|
Verbs that change an install that already exists, as opposed to rendering one.
|