Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
3 KiB
Phase 2 — Entities + relations matrix
Collect the entity list (typed) once, then click the relations matrix. This is the second (and last) phase that accepts typed input.
2a — Ask for entities (plain message, NOT AskUserQuestion)
List the entities (tables) and for each a short comma-separated field list. One entity per line, format:
<Entity>: field1, field2, .... Example:User: email, name, created_at Organization: name, plan Membership: user_id, org_id, role3–15 entities is typical. Keep it short — we'll refine fields in Phase 3.
Parse the reply into ENTITIES = [{name, fields: [...]}, ...]. Validate:
- Entity names are
PascalCase(normalize if user typesuser_profile→UserProfile, record normalization in state). - Each entity has ≥1 field.
- No duplicate entity names.
- If parse fails → re-ask once with a corrected example.
2b — Relations matrix click (AskUserQuestion, multi-select)
For each UNORDERED PAIR of entities (A, B), ask one multi-select row.
Skip pairs the user hasn't mentioned any cross-reference for (heuristic:
if A's fields include b_id or B's fields include a_id, or the
user's intake paragraph mentions both).
Build ONE AskUserQuestion call with up to 5 questions. If the entity
count yields > 5 candidate pairs, batch into multiple calls (still counts
toward the ≥1 AskUserQuestion minimum).
Per-pair question template:
{
"question": "Relation between <A> and <B>?",
"header": "<A>↔<B>",
"multiSelect": false,
"options": [
{"label": "None", "description": "No direct FK; entities are independent"},
{"label": "One-to-one", "description": "A.b_id UNIQUE FK to B.id (or vice versa)"},
{"label": "One-to-many (A→B)", "description": "B.a_id FK to A.id; one A has many B"},
{"label": "One-to-many (B→A)", "description": "A.b_id FK to B.id; one B has many A"},
{"label": "Many-to-many", "description": "Requires a junction table; skill will auto-name it <A><B>"}
]
}
Store the result in ENTITIES as .relations = [{from, to, kind}, ...].
2c — Auto-generate junction tables
For each pair marked Many-to-many, append a synthetic entity to
ENTITIES:
<A><B>:
<a>_id FK → <A>.id (ON DELETE CASCADE)
<b>_id FK → <B>.id (ON DELETE CASCADE)
PRIMARY KEY (<a>_id, <b>_id)
created_at TIMESTAMPTZ DEFAULT now()
Names must be deterministic (alphabetical order: OrganizationUser, not
UserOrganization, for pair (User, Organization)). Record the rule in
state so Phase 3 renders it consistently.
Verify-criterion
ENTITIEShas ≥1 entry after parse.- Every relation in
ENTITIES[*].relationsreferences two distinct existing entities. - Every
Many-to-manyhas produced a junction entity. - No entity is orphaned (zero relations AND not mentioned in INTAKE) —
warn the user with "Entity X has no relations; keep it?" (NO DOWNGRADE:
offer
keep / drop / add relationas follow-up click).