@eventmodelers/node-kit 0.0.12 → 0.0.13
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/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-slice-status
|
|
3
|
+
description: Update the status of a single slice on an eventmodelers board by changing the SLICE_BORDER node's sliceStatus field
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Update Slice Status
|
|
7
|
+
|
|
8
|
+
> **Before doing anything else**, invoke the `connect` skill to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until the connect skill has completed.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Step 1 — Parse arguments
|
|
13
|
+
|
|
14
|
+
From `$ARGUMENTS`, extract:
|
|
15
|
+
|
|
16
|
+
| Field | How to find it | Default |
|
|
17
|
+
|-------|---------------|---------|
|
|
18
|
+
| `sliceName` | the slice title to update (case-insensitive match) | **required** |
|
|
19
|
+
| `newStatus` | the target status value | **required** |
|
|
20
|
+
|
|
21
|
+
Valid status values (case-sensitive):
|
|
22
|
+
|
|
23
|
+
| Value | Meaning |
|
|
24
|
+
|-------|---------|
|
|
25
|
+
| `Created` | Default — slice has been created but not started |
|
|
26
|
+
| `Planned` | Work is planned |
|
|
27
|
+
| `InProgress` | Work is actively in progress |
|
|
28
|
+
| `Review` | Ready for review |
|
|
29
|
+
| `Done` | Completed |
|
|
30
|
+
| `Blocked` | Blocked by something |
|
|
31
|
+
| `Assigned` | Assigned to someone |
|
|
32
|
+
| `Informational` | Informational / reference slice |
|
|
33
|
+
|
|
34
|
+
If `newStatus` is not one of these exact values, stop and tell the user the valid options.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Step 2 — List all slices
|
|
39
|
+
|
|
40
|
+
Fetch all slices on the board:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s \
|
|
44
|
+
-H "x-token: <TOKEN>" \
|
|
45
|
+
-H "x-board-id: <BOARD_ID>" \
|
|
46
|
+
-H "x-user-id: update-slice-status-skill" \
|
|
47
|
+
"<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/slicedata/slices"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Response: `{ "slices": [{ "id": "<nodeId>", "title": "<title>", "status": "<status>" }] }`
|
|
51
|
+
|
|
52
|
+
The `id` here is the `SLICE_BORDER` node ID — use it directly in Step 3.
|
|
53
|
+
|
|
54
|
+
Find the slice whose `title` matches `sliceName` (case-insensitive). If no match is found, stop and list the available slice titles so the user can pick one.
|
|
55
|
+
|
|
56
|
+
Save the matched slice as:
|
|
57
|
+
- `SLICE_NODE_ID` — the node ID of the SLICE_BORDER
|
|
58
|
+
- `CURRENT_STATUS` — the current status value
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Step 3 — Update the slice status
|
|
63
|
+
|
|
64
|
+
Send a `node:changed` event to update the `sliceStatus` field in the SLICE_BORDER node's meta:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-H "x-token: <TOKEN>" \
|
|
70
|
+
-H "x-board-id: <BOARD_ID>" \
|
|
71
|
+
-H "x-user-id: update-slice-status-skill" \
|
|
72
|
+
-d '[{
|
|
73
|
+
"id": "<new-random-uuid>",
|
|
74
|
+
"eventType": "node:changed",
|
|
75
|
+
"nodeId": "<SLICE_NODE_ID>",
|
|
76
|
+
"boardId": "<BOARD_ID>",
|
|
77
|
+
"timestamp": <Date.now()>,
|
|
78
|
+
"changedAttributes": ["sliceStatus"],
|
|
79
|
+
"meta": {
|
|
80
|
+
"sliceStatus": "<newStatus>"
|
|
81
|
+
}
|
|
82
|
+
}]'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Response: `{ "hashes": { "<eventId>": "<hash>" } }`
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Step 4 — Report back
|
|
90
|
+
|
|
91
|
+
Tell the user:
|
|
92
|
+
|
|
93
|
+
- **Slice**: the title that was updated
|
|
94
|
+
- **Previous status**: `CURRENT_STATUS`
|
|
95
|
+
- **New status**: `newStatus`
|
|
96
|
+
- **Node ID**: `SLICE_NODE_ID`
|
|
97
|
+
- **Any errors**: raw API message if something failed
|
|
98
|
+
|
|
99
|
+
Example success output:
|
|
100
|
+
```
|
|
101
|
+
Updated: "Order Placed" slice
|
|
102
|
+
Before: InProgress
|
|
103
|
+
After: Done
|
|
104
|
+
Node ID: a1b2c3d4-…
|
|
105
|
+
```
|
|
@@ -10,7 +10,7 @@ The structure defined in the Project-Skills is relevant.
|
|
|
10
10
|
1. Read the description at `.slices/index.json` (in the same directory as this file). Every item in status "planned" is a task.
|
|
11
11
|
2. Read the progress log at `progress.txt` (check Codebase Patterns section first)
|
|
12
12
|
3. Make sure you are on the right branch "feature/<slicename>", if unsure, start from main.
|
|
13
|
-
5. Pick the **highest priority** slice where status is "planned" ( case insensitive ). This becomes your PRD. Set the status "InProgress" in the index.json. If no slice has status planned, reply with:
|
|
13
|
+
5. Pick the **highest priority** slice where status is "planned" ( case insensitive ). This becomes your PRD. Set the status "InProgress" in the index.json **and** update the slice status on the eventmodelers board using the `update-slice-status` skill (or MCP if available). If no slice has status planned, reply with:
|
|
14
14
|
<promise>NO_TASKS</promise> and stop. Do not work on other slices.
|
|
15
15
|
6. Pick the slice definition from the project root /.slices in <folder> defined in the prd. Never work on more than one slice per iteration.
|
|
16
16
|
7. A slice can define additional prompts as codegen/backendPrompt. any additional prompts defined in backend are hints for the implementation of the slice and have to be taken into account. If you use the additional prompt, add a line in progress.txt
|
|
@@ -26,7 +26,7 @@ The structure defined in the Project-Skills is relevant.
|
|
|
26
26
|
14. even if the slice is fully implemented, run your test-analyzer skill and provide the code-slice.json file as defined in the skill
|
|
27
27
|
15. If checks pass, commit ALL changes with message: `feat: [Slice Name]` and merge back to main as FF merge ( update
|
|
28
28
|
first )
|
|
29
|
-
16. Update the PRD to set `status: Done` for the completed story.
|
|
29
|
+
16. Update the PRD to set `status: Done` for the completed story in index.json **and** update the slice status on the eventmodelers board using the `update-slice-status` skill (or MCP if available).
|
|
30
30
|
17. Append your progress to `progress.txt` after each step in the iteration.
|
|
31
31
|
18. append your new learnings to AGENTS.md in a compressed form, reusable for future iterations. Only add learnings if they are not already there.
|
|
32
32
|
19. Finish the iteration.
|
package/templates/root/ralph.sh
CHANGED
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
#
|
|
7
7
|
# The phases are NOT causally linked — either can trigger on its own.
|
|
8
8
|
#
|
|
9
|
-
# Usage: ./ralph.sh [project_dir]
|
|
9
|
+
# Usage: ./ralph.sh [iterations] [project_dir]
|
|
10
|
+
# iterations — number of loop cycles to run; 0 or omitted means run forever
|
|
11
|
+
# project_dir — defaults to current working directory
|
|
10
12
|
|
|
11
13
|
set -euo pipefail
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
PROJECT_DIR="${
|
|
15
|
+
ITERATIONS="${1:-0}"
|
|
16
|
+
PROJECT_DIR="${2:-.}"
|
|
15
17
|
TASKS_FILE="$PROJECT_DIR/tasks.json"
|
|
16
18
|
PROMPT_FILE="$PROJECT_DIR/prompt.md"
|
|
17
19
|
BACKEND_PROMPT_FILE="$PROJECT_DIR/backend-prompt.md"
|
|
@@ -49,7 +51,8 @@ run_agent() {
|
|
|
49
51
|
done
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
cycle=0
|
|
55
|
+
while [[ "$ITERATIONS" -eq 0 || "$cycle" -lt "$ITERATIONS" ]]; do
|
|
53
56
|
ran_something=false
|
|
54
57
|
|
|
55
58
|
if has_pending_tasks; then
|
|
@@ -65,4 +68,6 @@ while true; do
|
|
|
65
68
|
if [[ "$ran_something" == false ]]; then
|
|
66
69
|
sleep 3
|
|
67
70
|
fi
|
|
71
|
+
|
|
72
|
+
(( cycle++ )) || true
|
|
68
73
|
done
|
package/templates/root/server.ts
CHANGED
|
@@ -4,15 +4,10 @@ import {glob} from "glob";
|
|
|
4
4
|
import express, {Application, Request, Response} from 'express';
|
|
5
5
|
import {jsonBigIntReplacer} from './src/util/sanitize';
|
|
6
6
|
import {requireUser} from "./src/supabase/requireUser";
|
|
7
|
-
import {requireBotApiToken} from "./src/slices/change/requireApiToken";
|
|
8
|
-
import {mcpAuthRouter} from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
9
|
-
import type {SupabaseOAuthProvider} from './src/slices/mcp/SupabaseOAuthProvider';
|
|
10
|
-
import {isOrgLicenseActive, CallerContext} from "./src/slices/organization/OrganizationLicense/IsLicenseActive";
|
|
11
7
|
import {getKnexInstance, closeDb} from "./src/common/db";
|
|
12
8
|
import swaggerUi from 'swagger-ui-express'
|
|
13
9
|
import {specs} from './src/swagger';
|
|
14
10
|
import cors from 'cors';
|
|
15
|
-
import {testPageHtml} from "./src/slices/internal/testing/routes";
|
|
16
11
|
import {findEventstore} from "./src/common/loadPostgresEventstore";
|
|
17
12
|
import {PostgresEventStore} from "@event-driven-io/emmett-postgresql";
|
|
18
13
|
|
|
@@ -88,14 +83,6 @@ async function startServer() {
|
|
|
88
83
|
});
|
|
89
84
|
childApp.set('json replacer', jsonBigIntReplacer);
|
|
90
85
|
|
|
91
|
-
// Add your custom routes to the main application (BEFORE the catch-all)
|
|
92
|
-
if (process.env.TESTING === 'true') {
|
|
93
|
-
childApp.get('/internal/test', (req: Request, res: Response) => {
|
|
94
|
-
res.setHeader('Content-Type', 'text/html');
|
|
95
|
-
res.send(testPageHtml);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
86
|
// Protected user info endpoint - requires JWT token in Authorization header
|
|
100
87
|
childApp.get('/api/user', async (req: Request, res: Response) => {
|
|
101
88
|
console.log('API user route hit'); // Debug log
|
|
@@ -143,57 +130,13 @@ async function startServer() {
|
|
|
143
130
|
const port = parseInt(process.env.PORT || '3000', 10);
|
|
144
131
|
console.log(`> Ready on port ${port}`);
|
|
145
132
|
|
|
146
|
-
const authenticate = async (req: Request, res: Response, next: () => void) => {
|
|
147
|
-
if (req.headers["x-token"]) {
|
|
148
|
-
const auth = await requireBotApiToken(req, res);
|
|
149
|
-
if (!auth) return;
|
|
150
|
-
req.tokenAuth = auth;
|
|
151
|
-
} else {
|
|
152
|
-
const principal = await requireUser(req, res, true);
|
|
153
|
-
if (principal.error) return;
|
|
154
|
-
req.userAuth = {id: principal.user.id, email: principal.user.email};
|
|
155
|
-
}
|
|
156
|
-
next();
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
rootApp.use('/api/org', authenticate);
|
|
160
|
-
rootApp.use('/api/boards', authenticate);
|
|
161
|
-
rootApp.use('/api/snapshots', authenticate);
|
|
162
|
-
rootApp.use('/api/takesnapshot', authenticate);
|
|
163
|
-
rootApp.use('/api/replay', authenticate);
|
|
164
|
-
|
|
165
|
-
rootApp.use('/api/org', async (req: Request, res: Response, next) => {
|
|
166
|
-
const boardMatch = req.path.match(/^\/([^/]+)\/boards\/([^/]+)\//);
|
|
167
|
-
if (!boardMatch) return next();
|
|
168
|
-
|
|
169
|
-
const [, orgId, boardId] = boardMatch;
|
|
170
|
-
const caller: CallerContext = req.tokenAuth
|
|
171
|
-
? {kind: 'token', organizationId: req.tokenAuth.organizationId}
|
|
172
|
-
: {kind: 'user', userId: req.userAuth!.id};
|
|
173
|
-
|
|
174
|
-
const result = await isOrgLicenseActive(orgId, boardId, caller);
|
|
175
|
-
if (!result.active) return res.status(403).json({error: 'license_inactive', reason: result.reason});
|
|
176
|
-
|
|
177
|
-
next();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
133
|
rootApp.use((req: Request, _res: Response, next) => {
|
|
181
134
|
console.log(`[${req.method}] ${req.path}`);
|
|
182
135
|
next();
|
|
183
136
|
});
|
|
184
137
|
|
|
185
|
-
const backendUrl = process.env.BACKEND_URL;
|
|
186
|
-
// Load oauthProvider from the same compiled dist module that routes.js uses,
|
|
187
|
-
// so both share the same in-memory pendingAuths/authCodes Maps.
|
|
188
|
-
const providerPath = join(__dirname, 'dist/src/slices/mcp/SupabaseOAuthProvider.js');
|
|
189
|
-
const {oauthProvider} = await import(providerPath) as {oauthProvider: SupabaseOAuthProvider};
|
|
190
138
|
rootApp.use(express.json());
|
|
191
|
-
|
|
192
|
-
provider: oauthProvider,
|
|
193
|
-
issuerUrl: new URL(backendUrl),
|
|
194
|
-
resourceServerUrl: new URL(`${backendUrl}/mcp`),
|
|
195
|
-
scopesSupported: ['mcp:tools'],
|
|
196
|
-
}));
|
|
139
|
+
|
|
197
140
|
|
|
198
141
|
rootApp.use(childApp)
|
|
199
142
|
// Start the main application
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import {getPostgreSQLEventStore} from "@event-driven-io/emmett-postgresql";
|
|
2
2
|
import {projections} from "@event-driven-io/emmett";
|
|
3
3
|
import {postgresUrl, getSharedPool} from "./db";
|
|
4
|
-
import {CreatedOrganizationsProjection} from "../slices/organization/CreatedOrganizations/CreatedOrganizationsProjection";
|
|
5
|
-
import {OrganizationLicenseProjection} from "../slices/organization/OrganizationLicense/OrganizationLicenseProjection";
|
|
6
|
-
import {InvitesProjection} from "../slices/organization/Invites/InvitesProjection";
|
|
7
|
-
import {OrganizationBoardsProjection} from "../slices/organization/OrganizationBoards/OrganizationBoardsProjection";
|
|
8
|
-
import {ActiveTokensProjection} from "../slices/organization/ActiveTokens/ActiveTokensProjection";
|
|
9
|
-
import {LicenseSeatsProjection} from "../slices/organization/LicenseSeats/LicenseSeatsProjection";
|
|
10
|
-
import {UserOrganizationsProjection} from "../slices/organization/UserOrganizations/UserOrganizationsProjection";
|
|
11
|
-
import {EnabledUsersProjection} from "../slices/beta/EnabledUsers/EnabledUsersProjection";
|
|
12
4
|
|
|
13
5
|
let eventStoreInstance: ReturnType<typeof getPostgreSQLEventStore> | null = null;
|
|
14
6
|
|
|
@@ -23,14 +15,6 @@ export const findEventstore = async () => {
|
|
|
23
15
|
pool: getSharedPool(),
|
|
24
16
|
},
|
|
25
17
|
projections: projections.inline([
|
|
26
|
-
CreatedOrganizationsProjection,
|
|
27
|
-
OrganizationLicenseProjection,
|
|
28
|
-
InvitesProjection,
|
|
29
|
-
OrganizationBoardsProjection,
|
|
30
|
-
ActiveTokensProjection,
|
|
31
|
-
LicenseSeatsProjection,
|
|
32
|
-
UserOrganizationsProjection,
|
|
33
|
-
EnabledUsersProjection,
|
|
34
18
|
]),
|
|
35
19
|
});
|
|
36
20
|
await eventStoreInstance.schema.migrate();
|