@eventmodelers/cli 2.0.2 → 2.0.4
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 +1 -0
- package/cli.js +61 -10
- package/package.json +1 -1
- package/shared/build-kit/lib/build-log-share.js +156 -0
- package/shared/build-kit/lib/events-view.js +2 -0
- package/shared/build-kit/lib/ralph.js +8 -1
- package/shared/build-kit/lib/slice-events.js +42 -2
- package/shared/build-kit/lib/slice-info.js +52 -0
- package/shared/build-kit/lib/slice-state.js +1 -1
- package/shared/scaffold/supabase/generate.mjs +1 -1
- package/shared/scaffold/supabase/template/web/src/app/TimelinePage.tsx.tmpl +1 -1
- package/stacks/axon/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/axon/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/blank/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/blank/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/classic/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/classic/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/kurrent/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/modeling-kit/templates/.claude/skills/prototype/SKILL.md +2 -0
- package/stacks/modeling-kit/templates/.claude/skills/prototype/assets/prototype.mjs +7 -5
- package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +1 -1
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +2 -1
- package/stacks/node/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/node/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/opencqrs/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/react/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/react/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/supabase/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/supabase-react/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/supabase-react/templates/build-kit/lib/prompt.md +10 -0
- package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +10 -0
- package/stacks/umadb/templates/build-kit/lib/prompt.md +10 -0
package/README.md
CHANGED
|
@@ -166,6 +166,7 @@ npx @eventmodelers/cli run --agent <name> # pick the agent: claude (de
|
|
|
166
166
|
npx @eventmodelers/cli run --agent <name> --model <id> # …on a given model: -m for opencode/codex/gemini, --model for Claude, LOCAL_AI_MODEL for --local-ai
|
|
167
167
|
npx @eventmodelers/cli run --exec "<command>" # manual fallback for any other harness (ralph-exec.js) — agent tracing needs its JSON flag, e.g. "codex exec --json --full-auto"
|
|
168
168
|
npx @eventmodelers/cli run --local # skip platform config/credential lookup entirely — local-only, no board sync
|
|
169
|
+
npx @eventmodelers/cli run --sync # share this build agent's event log with the board, constantly, for a live dashboard (see agent-build-log.md)
|
|
169
170
|
npx @eventmodelers/cli run --modeling # modeling-kit: warm Claude process driven by the board's prompt queue
|
|
170
171
|
npx @eventmodelers/cli run --standalone # same, plus acting on board changes unprompted — and needs no install at all
|
|
171
172
|
npx @eventmodelers/cli run --standalone --board-id <uuid> # …from any directory, against any board (see Power users)
|
package/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ import { filterEvents, baselineIds, formatEvent, formatPayload, sliceTitles, sum
|
|
|
46
46
|
import { stableGlobalAgentId } from './lib/global-agent-id.js';
|
|
47
47
|
import { ensureLearningsGitRules } from './shared/build-kit/lib/ralph.js';
|
|
48
48
|
import { createCallTracker, heldPromptsRequest, journalPromptAction, shouldJournalCall, journalPromptFinished, journalPromptReceived, recoverOwnPrompts } from './shared/build-kit/lib/prompt-journal.js';
|
|
49
|
+
import { INFO_KINDS, infoComment, recordInfo, recordTurnInfos } from './shared/build-kit/lib/slice-info.js';
|
|
49
50
|
import { listLearnings, mergeLearnings, recordLearning, recordTurnLearnings, retractLearning, syncLearnings } from './shared/build-kit/lib/slice-learnings.js';
|
|
50
51
|
|
|
51
52
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -2681,15 +2682,18 @@ async function runModeling(kitDir, projectDir, { codeDir = null, verbose = false
|
|
|
2681
2682
|
try {
|
|
2682
2683
|
turnPromptId = p.id;
|
|
2683
2684
|
const reply = await runTurn(buildTurn(p));
|
|
2684
|
-
// The agent cannot run `learn` (locked-down Bash), so it states
|
|
2685
|
-
// Best effort: a failed write never takes the prompt queue down.
|
|
2686
|
-
|
|
2687
|
-
|
|
2685
|
+
// The agent cannot run `learn` or `info` (locked-down Bash), so it states them in its reply and they are recorded here.
|
|
2686
|
+
// Best effort: a failed write never takes the prompt queue down. Infos are events stamped with their board, so every
|
|
2687
|
+
// run records them — standalone included; learnings are kept only where `keepsRecord` (they feed AGENTS.md).
|
|
2688
|
+
try {
|
|
2689
|
+
if (keepsRecord) {
|
|
2688
2690
|
const n = recordTurnLearnings({ kitDir, reply, meta: journalMeta });
|
|
2689
2691
|
if (n) log(`recorded ${n} learning${n === 1 ? '' : 's'} from the turn`);
|
|
2690
|
-
} catch (err) {
|
|
2691
|
-
log(`could not record the turn's learnings: ${err.message}`);
|
|
2692
2692
|
}
|
|
2693
|
+
const infos = recordTurnInfos({ kitDir, reply, meta: journalMeta });
|
|
2694
|
+
if (infos) log(`recorded ${infos} info event${infos === 1 ? '' : 's'} from the turn`);
|
|
2695
|
+
} catch (err) {
|
|
2696
|
+
log(`could not record the turn's learnings or infos: ${err.message}`);
|
|
2693
2697
|
}
|
|
2694
2698
|
journal(() => journalPromptFinished({ kitDir, id: p.id, outcome: 'worked', meta: journalMeta }));
|
|
2695
2699
|
} catch (err) {
|
|
@@ -3826,6 +3830,7 @@ credentialFlags(program
|
|
|
3826
3830
|
.option('--browser', 'Give the locked-down modeling agent a browser: the Playwright MCP server (npx @playwright/mcp) next to the eventmodelers one, for discover-storyboard. Off by default because a browser can reach any site, which the lockdown otherwise rules out. Default Claude runner only; --dangerously-skip-permissions already loads every MCP server the machine configures.')
|
|
3827
3831
|
.option('--handover', 'Backend build kit: for this run only, finish slices that have a UI at `Waiting` instead of `Done` (same as the project setting `handover: true`, which `init --handover` / `config --handover on` save — this flag writes nothing). Without it the project setting applies; without both, slices end at `Done`.')
|
|
3828
3832
|
.option('--status-trigger <status>', 'Build kit: the slice status the agent picks work up from, for this run only (e.g. Ready, Planned, Waiting). Defaults to the kit\'s own pickup status — `Planned` for backend kits, `Handover` for the UI kits. Nothing is written to disk.')
|
|
3833
|
+
.option('--sync', 'Build kit: share this agent\'s event log with the board — events that name a slice are uploaded constantly (POST …/buildlog) for a live dashboard. The platform keeps one iteration per slice, 1000 entries at most. A no-op for --modeling/--standalone, and with --local.')
|
|
3829
3834
|
.option('--verbose', 'Log every tool call\'s full input (commands, skill args, file paths) and assistant reasoning text. Default is condensed, high-level per-step logging only.')
|
|
3830
3835
|
.option('--id <id>', 'Pin the agent id this run identifies itself with on the platform. Without it an agent keeps one id: a project install per project, a --global/--standalone run per board and repo (the directory it is started in) — the same agent on every restart, with its chat, its star on the board and the prompts it holds. If another live process already runs under that id, this one gets a fresh id instead (two agents must not share a heartbeat row). Pass --id when a supervisor already knows the id you want, or for a second agent on purpose; it is used as given, with no such check. Per-run only: nothing is written to disk.')
|
|
3831
3836
|
.option('--name <name>', 'A human-readable name for this agent, sent with every heartbeat so the board shows which agent is live rather than a bare uuid (e.g. "ci-builder", "martins-laptop"). Per-run only, like --id: the persistent name is `agentName` in config.json (set via `init --name` / `init-config --name`), and this overrides it for one run without writing anything.')
|
|
@@ -3858,8 +3863,10 @@ credentialFlags(program
|
|
|
3858
3863
|
}
|
|
3859
3864
|
// Asked once, for the kit in this directory (the first one found when there is none), and
|
|
3860
3865
|
// handed to the others — the modeling agent reads this directory's config.
|
|
3861
|
-
|
|
3862
|
-
|
|
3866
|
+
// With no build kit (--modeling alone), the credentials belong to this directory itself:
|
|
3867
|
+
// a kit dir that need not exist, since only its parent is used.
|
|
3868
|
+
const rootDir = dirs.find((dir) => dirname(dir) === cwd) ?? dirs[0] ?? join(cwd, '.agent-modeling-kit');
|
|
3869
|
+
console.log(`\n🔑 Credentials for ${dirs.length ? relative(cwd, rootDir) || '.' : 'the modeling agent'}, shared by all`);
|
|
3863
3870
|
const { organizationId, boardId, token, baseUrl } = await forceBuildKitCredentials(rootDir, cwd, opts, globalOpts);
|
|
3864
3871
|
for (const dir of dirs.filter((d) => d !== rootDir)) {
|
|
3865
3872
|
await forceBuildKitCredentials(dir, cwd, opts, globalOpts, { organizationId, boardId, token, baseUrl });
|
|
@@ -3949,6 +3956,7 @@ credentialFlags(program
|
|
|
3949
3956
|
// no meaning for a build kit, which is scaffolded per project by definition.
|
|
3950
3957
|
if (opts.modeling || opts.standalone || opts.worker || opts.global) {
|
|
3951
3958
|
const picked = opts.modeling ? '--modeling' : opts.standalone ? '--standalone' : opts.worker ? '--worker' : '--global';
|
|
3959
|
+
if (opts.sync) console.log(`ℹ️ --sync shares a build agent's log — it does nothing with ${picked}.`);
|
|
3952
3960
|
// --local-ai and --exec name what does a turn, not a queue, and the modeling loop has its own
|
|
3953
3961
|
// runner for each (lib/modeling-local-ai.js, lib/modeling-exec.js).
|
|
3954
3962
|
const modelingLocalAi = resolveLocalAiTarget(opts);
|
|
@@ -4114,7 +4122,7 @@ credentialFlags(program
|
|
|
4114
4122
|
// path with no stream-json parsing to gate. RALPH_LOCAL is read by ralph.js's startRalph to force
|
|
4115
4123
|
// the local-only branch even when .eventmodelers/config.json has valid credentials.
|
|
4116
4124
|
// RALPH_AGENT_ID/RALPH_AGENT_NAME (--id/--name) are read there too.
|
|
4117
|
-
execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_STATUS_TRIGGER: opts.statusTrigger ? String(opts.statusTrigger).trim() : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}) } });
|
|
4125
|
+
execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_SYNC: opts.sync ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_STATUS_TRIGGER: opts.statusTrigger ? String(opts.statusTrigger).trim() : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}) } });
|
|
4118
4126
|
} catch (err) {
|
|
4119
4127
|
process.exit(err.status || 1);
|
|
4120
4128
|
}
|
|
@@ -4740,6 +4748,42 @@ program
|
|
|
4740
4748
|
}
|
|
4741
4749
|
});
|
|
4742
4750
|
|
|
4751
|
+
program
|
|
4752
|
+
.command('info <text>')
|
|
4753
|
+
.description("For a build agent: record something that is not a reusable learning — what you cannot change and why (limitation), a decision you took, a note. It is an event in the log and a comment on the slice. Reusable knowledge about the codebase or model is a `learn`, not an info.")
|
|
4754
|
+
.option('--kind <kind>', `${INFO_KINDS.join(' · ')} (default note)`, 'note')
|
|
4755
|
+
.option('--slice <idOrTitle>', 'The slice it is about (default: the one the run loop has claimed)')
|
|
4756
|
+
.option('--tag <tag>', 'Label the info (repeatable)', (tag, tags) => [...tags, tag], [])
|
|
4757
|
+
.action(async (text, opts, command) => {
|
|
4758
|
+
const cwd = process.cwd();
|
|
4759
|
+
const kitDir = requireBuildKitDir(cwd);
|
|
4760
|
+
const meta = agentEventMeta(cwd, kitDir, command);
|
|
4761
|
+
try {
|
|
4762
|
+
let sliceId;
|
|
4763
|
+
if (opts.slice) sliceId = findSliceDir(kitDir, opts.slice)?.slice.id ?? opts.slice;
|
|
4764
|
+
else {
|
|
4765
|
+
const claimed = Object.entries(fold(readEvents(kitDir)).slices).filter(([, s]) => s.claim);
|
|
4766
|
+
if (claimed.length === 1) sliceId = claimed[0][0];
|
|
4767
|
+
}
|
|
4768
|
+
const info = recordInfo({ kitDir, text, kind: opts.kind, slice: sliceId, tags: opts.tag, meta });
|
|
4769
|
+
console.log(`✅ Recorded ${info.kind} (${info.id.slice(0, 8)}).`);
|
|
4770
|
+
if (!sliceId) return console.log('ℹ️ No slice named or claimed — not posted to the board.');
|
|
4771
|
+
// Best effort: the event is the record, the comment is how the board shows it.
|
|
4772
|
+
const cfg = loadEffectiveConfig(cwd, kitDir, command.optsWithGlobals().config).config;
|
|
4773
|
+
if (['organizationId', 'boardId', 'token'].some((f) => !cfg[f])) return console.log('ℹ️ No board credentials — not posted to the board.');
|
|
4774
|
+
const res = await fetch(`${cfg.baseUrl || DEFAULT_BASE_URL}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/nodes/${sliceId}/comments`, {
|
|
4775
|
+
method: 'POST',
|
|
4776
|
+
headers: { 'Content-Type': 'application/json', 'x-token': cfg.token, 'x-user-id': 'cli-info', ...agentHeaders(cfg) },
|
|
4777
|
+
body: JSON.stringify({ text: infoComment(info), type: 'COMMENT', author: 'build-agent' }),
|
|
4778
|
+
signal: AbortSignal.timeout(15_000),
|
|
4779
|
+
});
|
|
4780
|
+
console.log(res.ok ? '✅ Posted as a comment on the slice.' : `⚠️ Board returned HTTP ${res.status} — the info is only in the event log.`);
|
|
4781
|
+
} catch (err) {
|
|
4782
|
+
console.error(`❌ ${err.message}`);
|
|
4783
|
+
process.exit(1);
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
|
|
4743
4787
|
program
|
|
4744
4788
|
.command('events')
|
|
4745
4789
|
.description("Decode a kit's event log in the terminal: one line per event (claims, settles, learnings, tasks, prompts …) with the slice's title and the agent that wrote it. Filter it, print the raw events, or print the state the log folds to.")
|
|
@@ -5112,5 +5156,12 @@ program
|
|
|
5112
5156
|
}
|
|
5113
5157
|
});
|
|
5114
5158
|
|
|
5115
|
-
|
|
5159
|
+
try {
|
|
5160
|
+
await program.parseAsync();
|
|
5161
|
+
} catch (err) {
|
|
5162
|
+
// A bug or bad input must end as one readable line, not a Node stack trace.
|
|
5163
|
+
console.error(`❌ ${err?.message ?? err}`);
|
|
5164
|
+
if (process.env.EVENTMODELERS_DEBUG && err?.stack) console.error(err.stack);
|
|
5165
|
+
process.exitCode = 1;
|
|
5166
|
+
}
|
|
5116
5167
|
if (sharedRl) sharedRl.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// A build agent shares its event log with the board — two capabilities, both build-agent only (a modeling
|
|
2
|
+
// agent gets no-ops):
|
|
3
|
+
//
|
|
4
|
+
// iterations every slice build turn is an iteration: `iteration-started` / `iteration-closed` events in the
|
|
5
|
+
// log, each carrying the slice, the iteration id, the board, the org and the date — and a
|
|
6
|
+
// `slice-done` after an iteration that settled the slice as Done. Always on.
|
|
7
|
+
// sync with --sync, the events that name a slice are uploaded to POST …/boards/:boardId/buildlog,
|
|
8
|
+
// numbered (only while the slice has an iteration open — an agent records only when it is active; the event's own `seq`, which defines the order) and tagged with the kit type (backend, or ui for a kit that picks up from Waiting) —
|
|
9
|
+
// constantly, on a short interval for as long as the loop runs, so a live dashboard follows the
|
|
10
|
+
// build, and once more at the start and end of each iteration.
|
|
11
|
+
//
|
|
12
|
+
// Best effort, like tracing: a cursor (the last event id sent) lives next to the log and only moves forward
|
|
13
|
+
// when the upload succeeded, so a failed upload is simply sent again with the next one. The server holds one
|
|
14
|
+
// iteration per slice and kit type — every `iteration-started` resets that log — and at most 1000 entries.
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
17
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { appendEvent, readEvents } from './slice-events.js';
|
|
20
|
+
import { statusKey } from './slice-state.js';
|
|
21
|
+
|
|
22
|
+
export const SYNC_STATE_FILE = 'build-log-sync.json';
|
|
23
|
+
export const SYNC_INTERVAL_MS = 2_000;
|
|
24
|
+
// Always shared, even with no iteration open: they are what opens and closes one.
|
|
25
|
+
const BOUNDARY = new Set(['iteration-started', 'iteration-closed', 'slice-done']);
|
|
26
|
+
const NOOP = { iterationStarted: () => null, iterationClosed: () => {}, flush: async () => {}, start: () => {}, stop: () => {} };
|
|
27
|
+
|
|
28
|
+
function readState(path) {
|
|
29
|
+
try { return JSON.parse(readFileSync(path, 'utf-8')); } catch { return {}; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeState(path, state) {
|
|
33
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
34
|
+
writeFileSync(tmp, JSON.stringify(state), 'utf-8');
|
|
35
|
+
renameSync(tmp, path);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The wire shape of one log event: the envelope the endpoint reads, everything else as payload.
|
|
39
|
+
export function toBuildLogEntry(e, { agentId, iteration, boardId, organizationId, kitType, seq }) {
|
|
40
|
+
const { id, at, type, slice, meta: _meta, tags: _tags, ...rest } = e;
|
|
41
|
+
return { ...rest, id, type, sliceId: slice, agentId, iteration, occurredAt: at, boardId, organizationId, kitType, seq };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A kit that picks slices up from Waiting builds the UI; any other builds the backend.
|
|
45
|
+
export const kitTypeOf = (pickupStatus) => (String(pickupStatus ?? '').trim().toLowerCase() === 'waiting' ? 'ui' : 'backend');
|
|
46
|
+
|
|
47
|
+
export function createBuildLogShare({ kitDir, cfg, meta, agentType = 'BUILD', kitType = 'backend', sync = false, enabled = true, intervalMs = SYNC_INTERVAL_MS, log = () => {} } = {}) {
|
|
48
|
+
if (agentType !== 'BUILD') return NOOP;
|
|
49
|
+
const statePath = join(kitDir, SYNC_STATE_FILE);
|
|
50
|
+
const canUpload = !!(sync && enabled && cfg?.baseUrl && cfg?.token && cfg?.organizationId && cfg?.boardId && cfg?.agentId);
|
|
51
|
+
const open = new Map(); // slice → the iteration in progress
|
|
52
|
+
let timer = null;
|
|
53
|
+
let inFlight = null;
|
|
54
|
+
let warned = false;
|
|
55
|
+
|
|
56
|
+
async function upload() {
|
|
57
|
+
const state = readState(statePath);
|
|
58
|
+
const events = readEvents(kitDir);
|
|
59
|
+
const from = state.cursor ? events.findIndex((e) => e.id === state.cursor) + 1 : 0; // a pruned cursor re-sends; the server drops duplicates
|
|
60
|
+
const fresh = events.slice(from);
|
|
61
|
+
if (!fresh.length) return;
|
|
62
|
+
const iterations = { ...(state.iterations ?? {}) };
|
|
63
|
+
const entries = [];
|
|
64
|
+
for (const e of fresh) {
|
|
65
|
+
if (!e.slice) continue;
|
|
66
|
+
// An agent records only while it is active: events of a slice with no iteration open are not shared. The kit mirrors
|
|
67
|
+
// every slice's board status as `board-observed`, whether or not it is working on it — that is not the agent's work.
|
|
68
|
+
// The boundaries themselves (the iteration's start and end, and slice-done after it) always are.
|
|
69
|
+
if (!iterations[e.slice] && !BOUNDARY.has(e.type)) continue;
|
|
70
|
+
if (e.type === 'iteration-started') iterations[e.slice] = e.iteration;
|
|
71
|
+
entries.push(toBuildLogEntry(e, { agentId: cfg.agentId, iteration: e.iteration ?? iterations[e.slice], boardId: cfg.boardId, organizationId: cfg.organizationId, kitType, seq: e.seq }));
|
|
72
|
+
if (e.type === 'iteration-closed') delete iterations[e.slice];
|
|
73
|
+
}
|
|
74
|
+
if (entries.length) {
|
|
75
|
+
const res = await fetch(`${cfg.baseUrl}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/buildlog`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json', 'x-token': cfg.token, 'x-agent-id': cfg.agentId },
|
|
78
|
+
body: JSON.stringify({ events: entries }),
|
|
79
|
+
signal: AbortSignal.timeout(15_000),
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const reason = `HTTP ${res.status}${await res.text().then((t) => `: ${t}`).catch(() => '')}`;
|
|
83
|
+
// A batch the platform rejects as invalid can never succeed: skip it rather than resend it every interval.
|
|
84
|
+
if (res.status !== 400) throw new Error(reason);
|
|
85
|
+
log(`info: the platform did not take ${entries.length} build log entries (${reason}) — skipped`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
writeState(statePath, { cursor: fresh[fresh.length - 1].id, iterations });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// One upload at a time. A call that arrives while one runs asks for another pass right after it, so events
|
|
92
|
+
// appended in between are never left to wait for the interval.
|
|
93
|
+
let again = false;
|
|
94
|
+
function flush() {
|
|
95
|
+
if (!canUpload) return Promise.resolve();
|
|
96
|
+
if (inFlight) { again = true; return inFlight; }
|
|
97
|
+
inFlight = (async () => {
|
|
98
|
+
do {
|
|
99
|
+
again = false;
|
|
100
|
+
try { await upload(); warned = false; } catch (err) {
|
|
101
|
+
if (!warned) { warned = true; log(`info: build log upload failed (${err.message}) — will send it with the next one`); }
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
} while (again);
|
|
105
|
+
})().finally(() => { inFlight = null; });
|
|
106
|
+
return inFlight;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
iterationStarted(planned) {
|
|
111
|
+
const iteration = randomUUID();
|
|
112
|
+
try {
|
|
113
|
+
appendEvent(kitDir, { type: 'iteration-started', slice: planned.id, iteration, title: planned.title, kitType, boardId: cfg?.boardId, organizationId: cfg?.organizationId }, meta);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
log(`info: could not record the start of an iteration (${err.message}) — the build goes on`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
open.set(planned.id, iteration);
|
|
119
|
+
flush();
|
|
120
|
+
return iteration;
|
|
121
|
+
},
|
|
122
|
+
async iterationClosed(planned, status) {
|
|
123
|
+
const iteration = open.get(planned.id);
|
|
124
|
+
if (!iteration) return;
|
|
125
|
+
open.delete(planned.id);
|
|
126
|
+
try {
|
|
127
|
+
appendEvent(kitDir, { type: 'iteration-closed', slice: planned.id, iteration, status, kitType, boardId: cfg?.boardId, organizationId: cfg?.organizationId }, meta);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
log(`info: could not record the end of an iteration (${err.message}) — the build goes on`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// The iteration that settled the slice as Done ends with `slice-done`; if it is the slice's newest entry when
|
|
133
|
+
// the next iteration starts, the platform knows the slice was reopened and starts its log over.
|
|
134
|
+
try {
|
|
135
|
+
const events = readEvents(kitDir);
|
|
136
|
+
const from = events.findLastIndex((e) => e.type === 'iteration-started' && e.iteration === iteration);
|
|
137
|
+
if (events.slice(from + 1).some((e) => e.type === 'settled' && e.slice === planned.id && statusKey(e.status) === 'done')) {
|
|
138
|
+
appendEvent(kitDir, { type: 'slice-done', slice: planned.id, iteration, kitType, boardId: cfg?.boardId, organizationId: cfg?.organizationId }, meta);
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
log(`info: could not record that the slice is done (${err.message}) — the build goes on`);
|
|
142
|
+
}
|
|
143
|
+
await flush();
|
|
144
|
+
},
|
|
145
|
+
flush,
|
|
146
|
+
start() {
|
|
147
|
+
if (!canUpload || timer) return;
|
|
148
|
+
timer = setInterval(flush, intervalMs);
|
|
149
|
+
timer.unref?.();
|
|
150
|
+
},
|
|
151
|
+
stop() {
|
|
152
|
+
if (timer) clearInterval(timer);
|
|
153
|
+
timer = null;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -51,6 +51,8 @@ function describe(e) {
|
|
|
51
51
|
return ['', `${plural(e.sources?.length ?? e.learnings?.length ?? 0, 'learning')} → ${oneLine(e.text, 120)}${e.reason ? ` — ${oneLine(e.reason, 80)}` : ''}`];
|
|
52
52
|
case 'learnings-curated':
|
|
53
53
|
return ['', ''];
|
|
54
|
+
case 'info-recorded':
|
|
55
|
+
return [e.slice, `[${e.kind ?? 'note'}] ${oneLine(e.text, 160)}`];
|
|
54
56
|
case 'prompt-received':
|
|
55
57
|
return [e.prompt?.id, `"${oneLine(e.prompt?.text, 80)}"${e.prompt?.priority ? ', priority' : ''}, pid ${e.pid}`];
|
|
56
58
|
case 'prompt-action':
|
|
@@ -20,6 +20,7 @@ import { readEvents } from './slice-events.js';
|
|
|
20
20
|
import { claimSlice, recordEvent, settleSlice, settleLeftoverClaims } from './slice-run.js';
|
|
21
21
|
import { isTreeClean } from './slice-git.js';
|
|
22
22
|
import { syncLearnings } from './slice-learnings.js';
|
|
23
|
+
import { createBuildLogShare, kitTypeOf, SYNC_STATE_FILE } from './build-log-share.js';
|
|
23
24
|
import { curateIfDue } from './slice-curation.js';
|
|
24
25
|
import { queueTask, workNextTask } from './slice-tasks.js';
|
|
25
26
|
import { defaultAgentName, withInstanceSuffix } from './agent-name.js';
|
|
@@ -739,7 +740,7 @@ function ensureLearningsGitRules(kitDir) {
|
|
|
739
740
|
const projectDir = dirname(kitDir);
|
|
740
741
|
if (!existsSync(join(projectDir, '.git'))) return;
|
|
741
742
|
const rules = [
|
|
742
|
-
['info/exclude', [`${basename(kitDir)}/events/`, `${basename(kitDir)}/events.log`]],
|
|
743
|
+
['info/exclude', [`${basename(kitDir)}/events/`, `${basename(kitDir)}/events.log`, `${basename(kitDir)}/events.seq`, `${basename(kitDir)}/${SYNC_STATE_FILE}`]],
|
|
743
744
|
['info/attributes', [`${basename(kitDir)}/AGENTS.md merge=union`]],
|
|
744
745
|
];
|
|
745
746
|
for (const [name, lines] of rules) {
|
|
@@ -769,6 +770,10 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false,
|
|
|
769
770
|
const board = credentialed ? createBoard(cfg) : null;
|
|
770
771
|
const meta = eventMeta(cfg);
|
|
771
772
|
ensureLearningsGitRules(kitDir);
|
|
773
|
+
// Sharing the event log with the board (`run --sync`): needs the board, so never with --local.
|
|
774
|
+
const share = createBuildLogShare({ kitDir, cfg, meta, agentType: cfg?.agentType ?? 'BUILD', kitType: kitTypeOf(readPickupStatus(kitDir)), sync: process.env.RALPH_SYNC === '1', enabled: credentialed, log: (line) => console.log(`[ralph] ${line}`) });
|
|
775
|
+
if (process.env.RALPH_SYNC === '1' && !credentialed) console.log('[ralph] --sync needs board credentials — the build log stays local.');
|
|
776
|
+
share.start();
|
|
772
777
|
let lastIdleCtx;
|
|
773
778
|
// The build agent's final reply on the most recent planned-slice turn — quoted in the Blocked note.
|
|
774
779
|
let lastReply = null;
|
|
@@ -831,6 +836,7 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false,
|
|
|
831
836
|
}
|
|
832
837
|
}
|
|
833
838
|
lastReply = null;
|
|
839
|
+
if (planned.id) share.iterationStarted(planned);
|
|
834
840
|
const built = await runWithRetry(`onPlannedSlice: building slice "${planned.title}"...`, async () => { lastReply = await onPlannedSlice(prompt, {
|
|
835
841
|
sliceId: planned.id,
|
|
836
842
|
sliceTitle: planned.title,
|
|
@@ -842,6 +848,7 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice, localOnly = false,
|
|
|
842
848
|
}); });
|
|
843
849
|
if (built) console.log(`[ralph] Slice build complete — waiting for next slice`);
|
|
844
850
|
if (planned.id) await settleSlice({ kitDir, planned, board, meta, pickupStatus: readPickupStatus(kitDir), details: { built, lastReply } });
|
|
851
|
+
if (planned.id) await share.iterationClosed(planned, built ? 'ok' : 'error');
|
|
845
852
|
if (credentialed) await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
|
|
846
853
|
didWork = true;
|
|
847
854
|
}
|
|
@@ -11,6 +11,7 @@ import { join } from 'path';
|
|
|
11
11
|
|
|
12
12
|
export const EVENTS_DIR = 'events';
|
|
13
13
|
export const INDEX_FILE = 'events.log';
|
|
14
|
+
export const SEQ_FILE = 'events.seq';
|
|
14
15
|
|
|
15
16
|
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
16
17
|
|
|
@@ -49,10 +50,41 @@ export function normalizeMeta(meta) {
|
|
|
49
50
|
return clean;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
// The last sequence number handed out is remembered next to the index (SEQ_FILE), so numbers keep climbing
|
|
54
|
+
// when events are pruned or the log is rebuilt. A kit that has no such file yet (a log written before events
|
|
55
|
+
// were numbered) continues from its newest event: its own `seq`, or how many events precede it. Two processes
|
|
56
|
+
// appending in the same instant can draw the same number — nothing enforces uniqueness, here or on the server:
|
|
57
|
+
// the index order decides, and readEvents renumbers so a reader still sees a strictly increasing sequence.
|
|
58
|
+
function newestEventSeq(kitDir) {
|
|
59
|
+
const index = join(kitDir, INDEX_FILE);
|
|
60
|
+
if (!existsSync(index)) return 0;
|
|
61
|
+
const ids = readFileSync(index, 'utf-8').split('\n').map((l) => l.trim()).filter((l) => ID.test(l));
|
|
62
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
63
|
+
try {
|
|
64
|
+
const { seq } = JSON.parse(readFileSync(join(kitDir, EVENTS_DIR, `${ids[i]}.json`), 'utf-8'));
|
|
65
|
+
return Number.isInteger(seq) ? seq : i + 1;
|
|
66
|
+
} catch { /* unreadable — look at the one before */ }
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Takes the next number and remembers it.
|
|
72
|
+
function nextSeq(kitDir) {
|
|
73
|
+
const file = join(kitDir, SEQ_FILE);
|
|
74
|
+
let remembered = 0;
|
|
75
|
+
try { remembered = Number.parseInt(readFileSync(file, 'utf-8'), 10) || 0; } catch { /* none yet */ }
|
|
76
|
+
const seq = Math.max(remembered, newestEventSeq(kitDir)) + 1;
|
|
77
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
78
|
+
writeFileSync(tmp, String(seq), 'utf-8');
|
|
79
|
+
renameSync(tmp, file);
|
|
80
|
+
return seq;
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
// The event file is written whole (temp file, then rename) before its id is indexed, so a reader that
|
|
53
84
|
// finds the id finds a complete file. The index line is one short append-mode write, so lines from
|
|
54
85
|
// concurrent processes never interleave however large the event. After a torn index line the new id
|
|
55
|
-
// starts on a fresh line instead of being glued onto the fragment.
|
|
86
|
+
// starts on a fresh line instead of being glued onto the fragment. Every event gets the next sequence number
|
|
87
|
+
// (`seq`), which — not the timestamp — defines the order. Returns the event id.
|
|
56
88
|
export function appendEvent(kitDir, event, meta) {
|
|
57
89
|
const dir = join(kitDir, EVENTS_DIR);
|
|
58
90
|
mkdirSync(dir, { recursive: true });
|
|
@@ -67,6 +99,7 @@ export function appendEvent(kitDir, event, meta) {
|
|
|
67
99
|
...rest,
|
|
68
100
|
...(cleanTags.length ? { tags: cleanTags } : {}),
|
|
69
101
|
...(Object.keys(cleanMeta).length ? { meta: cleanMeta } : {}),
|
|
102
|
+
seq: nextSeq(kitDir),
|
|
70
103
|
id,
|
|
71
104
|
}, null, 2), 'utf-8');
|
|
72
105
|
renameSync(tmp, join(dir, `${id}.json`));
|
|
@@ -75,7 +108,7 @@ export function appendEvent(kitDir, event, meta) {
|
|
|
75
108
|
return id;
|
|
76
109
|
}
|
|
77
110
|
|
|
78
|
-
// All events in index order
|
|
111
|
+
// All events in index order, each with its `seq`; with `tag`, only those carrying it.
|
|
79
112
|
export function readEvents(kitDir, { tag } = {}) {
|
|
80
113
|
const index = join(kitDir, INDEX_FILE);
|
|
81
114
|
if (!existsSync(index)) return [];
|
|
@@ -89,5 +122,12 @@ export function readEvents(kitDir, { tag } = {}) {
|
|
|
89
122
|
events.push(JSON.parse(readFileSync(join(kitDir, EVENTS_DIR, `${id}.json`), 'utf-8')));
|
|
90
123
|
} catch { /* indexed but missing or unreadable — skipped */ }
|
|
91
124
|
}
|
|
125
|
+
// Strictly increasing in index order: an event from before numbering, or one that drew the same number as its
|
|
126
|
+
// predecessor, takes the next free one.
|
|
127
|
+
let prev = 0;
|
|
128
|
+
for (const e of events) {
|
|
129
|
+
if (!Number.isInteger(e.seq) || e.seq <= prev) e.seq = prev + 1;
|
|
130
|
+
prev = e.seq;
|
|
131
|
+
}
|
|
92
132
|
return tag === undefined ? events : events.filter((e) => e.tags?.includes(tag));
|
|
93
133
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Info events: what an agent reports that is not a reusable learning — "I can't change X because Y", a
|
|
2
|
+
// decision it took, a note for whoever reads the board. A learning is true of the codebase or the model for
|
|
3
|
+
// every later build (slice-learnings.js); an info is about this slice, now. Infos feed no projection — the
|
|
4
|
+
// event is the record — and are also posted as a comment on the slice, so the board shows them.
|
|
5
|
+
|
|
6
|
+
import { appendEvent } from './slice-events.js';
|
|
7
|
+
|
|
8
|
+
export const INFO_KINDS = ['limitation', 'decision', 'note'];
|
|
9
|
+
const MAX_INFO_LENGTH = 500;
|
|
10
|
+
|
|
11
|
+
export function recordInfo({ kitDir, text, kind = 'note', slice, tags, meta }) {
|
|
12
|
+
const clean = String(text ?? '').trim();
|
|
13
|
+
if (!clean) throw new Error('the info is empty');
|
|
14
|
+
if (!INFO_KINDS.includes(kind)) throw new Error(`kind must be ${INFO_KINDS.join(', ')}, not "${kind}"`);
|
|
15
|
+
const id = appendEvent(kitDir, { type: 'info-recorded', kind, text: clean.slice(0, MAX_INFO_LENGTH), ...(slice ? { slice } : {}), tags }, meta);
|
|
16
|
+
return { id, text: clean.slice(0, MAX_INFO_LENGTH), kind };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// The comment the board shows for an info.
|
|
20
|
+
export function infoComment({ kind, text }) {
|
|
21
|
+
return `[${kind}] ${text}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── infos a turn states in its reply ────────────────────────────────────────────
|
|
25
|
+
// Same contract as learnings: the locked-down modeling agent ends a reply with `Info [<kind>]: <line>` and
|
|
26
|
+
// the CLI records it. At most five per reply, each capped, so what a board collaborator can talk the agent
|
|
27
|
+
// into saying stays small.
|
|
28
|
+
const MAX_TURN_INFOS = 5;
|
|
29
|
+
const TURN_INFO = /^\s*(?:[-*]\s+)?info(?:\s*\[([^\]]{1,20})\])?:\s*(.*?)\s*$/i;
|
|
30
|
+
|
|
31
|
+
export function parseTurnInfos(reply) {
|
|
32
|
+
const found = [];
|
|
33
|
+
let fenced = false;
|
|
34
|
+
for (const line of String(reply ?? '').split('\n')) {
|
|
35
|
+
if (line.trim().startsWith('```')) { fenced = !fenced; continue; }
|
|
36
|
+
if (fenced) continue;
|
|
37
|
+
const m = TURN_INFO.exec(line);
|
|
38
|
+
if (!m) continue;
|
|
39
|
+
const text = m[2];
|
|
40
|
+
if (!text || /^none\.?$/i.test(text) || text.length > MAX_INFO_LENGTH) continue;
|
|
41
|
+
const kind = m[1]?.trim().toLowerCase();
|
|
42
|
+
found.push({ kind: INFO_KINDS.includes(kind) ? kind : 'note', text });
|
|
43
|
+
if (found.length === MAX_TURN_INFOS) break;
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function recordTurnInfos({ kitDir, reply, meta }) {
|
|
49
|
+
const infos = parseTurnInfos(reply);
|
|
50
|
+
for (const { kind, text } of infos) recordInfo({ kitDir, text, kind, tags: ['modeling'], meta });
|
|
51
|
+
return infos.length;
|
|
52
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Events (one writer per type): board-observed, claimed, claim-confirmed, agent-recorded, settled,
|
|
6
6
|
// released, attempt-failed, blocked, learning-recorded, learning-retracted, learning-merged, learnings-curated,
|
|
7
|
-
// task-queued, task-done, prompt-received, prompt-finished. Unknown types are ignored, so a
|
|
7
|
+
// info-recorded (read by nothing here — it is the record), task-queued, task-done, prompt-received, prompt-finished. Unknown types are ignored, so a
|
|
8
8
|
// newer loop's log is readable.
|
|
9
9
|
|
|
10
10
|
const emptySlice = () => ({
|
|
@@ -164,7 +164,7 @@ const pageList = [...pages.values()].map(({name, title, route, hasParams, slices
|
|
|
164
164
|
write('web/src/app/model.ts', `// GENERATED by "eventmodelers scaffold" from the event model. Do not edit by hand: re-run the command.
|
|
165
165
|
// Navigation has one entry per timeline (chapter). Pages are one per distinct screen title (web kit convention).
|
|
166
166
|
|
|
167
|
-
export type SliceInfo = { title: string; type
|
|
167
|
+
export type SliceInfo = { title: string; type?: string; endpoint?: string };
|
|
168
168
|
export type TimelineInfo = { title: string; slug: string; pages: string[]; slices: SliceInfo[] };
|
|
169
169
|
export type PageInfo = { name: string; title: string; route: string; hasParams: boolean; slices: string[] };
|
|
170
170
|
|
|
@@ -60,7 +60,7 @@ export function TimelinePage() {
|
|
|
60
60
|
{timeline.slices.map((s) => (
|
|
61
61
|
<tr key={s.title}>
|
|
62
62
|
<td>{s.title}</td>
|
|
63
|
-
<td>{TYPE_LABEL[s.type] ?? s.type}</td>
|
|
63
|
+
<td>{s.type ? TYPE_LABEL[s.type] ?? s.type : '—'}</td>
|
|
64
64
|
<td><code>{s.endpoint ?? '—'}</code></td>
|
|
65
65
|
<td>{s.type === 'STATE_VIEW' && s.endpoint ? <ReadModelStatus slice={s} /> : <span className="has-text-grey">—</span>}</td>
|
|
66
66
|
</tr>
|
|
@@ -64,6 +64,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
64
64
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
65
65
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
66
66
|
|
|
67
|
+
### Recording Info
|
|
68
|
+
|
|
69
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
76
|
+
|
|
67
77
|
## Update AGENTS.md Files
|
|
68
78
|
|
|
69
79
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -112,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
112
112
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
113
113
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
114
114
|
|
|
115
|
+
### Recording Info
|
|
116
|
+
|
|
117
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
124
|
+
|
|
115
125
|
## Important
|
|
116
126
|
|
|
117
127
|
- Process **one task per iteration**.
|
|
@@ -54,6 +54,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
54
54
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
55
55
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
56
56
|
|
|
57
|
+
### Recording Info
|
|
58
|
+
|
|
59
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
66
|
+
|
|
57
67
|
## Update AGENTS.md Files
|
|
58
68
|
|
|
59
69
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files — API patterns/conventions, gotchas, dependencies between files, testing approaches, configuration/environment requirements.
|
|
@@ -112,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
112
112
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
113
113
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
114
114
|
|
|
115
|
+
### Recording Info
|
|
116
|
+
|
|
117
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
124
|
+
|
|
115
125
|
## Important
|
|
116
126
|
|
|
117
127
|
- Process **one task per iteration**.
|
|
@@ -58,6 +58,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
58
58
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
59
59
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
60
60
|
|
|
61
|
+
### Recording Info
|
|
62
|
+
|
|
63
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
70
|
+
|
|
61
71
|
## Update AGENTS.md Files
|
|
62
72
|
|
|
63
73
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files — API patterns/conventions, gotchas, dependencies between files, testing approaches, configuration/environment requirements.
|
|
@@ -116,6 +116,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
116
116
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
117
117
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
118
118
|
|
|
119
|
+
### Recording Info
|
|
120
|
+
|
|
121
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
128
|
+
|
|
119
129
|
## Important
|
|
120
130
|
|
|
121
131
|
- Process **one task per iteration**.
|
|
@@ -64,6 +64,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
64
64
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
65
65
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
66
66
|
|
|
67
|
+
### Recording Info
|
|
68
|
+
|
|
69
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
76
|
+
|
|
67
77
|
## Update AGENTS.md Files
|
|
68
78
|
|
|
69
79
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -112,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
112
112
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
113
113
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
114
114
|
|
|
115
|
+
### Recording Info
|
|
116
|
+
|
|
117
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
124
|
+
|
|
115
125
|
## Important
|
|
116
126
|
|
|
117
127
|
- Process **one task per iteration**.
|
|
@@ -35,6 +35,8 @@ From `$ARGUMENTS`: the context (a MODEL_CONTEXT name or a timeline name) and opt
|
|
|
35
35
|
|
|
36
36
|
The unit is the **effective context**: a timeline assigned to or connected with a MODEL_CONTEXT belongs to that context; a timeline with neither is its own context. Either name returns the whole effective context — every timeline in it, each slice carrying its `chapter`.
|
|
37
37
|
|
|
38
|
+
**Pass the id, not the name, whenever you have it** — the turn's `timelineId` (or a MODEL_CONTEXT's id) goes straight into `--context "<id>"`. A chapter may have no name at all; never rename a chapter to make the lookup work, and never ask about it.
|
|
39
|
+
|
|
38
40
|
No context given, or a name that matches nothing: list the options once —
|
|
39
41
|
|
|
40
42
|
```
|
|
@@ -97,10 +97,12 @@ async function status() {
|
|
|
97
97
|
return res.json();
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
// --context is a name, or the id of a MODEL_CONTEXT / timeline (a chapter without a name has only that).
|
|
101
|
+
const contextIsId = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args.context);
|
|
102
|
+
const ctx = `${contextIsId ? 'contextId' : 'contextName'}=${encodeURIComponent(args.context)}`;
|
|
101
103
|
const [fields, specs] = await Promise.all([
|
|
102
|
-
get(`/slicedata
|
|
103
|
-
get(`/slicedata
|
|
104
|
+
get(`/slicedata?${ctx}&projection=fields&format=json`),
|
|
105
|
+
get(`/slicedata?${ctx}&projection=specs&format=json`),
|
|
104
106
|
]);
|
|
105
107
|
const specsById = Object.fromEntries((specs.slices || []).map((s) => [s.id, s]));
|
|
106
108
|
|
|
@@ -127,7 +129,7 @@ async function status() {
|
|
|
127
129
|
// The baseline whose blocks can be kept: the page the platform stores for this context — what everyone
|
|
128
130
|
// plays, and there in any session or folder (its sanitizer leaves the blocks byte for byte) — else a
|
|
129
131
|
// local one, from an earlier run whose upload failed. Neither: every slice is new.
|
|
130
|
-
const contextId = args
|
|
132
|
+
const contextId = contextIsId ? args.context : await contextIdOf(get, args.context);
|
|
131
133
|
const storedRes = await fetch(`${api}/prototype/${encodeURIComponent(contextId)}`, { headers });
|
|
132
134
|
if (!storedRes.ok && storedRes.status !== 404) fail(`GET /prototype/${contextId} → ${storedRes.status} ${await storedRes.text()}`);
|
|
133
135
|
const stored = storedRes.ok ? await storedRes.text() : null;
|
|
@@ -158,7 +160,7 @@ async function status() {
|
|
|
158
160
|
const removed = existing.filter((b) => !current.has(b.id)).map(({ id, title }) => ({ id, title }));
|
|
159
161
|
const cssFramework = Object.entries(frameworkVotes).sort((a, b) => b[1] - a[1])[0]?.[0] || 'bulma';
|
|
160
162
|
|
|
161
|
-
const report = { context: args.context, contextId, baseline, cssFramework, mixedFrameworks: Object.keys(frameworkVotes).length > 1, slices, removed };
|
|
163
|
+
const report = { context: fields.contextName || args.context, contextId, baseline, cssFramework, mixedFrameworks: Object.keys(frameworkVotes).length > 1, slices, removed };
|
|
162
164
|
writeFileSync(join(WORK, 'status.json'), JSON.stringify(report, null, 2));
|
|
163
165
|
for (const s of slices) console.log(`${s.state.padEnd(9)} ${s.hash} ${s.id} ${s.title}`);
|
|
164
166
|
for (const r of removed) console.log(`removed ${''.padEnd(16)} ${r.id} ${r.title}`);
|
|
@@ -199,7 +199,7 @@ Steps:
|
|
|
199
199
|
ever get here in a `standalone=on` session, which is ad-hoc: its kit dir is shared across
|
|
200
200
|
every board and nobody reads it afterwards. The board is the only place anything is kept, so
|
|
201
201
|
anything reusable — including what a subagent reported back — goes there, as a comment on the
|
|
202
|
-
node it concerns.
|
|
202
|
+
node it concerns. The exception: end the reply with `Info [limitation|decision|note]: <one terse line>` for anything you could not change (and why) or decided — the CLI records it as an event, standalone included.
|
|
203
203
|
8. Reply `<promise>DONE</promise>`, naming what you dispatched and what each agent did, or —
|
|
204
204
|
when step 2 turned up nothing worth doing — change nothing at all and reply
|
|
205
205
|
`<promise>NOOP</promise>`. A NOOP is a perfectly good outcome, and the CLI widens the gap
|
|
@@ -19,7 +19,7 @@ session: it belongs to no project, its kit dir is a `~/.eventmodelers/kit` share
|
|
|
19
19
|
and nobody goes looking in there afterwards. So in a `standalone=on` session the **board is the
|
|
20
20
|
only place anything is kept** — comments, elements, slice statuses, scenarios. Write no file at
|
|
21
21
|
all: no `Learning:` lines in your reply (step 8), no `.agent-modeling-kit/AGENTS.md`, and nothing a skill's
|
|
22
|
-
own instructions suggest writing down either. Anything worth keeping goes on the board, as a
|
|
22
|
+
own instructions suggest writing down either. The one exception is `Info [kind]:` lines (step 8): the CLI records those as events in every session, standalone included. Anything worth keeping goes on the board, as a
|
|
23
23
|
comment on the node it concerns. This overrides every "write it down" instruction elsewhere in this
|
|
24
24
|
file and in any skill.
|
|
25
25
|
|
|
@@ -151,6 +151,7 @@ mention it in the `DONE` comment, and leave it for a self-directed turn (or for
|
|
|
151
151
|
6. **Mark the prompt as finished** — invoke `/update-prompt-status` with this turn's `prompt_id`, `newStatus=DONE`, and a `comment` that summarizes what you actually did (e.g. "Added the OrderPlaced event and wired it to the read model"). Do this once, right after the work is done — not per skill call within the turn.
|
|
152
152
|
7. If this turn has a `comment_id` field, invoke `/handle-comment` with `action=resolve`, `nodeId` from the resolved `NODE_ID` (step 3), `commentId` from `comment_id`.
|
|
153
153
|
8. **`standalone=off` only** — if you noticed something reusable this turn (a pattern, a gotcha, useful context about this model), end your reply with one line per learning: `Learning [<topic>]: <one terse line>` (the topic is optional, at most five lines). The CLI records them — never write them into a file yourself, and add nothing that is already in `.agent-modeling-kit/AGENTS.md`. In a `standalone=on` session skip this: that session writes no files (see Mode), so note anything worth keeping as a board comment instead.
|
|
154
|
+
**In every session, `standalone=on` too:** anything that is *not* a reusable learning — something you could not change and why, a decision you took — goes the same way as `Info [limitation|decision|note]: <one terse line>` (at most five lines). The CLI records each as an event; never leave it only in prose.
|
|
154
155
|
9. Reply `<promise>DONE</promise>` and wait for the next turn.
|
|
155
156
|
|
|
156
157
|
|
|
@@ -58,6 +58,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
58
58
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
59
59
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
60
60
|
|
|
61
|
+
### Recording Info
|
|
62
|
+
|
|
63
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
70
|
+
|
|
61
71
|
## Update AGENTS.md Files
|
|
62
72
|
|
|
63
73
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -94,6 +94,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
94
94
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
95
95
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
96
96
|
|
|
97
|
+
### Recording Info
|
|
98
|
+
|
|
99
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
106
|
+
|
|
97
107
|
## Important
|
|
98
108
|
|
|
99
109
|
- Process **one task per iteration**.
|
|
@@ -64,6 +64,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
64
64
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
65
65
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
66
66
|
|
|
67
|
+
### Recording Info
|
|
68
|
+
|
|
69
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
76
|
+
|
|
67
77
|
## Update AGENTS.md Files
|
|
68
78
|
|
|
69
79
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -112,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
112
112
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
113
113
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
114
114
|
|
|
115
|
+
### Recording Info
|
|
116
|
+
|
|
117
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
124
|
+
|
|
115
125
|
## Important
|
|
116
126
|
|
|
117
127
|
- Process **one task per iteration**.
|
|
@@ -54,6 +54,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
54
54
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
55
55
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
56
56
|
|
|
57
|
+
### Recording Info
|
|
58
|
+
|
|
59
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
66
|
+
|
|
57
67
|
## Update AGENTS.md Files
|
|
58
68
|
|
|
59
69
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files — API patterns/conventions, gotchas, dependencies between files, testing approaches, configuration/environment requirements.
|
|
@@ -125,6 +125,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
125
125
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
126
126
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
127
127
|
|
|
128
|
+
### Recording Info
|
|
129
|
+
|
|
130
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
137
|
+
|
|
128
138
|
## Important
|
|
129
139
|
|
|
130
140
|
- Process **one task per iteration**.
|
|
@@ -58,6 +58,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
58
58
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
59
59
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
60
60
|
|
|
61
|
+
### Recording Info
|
|
62
|
+
|
|
63
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
70
|
+
|
|
61
71
|
## Update AGENTS.md Files
|
|
62
72
|
|
|
63
73
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -95,6 +95,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
95
95
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
96
96
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
97
97
|
|
|
98
|
+
### Recording Info
|
|
99
|
+
|
|
100
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
107
|
+
|
|
98
108
|
## Important
|
|
99
109
|
|
|
100
110
|
- Process **one task per iteration**.
|
|
@@ -54,6 +54,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
54
54
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
55
55
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
56
56
|
|
|
57
|
+
### Recording Info
|
|
58
|
+
|
|
59
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
66
|
+
|
|
57
67
|
## Update AGENTS.md Files
|
|
58
68
|
|
|
59
69
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files — API patterns/conventions, gotchas, dependencies between files, testing approaches, configuration/environment requirements.
|
|
@@ -135,6 +135,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
135
135
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
136
136
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
137
137
|
|
|
138
|
+
### Recording Info
|
|
139
|
+
|
|
140
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
147
|
+
|
|
138
148
|
## Important
|
|
139
149
|
|
|
140
150
|
- Process **one task per iteration**.
|
|
@@ -64,6 +64,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
64
64
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
65
65
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
66
66
|
|
|
67
|
+
### Recording Info
|
|
68
|
+
|
|
69
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
76
|
+
|
|
67
77
|
## Update AGENTS.md Files
|
|
68
78
|
|
|
69
79
|
Before committing, check if any edited files have learnings worth preserving in nearby AGENTS.md files:
|
|
@@ -112,6 +112,16 @@ npx @eventmodelers/cli learn "<one terse line>" --topic "<topic>"
|
|
|
112
112
|
- `--scope system` for what is true of the modeled domain (a business rule, a naming convention of the model); `repo` (the default) for the codebase and its tooling. `--tag <tag>` labels it (repeatable).
|
|
113
113
|
- One in `.build-kit/AGENTS.md` that turned out wrong or obsolete: `npx @eventmodelers/cli learnings --ids` lists each with its id, `npx @eventmodelers/cli learn --retract <id>` removes it.
|
|
114
114
|
|
|
115
|
+
### Recording Info
|
|
116
|
+
|
|
117
|
+
Something that is **not** a reusable learning — you cannot change something and why, a decision you took, a note for whoever reads the board — is an info, not a learning. Never leave it only in your reply:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
npx @eventmodelers/cli info "<one terse line>" --kind limitation|decision|note
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
It is recorded as an event and posted as a comment on the slice you are building (`--slice <id>` to name another). Reusable knowledge stays a `learn`.
|
|
124
|
+
|
|
115
125
|
## Important
|
|
116
126
|
|
|
117
127
|
- Process **one task per iteration**.
|