@eventmodelers/cli 1.0.163 → 2.0.1
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/README.md +22 -27
- package/RELEASE_NOTES.md +20 -2
- package/cli.js +461 -225
- package/lib/adapters/spec-kitty-adapter.js +3 -7
- package/lib/global-agent-id.js +45 -0
- package/lib/modeling-local-ai.js +4 -2
- package/package.json +1 -1
- package/shared/build-kit/README.md +4 -19
- package/shared/build-kit/lib/agent-lock.js +75 -0
- package/shared/build-kit/lib/agent-name.js +27 -0
- package/shared/build-kit/lib/event-prune.js +58 -0
- package/shared/build-kit/lib/events-view.js +121 -0
- package/shared/build-kit/lib/local-ai-agent.js +14 -26
- package/shared/build-kit/lib/mcp-register.js +17 -1
- package/shared/build-kit/lib/prompt-journal.js +191 -0
- package/shared/build-kit/lib/prompt-replay.js +35 -0
- package/shared/build-kit/lib/ralph.js +201 -156
- package/shared/build-kit/lib/slice-curation.js +40 -0
- package/shared/build-kit/lib/slice-events.js +93 -0
- package/shared/build-kit/lib/slice-git.js +86 -0
- package/shared/build-kit/lib/slice-learnings.js +197 -0
- package/shared/build-kit/lib/slice-projection.js +101 -0
- package/shared/build-kit/lib/slice-run.js +181 -0
- package/shared/build-kit/lib/slice-state.js +182 -0
- package/shared/build-kit/lib/slice-tasks.js +35 -0
- package/shared/build-kit/ralph-claude.js +4 -2
- package/shared/build-kit/ralph-exec.js +4 -2
- package/shared/build-kit/ralph-local-ai.js +5 -3
- package/shared/skills/learn-eventmodelers-api/SKILL.md +13 -0
- package/shared/skills/request-feedback/SKILL.md +1 -2
- package/shared/skills/ui-contract/SKILL.md +5 -3
- package/stacks/axon/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/axon/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/axon/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/axon/templates/build-kit/lib/prompt.md +18 -41
- package/stacks/blank/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/blank/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/blank/templates/build-kit/lib/backend-prompt.md +20 -37
- package/stacks/blank/templates/build-kit/lib/prompt.md +19 -42
- package/stacks/classic/templates/.claude/skills/build-automation/SKILL.md +1 -1
- package/stacks/classic/templates/.claude/skills/build-state-change/SKILL.md +1 -1
- package/stacks/classic/templates/.claude/skills/build-state-view/SKILL.md +2 -2
- package/stacks/classic/templates/.claude/skills/record-evidence/SKILL.md +2 -2
- package/stacks/classic/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/classic/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/classic/templates/build-kit/lib/backend-prompt.md +20 -38
- package/stacks/classic/templates/build-kit/lib/prompt.md +19 -42
- package/stacks/kurrent/templates/build-kit/CLAUDE.md +3 -4
- package/stacks/kurrent/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/kurrent/templates/build-kit/lib/prompt.md +18 -41
- package/stacks/modeling-kit/templates/.claude/skills/detect-model-drift/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-core-rules/SKILL.md +2 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +7 -3
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +3 -4
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +9 -24
- package/stacks/modeling-kit/templates/kit/README.md +1 -1
- package/stacks/modeling-kit/templates/kit/lib/config.js +1 -1
- package/stacks/node/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/node/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/node/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/node/templates/build-kit/lib/prompt.md +18 -41
- package/stacks/opencqrs/templates/build-kit/CLAUDE.md +3 -4
- package/stacks/opencqrs/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/opencqrs/templates/build-kit/lib/prompt.md +18 -41
- package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +1 -1
- package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +1 -1
- package/stacks/react/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/react/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/react/templates/build-kit/lib/backend-prompt.md +20 -37
- package/stacks/react/templates/build-kit/lib/prompt.md +19 -42
- package/stacks/supabase/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/supabase/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/supabase/templates/build-kit/lib/prompt.md +18 -41
- package/stacks/supabase-react/templates/.claude/skills/build-state-change/SKILL.md +2 -2
- package/stacks/supabase-react/templates/.claude/skills/build-state-view/SKILL.md +2 -2
- package/stacks/supabase-react/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/supabase-react/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +20 -37
- package/stacks/supabase-react/templates/build-kit/lib/prompt.md +19 -42
- package/stacks/umadb/templates/build-kit/CLAUDE.md +3 -3
- package/stacks/umadb/templates/build-kit/lib/AGENT.md +6 -7
- package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +20 -42
- package/stacks/umadb/templates/build-kit/lib/prompt.md +18 -41
- package/shared/bridge/spec-kitty/bridge-spec-kitty-specify/SKILL.md +0 -125
- package/shared/bridge/spec-kitty/bridge-spec-kitty-tasks/SKILL.md +0 -95
- package/shared/build-kit/lib/agent.sh +0 -20
- package/shared/build-kit/ralph.sh +0 -111
- package/shared/build-kit/realtime-agent.js +0 -20
- package/stacks/bridge/templates/bridge/lib/AGENT.md +0 -47
- package/stacks/bridge/templates/bridge/lib/prompt.md +0 -111
- package/stacks/bridge/templates/bridge/ralph-claude.js +0 -50
- package/stacks/bridge/templates/bridge/ralph-hook.js +0 -103
- package/stacks/bridge/templates/bridge/ralph-local-ai.js +0 -43
- package/stacks/bridge/templates/bridge/ralph-static.js +0 -90
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: bridge-spec-kitty-specify
|
|
3
|
-
description: "Adapter: generate a Spec Kitty mission spec.md (and auto-evaluate its quality checklist) from an Eventmodelers board's exported event model, instead of running the /spec-kitty.specify interview. Use when the user says \"use the event model as the spec\", \"sync the board to spec-kitty\", \"generate the spec from the event model\", or when a .slices/ export exists and a Spec Kitty mission needs to start or refresh from it."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# bridge-spec-kitty-specify
|
|
7
|
-
|
|
8
|
-
Replaces the human interview in `/spec-kitty.specify` with a direct read of an
|
|
9
|
-
Eventmodelers board export. The event model is the source of truth; `spec.md`
|
|
10
|
-
becomes a **generated, read-only artifact** derived from it. Everything
|
|
11
|
-
downstream of specify (`/spec-kitty.plan`, `/spec-kitty.tasks`,
|
|
12
|
-
implement/review/merge) is untouched — this skill only produces the two
|
|
13
|
-
artifacts that phase gate expects: `spec.md` and `checklists/requirements.md`.
|
|
14
|
-
|
|
15
|
-
Do not use this to hand-author or hand-edit `spec.md` — if the event model is
|
|
16
|
-
wrong, fix it on the board and re-run this skill. A `spec.md` produced here
|
|
17
|
-
that gets manually edited afterward will be silently overwritten on the next
|
|
18
|
-
sync; there is no merge/diff in this version (see Limitations).
|
|
19
|
-
|
|
20
|
-
## Inputs
|
|
21
|
-
|
|
22
|
-
The Eventmodelers `listen` server (`npx @eventmodelers/cli listen`) writes the
|
|
23
|
-
board export locally. Read it directly — do not call the board API:
|
|
24
|
-
|
|
25
|
-
- `.slices/current_context.json` → `{ "name": "<context>" }` — which context
|
|
26
|
-
to ingest if the user didn't name one.
|
|
27
|
-
- `.slices/<context>/index.json` → `{ "slices": [{ id, slice, index, context,
|
|
28
|
-
folder, status, group }] }` — the ordered slice list.
|
|
29
|
-
- `.slices/<context>/context.json` → context-level name/package info.
|
|
30
|
-
- `.slices/<context>/<folder>/slice.json` → full slice detail per the
|
|
31
|
-
[event-modeling-spec](https://github.com/dilgerma/event-modeling-spec)
|
|
32
|
-
contract: `commands[]`, `events[]`, `readModels[]` (projections/queries),
|
|
33
|
-
`screens[]`, `automations[]`/`processors[]`, `specifications[]` (given/when/
|
|
34
|
-
then), plus free-text `description`/`notes`.
|
|
35
|
-
|
|
36
|
-
If `.slices/` doesn't exist yet, stop and tell the user to run
|
|
37
|
-
`npx @eventmodelers/cli listen` (or `init-modeling` first if the kit isn't
|
|
38
|
-
installed) and push the board export before retrying.
|
|
39
|
-
|
|
40
|
-
## Flow
|
|
41
|
-
|
|
42
|
-
1. **Resolve the mission.** If an existing `kitty-specs/<slug>/meta.json`
|
|
43
|
-
already points at this context (check `purpose_tldr`/a stored context
|
|
44
|
-
reference), re-sync into it. Otherwise create a new mission dir the same
|
|
45
|
-
way `/spec-kitty.specify` does: kebab-case name + 8-char ULID suffix,
|
|
46
|
-
`mission_type: software-dev` (this adapter does not introduce a new
|
|
47
|
-
mission type — see project memory on why). Write `meta.json` matching the
|
|
48
|
-
shape in an existing mission (`mission_id`, `mission_slug`, `purpose_tldr`,
|
|
49
|
-
`purpose_context`, `target_branch`, etc.).
|
|
50
|
-
2. **Load every slice** referenced in `index.json` by reading each
|
|
51
|
-
`<folder>/slice.json`. Do not skip slices with empty `specifications` —
|
|
52
|
-
surface them as gaps in the checklist instead (step 4).
|
|
53
|
-
3. **Compose `spec.md`** using the standard Spec Kitty template sections,
|
|
54
|
-
sourced only from what the model actually states — never invent detail
|
|
55
|
-
the board doesn't have:
|
|
56
|
-
- **Purpose** ← `context.json` name/description, or ask the user for a
|
|
57
|
-
one-paragraph purpose if the board has none.
|
|
58
|
-
- **Primary User Story** ← walk the slices in `index.json` order (this is
|
|
59
|
-
already the chronological/timeline order from the board) and narrate
|
|
60
|
-
the SCREEN → COMMAND → EVENT → READMODEL flow across them.
|
|
61
|
-
- **Acceptance Scenarios** ← one bullet per `specifications[]` entry,
|
|
62
|
-
copied straight from its given/when/then — do not paraphrase away
|
|
63
|
-
precision the model already captured.
|
|
64
|
-
- **Edge Cases** ← `specifications[]` entries that describe rejection/
|
|
65
|
-
negative/boundary behavior (reject, invalid, already-exists, etc.).
|
|
66
|
-
- **Domain Language** ← distinct nouns from event/command/read-model field
|
|
67
|
-
names and titles, deduped.
|
|
68
|
-
- **Functional Requirements (FR-###)** ← one row per COMMAND (state-change
|
|
69
|
-
slice) and per AUTOMATION slice; wording from the command/automation
|
|
70
|
-
name + `description`.
|
|
71
|
-
- **Non-Functional Requirements / Constraints** ← only from explicit
|
|
72
|
-
`notes`/`description` content tagged as timing/authorization/volume
|
|
73
|
-
constraints. If the model states none, leave the table empty — do not
|
|
74
|
-
fabricate NFRs to fill it.
|
|
75
|
-
- **Key Entities** ← distinct read-model/event payload shapes.
|
|
76
|
-
- **Interfaces to Other Teams** ← any `TRANSLATION`-type slice or external
|
|
77
|
-
system mentioned in a slice's `description`.
|
|
78
|
-
4. **Evaluate `checklists/requirements.md`** against the ingested model
|
|
79
|
-
instead of a human review pass. Check an item only if the model
|
|
80
|
-
demonstrably satisfies it:
|
|
81
|
-
- "No [NEEDS CLARIFICATION] markers remain" → fails if any slice has an
|
|
82
|
-
empty `specifications[]` or a `description` containing `TODO`/`TBD`.
|
|
83
|
-
- "Requirements are testable and unambiguous" → fails if an FR/NFR row has
|
|
84
|
-
no backing `specifications[]` entry.
|
|
85
|
-
- "Non-functional requirements include measurable thresholds" → fails
|
|
86
|
-
(leave unchecked, don't skip the row) if the NFR table is empty but the
|
|
87
|
-
model contains AUTOMATION slices implying a timing constraint the board
|
|
88
|
-
never made explicit.
|
|
89
|
-
- Leave any check unchecked with a `## Notes` explanation when the event
|
|
90
|
-
model genuinely doesn't provide enough to verify it — this checklist
|
|
91
|
-
gates `/spec-kitty.plan`, so a false pass defeats the point of the gate.
|
|
92
|
-
5. **Hand off.** Once `spec.md` + `checklists/requirements.md` are written,
|
|
93
|
-
tell the user the mission is ready for `/spec-kitty.plan` as normal — do
|
|
94
|
-
not run plan/tasks yourself from this skill.
|
|
95
|
-
|
|
96
|
-
## Re-sync behavior
|
|
97
|
-
|
|
98
|
-
Re-running this skill against the same mission regenerates `spec.md` and
|
|
99
|
-
`checklists/requirements.md` wholesale from the current `.slices/` state.
|
|
100
|
-
There is no field-level diff against the previous generation — if the spec
|
|
101
|
-
needs to reflect board changes, re-run this skill rather than hand-patching
|
|
102
|
-
`spec.md`.
|
|
103
|
-
|
|
104
|
-
## Guardrail
|
|
105
|
-
|
|
106
|
-
If a required section can't be populated from the event model (e.g. no
|
|
107
|
-
Purpose anywhere on the board), ask the user rather than inventing one — an
|
|
108
|
-
invented Purpose defeats the reason this adapter exists (the event model,
|
|
109
|
-
not the LLM, is the source of truth for product intent).
|
|
110
|
-
|
|
111
|
-
## Limitations (by design, v1)
|
|
112
|
-
|
|
113
|
-
- Does not itself generate `tasks/WP##-*.md` from slices — see the companion
|
|
114
|
-
`bridge-spec-kitty-tasks` skill, which enforces a 1:1
|
|
115
|
-
slice-to-WP mapping when `wps.yaml` is written during
|
|
116
|
-
`/spec-kitty.tasks-outline`.
|
|
117
|
-
- Does not introduce a new Spec Kitty mission type. Spec Kitty 3.2.5's
|
|
118
|
-
built-in mission types are loaded from a flat directory inside the
|
|
119
|
-
installed package (`doctrine/missions/mission_types/*.yaml`) via
|
|
120
|
-
`MissionTypeRepository.default()`, and org-pack extension for the
|
|
121
|
-
`mission_types` kind is unwired in this version (fragments only add
|
|
122
|
-
governance-graph metadata, not an executable type). A real native mission
|
|
123
|
-
type would require patching the installed `spec-kitty-cli` package itself.
|
|
124
|
-
This adapter avoids that entirely — it stays inside `mission_type:
|
|
125
|
-
software-dev` and only replaces the specify phase's *inputs*.
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: bridge-spec-kitty-tasks
|
|
3
|
-
description: "Adapter: enforce a 1:1 mapping between Eventmodelers board slices and Spec Kitty work packages when writing wps.yaml. Use when the user says \"one slice should become one task/WP\", \"generate tasks from the event model\", or right after bridge-spec-kitty-specify, in place of freeform subtask-grouping judgment in /spec-kitty.tasks-outline."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# bridge-spec-kitty-tasks
|
|
7
|
-
|
|
8
|
-
Companion to `bridge-spec-kitty-specify`. That skill turns the event model into
|
|
9
|
-
`spec.md`; this one enforces that each **slice** on the board becomes exactly
|
|
10
|
-
**one work package** — never split across WPs, never merged with another
|
|
11
|
-
slice — when Spec Kitty's task-planning pipeline builds `wps.yaml`.
|
|
12
|
-
|
|
13
|
-
This replaces only step 3–6 of `/spec-kitty.tasks-outline` (deriving
|
|
14
|
-
subtasks and rolling them into WPs). `/spec-kitty.tasks-packages` and
|
|
15
|
-
`/spec-kitty.tasks-finalize` run completely unmodified afterward — they only
|
|
16
|
-
ever consume `wps.yaml`, and don't care where its groupings came from.
|
|
17
|
-
|
|
18
|
-
## Why a hard 1:1 boundary
|
|
19
|
-
|
|
20
|
-
Spec Kitty's default `tasks-outline` groups subtasks into WPs by
|
|
21
|
-
judgment (target 3–7 subtasks, merge small ones, split large ones). That's
|
|
22
|
-
the right default when subtask boundaries come from an LLM reading prose. It
|
|
23
|
-
is the *wrong* default here: the event model already drew the real
|
|
24
|
-
boundaries (each SLICE_BORDER is a reviewable, independently buildable unit
|
|
25
|
-
the board's `Planned → InProgress → Done` slice lifecycle already governs).
|
|
26
|
-
Re-grouping at the WP layer would create a second, competing notion of "unit
|
|
27
|
-
of work" that drifts from the board over time. So this skill trades away
|
|
28
|
-
`tasks-outline`'s sizing flexibility on purpose: WP boundary = slice
|
|
29
|
-
boundary, full stop.
|
|
30
|
-
|
|
31
|
-
## Inputs
|
|
32
|
-
|
|
33
|
-
Same event-model export as `bridge-spec-kitty-specify`:
|
|
34
|
-
`.slices/<context>/index.json` (slice list, board/timeline order) and each
|
|
35
|
-
`.slices/<context>/<folder>/slice.json` (commands, events, readModels,
|
|
36
|
-
automations, `specifications`). Also read, from `feature_dir`:
|
|
37
|
-
`spec.md` (for FR/NFR ids — already generated by `bridge-spec-kitty-specify`) and
|
|
38
|
-
`plan.md` (for the tech-stack/file-layout context needed to fill
|
|
39
|
-
`owned_files`, since the event model doesn't know the codebase's directory
|
|
40
|
-
structure).
|
|
41
|
-
|
|
42
|
-
## Flow
|
|
43
|
-
|
|
44
|
-
1. **One WP per slice, in board order.** Assign `WP01`, `WP02`, ... following
|
|
45
|
-
`index.json`'s order (already the board's chronological/timeline order).
|
|
46
|
-
Do not reorder for "phase grouping" the way default `tasks-outline` does —
|
|
47
|
-
the board order already encodes the intended build sequence.
|
|
48
|
-
2. **Subtasks come from inside the slice, not across slices.** For each
|
|
49
|
-
slice, break its own commands/read-models/screens/automations into T-ids
|
|
50
|
-
(one per command handler, one per read-model endpoint, one per
|
|
51
|
-
screen/automation, one for its tests) — all nested under that slice's one
|
|
52
|
-
WP. Every subtask must trace to exactly one slice; never let a subtask
|
|
53
|
-
span two slices.
|
|
54
|
-
3. **Dependencies from event flow, not guesswork.** WP *B* depends on WP *A*
|
|
55
|
-
iff slice B's commands/queries/automations consume an event that slice
|
|
56
|
-
A's commands/automations produce. Derive this by diffing each slice's
|
|
57
|
-
emitted event names against every other slice's consumed event names —
|
|
58
|
-
this is mechanical, not judgment-based, so don't also apply
|
|
59
|
-
`tasks-outline`'s "Phase 2 typically depends on Phase 1" heuristic on top
|
|
60
|
-
of it.
|
|
61
|
-
4. **`requirement_refs` from the spec's own FR/NFR table.** Match each
|
|
62
|
-
slice's command/read-model/automation names against `spec.md`'s FR/NFR
|
|
63
|
-
rows (written by `bridge-spec-kitty-specify` from these same slices) and cite
|
|
64
|
-
whichever ids trace to this slice. A slice with no matching row means
|
|
65
|
-
`bridge-spec-kitty-specify` needs a re-run, not a fabricated ref here.
|
|
66
|
-
5. **Write `wps.yaml`** using the exact schema `/spec-kitty.tasks-outline`
|
|
67
|
-
expects (`id`, `title`, `dependencies`, `owned_files`, `requirement_refs`,
|
|
68
|
-
`plan_concern_refs` or `cross_cutting: true`, `subtasks`, `prompt_file:
|
|
69
|
-
null`) — so `tasks-packages`/`finalize-tasks` need no awareness that this
|
|
70
|
-
skill (rather than default `tasks-outline`) produced it.
|
|
71
|
-
6. **Hand off.** Tell the user to continue with `/spec-kitty.tasks-packages`
|
|
72
|
-
then `/spec-kitty.tasks-finalize` (or `spec-kitty next`) exactly as normal.
|
|
73
|
-
|
|
74
|
-
## Guardrail: don't fix boundary problems here
|
|
75
|
-
|
|
76
|
-
If a slice's own scope would blow past Spec Kitty's WP sizing ceiling (>10
|
|
77
|
-
subtasks / >700 lines), **do not silently split it into two WPs** — that
|
|
78
|
-
breaks the 1:1 invariant this skill exists to enforce, and produces the
|
|
79
|
-
exact spec/code drift it's meant to prevent. Instead, flag it to the user:
|
|
80
|
-
the slice is too coarse on the board itself, so split the SLICE_BORDER
|
|
81
|
-
there and re-run both `bridge-spec-kitty-specify` and this skill. The event model
|
|
82
|
-
stays the single place slice boundaries get decided.
|
|
83
|
-
|
|
84
|
-
The same holds in the other direction: never merge two small slices into
|
|
85
|
-
one WP for tidiness, even if `tasks-outline`'s default guidance would.
|
|
86
|
-
|
|
87
|
-
## Limitations (v1)
|
|
88
|
-
|
|
89
|
-
- `owned_files`/file-layout ownership still comes from `plan.md`, which is
|
|
90
|
-
still produced by the normal, LLM-authored `/spec-kitty.plan` — the event
|
|
91
|
-
model has no notion of source-file structure, so this isn't something to
|
|
92
|
-
push onto it.
|
|
93
|
-
- Re-running this skill after the board changes regenerates `wps.yaml`
|
|
94
|
-
wholesale from the current slice set, same as `bridge-spec-kitty-specify`'s
|
|
95
|
-
re-sync behavior — no field-level diff against a prior run.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Runs the AI agent with the given prompt in the project directory.
|
|
3
|
-
# Called by ralph.sh with cwd already set to the project root.
|
|
4
|
-
# Usage: ./agent.sh "<prompt>"
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
PROMPT="${1:-}"
|
|
9
|
-
if [[ -z "$PROMPT" ]]; then
|
|
10
|
-
echo "ERROR: No prompt provided"
|
|
11
|
-
exit 1
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
claude --dangerously-skip-permissions -p "$PROMPT"
|
|
15
|
-
|
|
16
|
-
# --- To use a local Ollama model instead, comment out the line above
|
|
17
|
-
# and uncomment the block below. Run `ollama serve` first.
|
|
18
|
-
#
|
|
19
|
-
# MODEL="${OLLAMA_MODEL:-qwen3.5:9b}"
|
|
20
|
-
# node "$(dirname "$0")/ollama-agent.js" "$MODEL"
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Ralph agent loop — two independent loops, each triggered by their own condition
|
|
3
|
-
#
|
|
4
|
-
# onTask: tasks.json has entries → load slice from board, update .build-kit/.slices/
|
|
5
|
-
# onPlannedSlice: .build-kit/.slices/ has a "Planned" slice → build it
|
|
6
|
-
#
|
|
7
|
-
# The loops are NOT causally linked — either can trigger on its own.
|
|
8
|
-
#
|
|
9
|
-
# Usage: ./ralph.sh [iterations] [project_dir]
|
|
10
|
-
# iterations — number of loop cycles to run; 0 or omitted means run forever
|
|
11
|
-
# project_dir — path to the project root; defaults to the parent of .build-kit
|
|
12
|
-
|
|
13
|
-
set -euo pipefail
|
|
14
|
-
|
|
15
|
-
KIT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
-
ITERATIONS="${1:-0}"
|
|
17
|
-
PROJECT_DIR="${2:-"$KIT_DIR/.."}"
|
|
18
|
-
TASKS_FILE="$KIT_DIR/tasks.json"
|
|
19
|
-
PROMPT_FILE="$KIT_DIR/lib/prompt.md"
|
|
20
|
-
BACKEND_PROMPT_FILE="$KIT_DIR/lib/backend-prompt.md"
|
|
21
|
-
AGENT_SCRIPT="$KIT_DIR/lib/agent.sh"
|
|
22
|
-
|
|
23
|
-
HAS_CREDENTIALS=true
|
|
24
|
-
if [[ "${RALPH_LOCAL:-}" == "1" ]]; then
|
|
25
|
-
echo "[ralph] --local — platform sync disabled." >&2
|
|
26
|
-
HAS_CREDENTIALS=false
|
|
27
|
-
elif [[ ! -f "$KIT_DIR/.eventmodelers/config.json" ]]; then
|
|
28
|
-
echo "[ralph] Note: no .eventmodelers/config.json found — platform sync disabled." >&2
|
|
29
|
-
echo " To enable board sync, follow: https://app.eventmodelers.ai/documentation#build" >&2
|
|
30
|
-
echo " Code generation from local slice definitions will still run." >&2
|
|
31
|
-
HAS_CREDENTIALS=false
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
echo "Ralph — kit: $KIT_DIR project: $PROJECT_DIR"
|
|
35
|
-
|
|
36
|
-
# Returns 0 if tasks.json has at least one task
|
|
37
|
-
has_pending_tasks() {
|
|
38
|
-
[[ -f "$TASKS_FILE" ]] || return 1
|
|
39
|
-
local content
|
|
40
|
-
content=$(cat "$TASKS_FILE")
|
|
41
|
-
[[ "$content" != "[]" && -n "$content" ]]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
# Status this loop picks work up from — `run --status-trigger <status>` (RALPH_STATUS_TRIGGER), default Planned
|
|
45
|
-
PICKUP_STATUS="${RALPH_STATUS_TRIGGER:-Planned}"
|
|
46
|
-
|
|
47
|
-
# Returns 0 if any JSON under .build-kit/.slices/ contains a slice in the pickup status
|
|
48
|
-
has_planned_slices() {
|
|
49
|
-
grep -rqi "\"status\"[[:space:]]*:[[:space:]]*\"$PICKUP_STATUS\"" "$KIT_DIR/.slices/" --include='index.json' 2>/dev/null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# Returns the title of the first "Planned" slice, or empty string
|
|
53
|
-
get_planned_slice_title() {
|
|
54
|
-
for index_file in "$KIT_DIR/.slices/"*/index.json; do
|
|
55
|
-
[[ -f "$index_file" ]] || continue
|
|
56
|
-
local title
|
|
57
|
-
title=$(node -e "
|
|
58
|
-
try {
|
|
59
|
-
const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf-8'));
|
|
60
|
-
const s = (d.slices||[]).find(s => (s.status||'').toLowerCase() === process.argv[2].toLowerCase());
|
|
61
|
-
if (s) process.stdout.write(s.slice || s.id || '');
|
|
62
|
-
} catch(e) {}
|
|
63
|
-
" "$index_file" "$PICKUP_STATUS" 2>/dev/null)
|
|
64
|
-
if [[ -n "$title" ]]; then
|
|
65
|
-
echo "$title"
|
|
66
|
-
return
|
|
67
|
-
fi
|
|
68
|
-
done
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# prompt.md and backend-prompt.md are instruction manuals with no request in them — handed one
|
|
72
|
-
# alone, the agent may read it as background and ask what to do. Every turn ends with the concrete
|
|
73
|
-
# request, the same one lib/ralph.js's taskRequest() appends.
|
|
74
|
-
task_request() {
|
|
75
|
-
printf '\n\n---\n\nYour task now: %s, following the instructions above. Start immediately.\n' "$1"
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
# Runs agent.sh with the given prompt; retries on non-zero exit
|
|
79
|
-
run_agent() {
|
|
80
|
-
local label="$1"
|
|
81
|
-
local prompt="$2"
|
|
82
|
-
while true; do
|
|
83
|
-
echo "[$(date -u +%H:%M:%S)] $label"
|
|
84
|
-
(cd "$PROJECT_DIR" && bash "$AGENT_SCRIPT" "$prompt") 2>&1 && return 0
|
|
85
|
-
echo "[$(date -u +%H:%M:%S)] Agent error — retrying in 60s..."
|
|
86
|
-
sleep 60
|
|
87
|
-
done
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
cycle=0
|
|
91
|
-
while [[ "$ITERATIONS" -eq 0 || "$cycle" -lt "$ITERATIONS" ]]; do
|
|
92
|
-
ran_something=false
|
|
93
|
-
|
|
94
|
-
if [[ "$HAS_CREDENTIALS" == true ]] && has_pending_tasks; then
|
|
95
|
-
run_agent "onTask: loading slice from board..." "$(cat "$PROMPT_FILE"; task_request 'process the pending tasks in tasks.json')"
|
|
96
|
-
ran_something=true
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
if has_planned_slices; then
|
|
100
|
-
slice_title=$(get_planned_slice_title)
|
|
101
|
-
run_agent "onPlannedSlice: building \"$slice_title\"..." "$(cat "$BACKEND_PROMPT_FILE"; task_request "build the $PICKUP_STATUS slice \"$slice_title\"")"
|
|
102
|
-
echo "[$(date -u +%H:%M:%S)] Slice \"$slice_title\" build complete — waiting for next slice"
|
|
103
|
-
ran_something=true
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
if [[ "$ran_something" == false ]]; then
|
|
107
|
-
sleep 3
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
(( cycle++ )) || true
|
|
111
|
-
done
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Standalone realtime agent — subscribes to board events and writes tasks.json.
|
|
3
|
-
// The same logic runs embedded inside ralph-claude.js / ralph-local-ai.js, so you
|
|
4
|
-
// only need this if you want to run the agent independently (e.g. separate terminal).
|
|
5
|
-
// Usage: node realtime-agent.js [kit_dir]
|
|
6
|
-
|
|
7
|
-
import { loadLocalConfig, fetchPlatformConfig, retryTransient, startRealtimeAgent } from './lib/ralph.js';
|
|
8
|
-
import { redactConsole } from './lib/redact.js';
|
|
9
|
-
import { dirname, resolve } from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
const kitDir = process.argv[2] ? resolve(process.argv[2]) : dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
14
|
-
const local = loadLocalConfig(kitDir);
|
|
15
|
-
redactConsole([local.token]);
|
|
16
|
-
const cfg = await retryTransient('fetchPlatformConfig', () => fetchPlatformConfig(local));
|
|
17
|
-
|
|
18
|
-
console.log(`[agent] Starting — org=${cfg.organizationId}${cfg.boardId ? `, board=${cfg.boardId}` : ''}, base=${cfg.baseUrl}`);
|
|
19
|
-
|
|
20
|
-
await startRealtimeAgent(cfg, kitDir);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# Agent Learnings
|
|
2
|
-
|
|
3
|
-
Patterns and gotchas discovered during task processing. Update this file
|
|
4
|
-
whenever you encounter something reusable.
|
|
5
|
-
|
|
6
|
-
## tasks.json
|
|
7
|
-
|
|
8
|
-
- Tasks are objects with `id`, `createdAt`, and `payload` (a
|
|
9
|
-
`SliceChangedPayload`).
|
|
10
|
-
- Unlike build-kit, every status change is queued (not just non-`Planned`
|
|
11
|
-
ones) — see `queueAllStatuses` in `lib/ralph.js`.
|
|
12
|
-
- After completing a task, remove it from the array entirely — do not add a
|
|
13
|
-
status field.
|
|
14
|
-
- Write `[]` to `tasks.json` if the last task is completed.
|
|
15
|
-
|
|
16
|
-
## SliceChangedPayload fields
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
event always "slice:changed"
|
|
20
|
-
organizationId org UUID or null
|
|
21
|
-
boardId board UUID
|
|
22
|
-
sliceId SLICE_BORDER node UUID
|
|
23
|
-
sliceTitle human-readable slice name (may be null)
|
|
24
|
-
sliceStatus e.g. "Created", "Planned", "InProgress", "Done" (may be null)
|
|
25
|
-
timestamp unix ms when the change was emitted
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Bridge target
|
|
29
|
-
|
|
30
|
-
- The active target framework lives in `.bridge-kit/bridge.json`'s `target`
|
|
31
|
-
field, written at `init --bridge --target <name>` time. This file is
|
|
32
|
-
committed (unlike `.eventmodelers/config.json`, which is gitignored) since
|
|
33
|
-
it's project policy shared by every teammate and CI runner, not a
|
|
34
|
-
per-machine credential.
|
|
35
|
-
- Skill naming convention: `bridge-<target>-specify` (kept in sync every
|
|
36
|
-
task) and `bridge-<target>-tasks` (invoked separately, by that framework's
|
|
37
|
-
own task-planning phase — not from this loop).
|
|
38
|
-
|
|
39
|
-
## Executors
|
|
40
|
-
|
|
41
|
-
- Claude (`ralph-claude.js`, this prompt) is the default. `bridge --local-ai`
|
|
42
|
-
swaps in a local model instead — same prompt, different executor.
|
|
43
|
-
- `bridge --hook "<command>"` (or a `hookCommand` persisted in
|
|
44
|
-
`bridge.json`) bypasses this prompt entirely: `ralph-hook.js` runs an
|
|
45
|
-
arbitrary shell command per batch of changes instead of any AI agent — e.g.
|
|
46
|
-
commit + push `.slices/` and let a CI pipeline do the actual translation.
|
|
47
|
-
If you're reading this file, a hook wasn't configured for this run.
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# Bridge Task Instructions
|
|
2
|
-
|
|
3
|
-
You are an autonomous agent reacting to slice status change events on an
|
|
4
|
-
Eventmodelers board. Unlike a build-kit agent, you do **not** write
|
|
5
|
-
application code — you translate the board's event model into artifacts for
|
|
6
|
-
another spec framework (e.g. Spec Kitty), keeping that framework's files in
|
|
7
|
-
sync with the board as it changes.
|
|
8
|
-
|
|
9
|
-
## Your Loop
|
|
10
|
-
|
|
11
|
-
1. Read `AGENT.md` to load accumulated learnings before doing anything else.
|
|
12
|
-
2. Read `.bridge-kit/tasks.json`.
|
|
13
|
-
3. If `tasks.json` is empty or missing, reply with:
|
|
14
|
-
<promise>IDLE</promise>
|
|
15
|
-
and stop.
|
|
16
|
-
4. Pick the **oldest task** (earliest `createdAt`).
|
|
17
|
-
5. Execute the task — see the Execution section below.
|
|
18
|
-
6. After execution, remove that task from the array and write
|
|
19
|
-
`.bridge-kit/tasks.json` back.
|
|
20
|
-
7. Append a progress entry to `progress.txt` (create if missing).
|
|
21
|
-
8. Update `AGENT.md` with any new reusable learnings discovered this
|
|
22
|
-
iteration.
|
|
23
|
-
9. Reply normally so the next iteration can pick up the next task.
|
|
24
|
-
|
|
25
|
-
## Execution
|
|
26
|
-
|
|
27
|
-
Each task has a single `payload` of type `SliceChangedPayload`:
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
{
|
|
31
|
-
event: "slice:changed"
|
|
32
|
-
organizationId: string | null
|
|
33
|
-
boardId: string
|
|
34
|
-
sliceId: string ← SLICE_BORDER node UUID
|
|
35
|
-
sliceTitle: string | null
|
|
36
|
-
sliceStatus: string | null ← e.g. "Planned", "InProgress", "Done"
|
|
37
|
-
timestamp: number
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Unlike a build-kit agent, a bridge agent reacts to **every** status change
|
|
42
|
-
(including `Planned`) — a task is queued regardless of `sliceStatus`. There
|
|
43
|
-
is no separate "claim and build" step, so don't invoke `/update-slice-status`
|
|
44
|
-
here; translation doesn't take ownership of a slice the way building does.
|
|
45
|
-
|
|
46
|
-
### Step 1 — Load credentials
|
|
47
|
-
|
|
48
|
-
Run `/connect` to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL` from
|
|
49
|
-
`.eventmodelers/config.json`.
|
|
50
|
-
|
|
51
|
-
### Step 2 — Resolve the bridge target
|
|
52
|
-
|
|
53
|
-
Read `.bridge-kit/bridge.json`'s `target` field (e.g. `"spec-kitty"`). The
|
|
54
|
-
matching translation skill is named `bridge-<target>-specify`.
|
|
55
|
-
|
|
56
|
-
### Step 3 — Translate
|
|
57
|
-
|
|
58
|
-
Invoke the `bridge-<target>-specify` skill. It reads `.slices/` (written by
|
|
59
|
-
`npx @eventmodelers/cli listen`, which must be running separately — see that
|
|
60
|
-
skill's own guardrail if `.slices/` doesn't exist yet) and regenerates that
|
|
61
|
-
framework's spec artifacts **wholesale** from the current board state. Do
|
|
62
|
-
not hand-author or patch those artifacts yourself — if they're wrong, the
|
|
63
|
-
event model is wrong; fix it on the board and let the next slice change
|
|
64
|
-
re-trigger this skill.
|
|
65
|
-
|
|
66
|
-
Do **not** also invoke `bridge-<target>-tasks` from this loop. That
|
|
67
|
-
companion skill enforces the 1:1 slice-to-work-package mapping during that
|
|
68
|
-
framework's own task-planning phase (e.g. Spec Kitty's
|
|
69
|
-
`/spec-kitty.tasks-outline`) — it's triggered by that framework's own
|
|
70
|
-
tooling when the user reaches that phase, not by every board change.
|
|
71
|
-
|
|
72
|
-
## Updating tasks.json
|
|
73
|
-
|
|
74
|
-
After completing a task, remove it from the array and write the updated
|
|
75
|
-
array back to `.bridge-kit/tasks.json`. If the array is now empty, write
|
|
76
|
-
`[]`.
|
|
77
|
-
|
|
78
|
-
## Progress Report Format
|
|
79
|
-
|
|
80
|
-
APPEND to `progress.txt` (never replace):
|
|
81
|
-
```
|
|
82
|
-
## [ISO timestamp] — Task [task.id]
|
|
83
|
-
|
|
84
|
-
Slice: [sliceTitle] ([sliceId])
|
|
85
|
-
Status change: [sliceStatus]
|
|
86
|
-
|
|
87
|
-
Action taken:
|
|
88
|
-
- [what was translated / regenerated in response to the slice change]
|
|
89
|
-
|
|
90
|
-
Learnings:
|
|
91
|
-
- [any patterns, gotchas, or reusable knowledge discovered]
|
|
92
|
-
---
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Stop Condition
|
|
96
|
-
|
|
97
|
-
If `.bridge-kit/tasks.json` is empty (`[]`) or does not exist, reply with:
|
|
98
|
-
<promise>IDLE</promise>
|
|
99
|
-
|
|
100
|
-
## Updating AGENT.md
|
|
101
|
-
|
|
102
|
-
After completing a task, add any **reusable** learnings to `AGENT.md` —
|
|
103
|
-
patterns, gotchas, or skill behaviour that future iterations should know.
|
|
104
|
-
Only add things that are general and applicable beyond this single task. Do
|
|
105
|
-
not duplicate what is already there.
|
|
106
|
-
|
|
107
|
-
## Important
|
|
108
|
-
|
|
109
|
-
- Process **one task per iteration**.
|
|
110
|
-
- Read `AGENT.md` first — it contains patterns from previous iterations.
|
|
111
|
-
- Always start with `/connect` if credentials are not yet loaded.
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Bridge loop using Claude Code as the executor. Unlike build-kit's
|
|
3
|
-
// ralph-claude.js, there is no onPlannedSlice consumer — a bridge agent
|
|
4
|
-
// doesn't "build" a Planned slice, it just translates every slice change as
|
|
5
|
-
// it arrives via tasks.json (see lib/prompt.md and queueAllStatuses below).
|
|
6
|
-
// Usage: node ralph-claude.js [project_dir]
|
|
7
|
-
|
|
8
|
-
import { startRalph, loadLocalConfig } from './lib/ralph.js';
|
|
9
|
-
import { spawn } from 'child_process';
|
|
10
|
-
import { dirname, resolve } from 'path';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
|
|
13
|
-
const kitDir = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const projectDir = process.argv[2] ? resolve(process.argv[2]) : resolve(kitDir, '..');
|
|
15
|
-
|
|
16
|
-
const cfg = loadLocalConfig(kitDir);
|
|
17
|
-
const inlineHeader = cfg.boardId
|
|
18
|
-
? `board=${cfg.boardId} token=${cfg.token} org=${cfg.organizationId} baseUrl=${cfg.baseUrl}\n\n`
|
|
19
|
-
: '';
|
|
20
|
-
|
|
21
|
-
const claudeArgs = ['--dangerously-skip-permissions'];
|
|
22
|
-
if (cfg.model) claudeArgs.push('--model', cfg.model);
|
|
23
|
-
const claudeEnv = cfg.anthropicBaseUrl
|
|
24
|
-
? { ...process.env, ANTHROPIC_BASE_URL: cfg.anthropicBaseUrl }
|
|
25
|
-
: process.env;
|
|
26
|
-
|
|
27
|
-
function runClaude(prompt) {
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
29
|
-
// Prompt on stdin, not as a `-p` argument — Windows cuts a multi-line argv entry at its first newline.
|
|
30
|
-
const proc = spawn('claude', [...claudeArgs, '-p'], {
|
|
31
|
-
cwd: projectDir,
|
|
32
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
33
|
-
env: claudeEnv,
|
|
34
|
-
});
|
|
35
|
-
proc.stdin.end(inlineHeader + prompt);
|
|
36
|
-
proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`Claude exited ${code}`))));
|
|
37
|
-
proc.on('error', reject);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
startRalph({
|
|
42
|
-
kitDir,
|
|
43
|
-
projectDir,
|
|
44
|
-
onTask: runClaude,
|
|
45
|
-
agentType: 'BRIDGE',
|
|
46
|
-
queueAllStatuses: true,
|
|
47
|
-
}).catch((err) => {
|
|
48
|
-
console.error('[ralph] Fatal:', err);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
});
|