@monster-orange/den 0.3.0 → 0.4.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/CHANGELOG.md +593 -0
- package/docs/distributions.md +429 -18
- package/package.json +6 -1
- package/src/shared/actor-token.ts +275 -0
- package/src/shared/agent-actor.ts +12 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +271 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +199 -0
- package/src/shared/agents/runtimes.ts +54 -0
- package/src/shared/den-config.ts +116 -0
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/scope.ts +22 -0
- package/src/shared/environment-manager/service-runner.ts +94 -2
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/profile.ts +15 -4
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/sessions/client.ts +284 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +89 -0
- package/src/shared/sessions/host.ts +690 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/web-secret.ts +181 -0
- package/src/shared/web-session.ts +491 -0
- package/src/ui/cli/agent-ops.ts +843 -0
- package/src/ui/cli/agents-command.ts +294 -0
- package/src/ui/cli/doctor-command.ts +138 -9
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +32 -3
- package/src/ui/cli/index.ts +128 -12
- package/src/ui/cli/sessions-command.ts +750 -0
- package/src/ui/cli/target-workspace.ts +105 -0
- package/src/ui/cli/web-command.ts +536 -14
- package/src/ui/mcp/api.ts +7 -0
- package/src/ui/mcp/scope.ts +123 -19
- package/src/ui/mcp/server.ts +100 -7
- package/src/ui/mcp/tools.ts +44 -5
- package/src/ui/server/agent-routes.ts +323 -0
- package/src/ui/server/audit.ts +111 -22
- package/src/ui/server/http-errors.ts +21 -3
- package/src/ui/server/index.ts +820 -65
- package/src/ui/server/public/agents-page.js +648 -0
- package/src/ui/server/public/agents-section.js +299 -0
- package/src/ui/server/public/agents.html +439 -0
- package/src/ui/server/public/appearance.js +3 -0
- package/src/ui/server/public/error-page.js +103 -0
- package/src/ui/server/public/header.js +45 -11
- package/src/ui/server/public/i18n/es.js +90 -1
- package/src/ui/server/public/i18n/ja.js +86 -1
- package/src/ui/server/public/local-dev.html +53 -0
- package/src/ui/server/public/logs.html +3 -0
- package/src/ui/server/public/panel.js +1 -1
- package/src/ui/server/public/release.html +8 -0
- package/src/ui/server/public/settings-page.js +5 -0
- package/src/ui/server/public/settings-registry.js +33 -0
- package/src/ui/server/public/shell.js +248 -5
- package/src/ui/server/public/topbar.js +51 -2
- package/src/ui/server/readonly-routes.ts +25 -2
- package/src/ui/server/request-guard.ts +670 -0
- package/src/ui/server/route-classes.ts +317 -0
- package/src/ui/server/session-routes.ts +445 -0
- package/src/ui/server/shutdown-guard.ts +91 -0
- package/src/ui/server/sudo-store.ts +29 -3
- package/src/ui/server/ui-assets.ts +10 -1
- package/src/ui/server/websocket.ts +326 -0
- package/src/ui/server/workspace-routes.ts +16 -22
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEC-1c (SEC-1 spec §5): an agent's actor token.
|
|
3
|
+
*
|
|
4
|
+
* `agents start` (and resume) mints `den_at_<43 b64url>`: 32 random bytes,
|
|
5
|
+
* handed to the agent's session as DEN_ACTOR_TOKEN and to nothing else.
|
|
6
|
+
* The state dir keeps only its sha256 (`actor-tokens.json`, 0600, the dir
|
|
7
|
+
* 0700), with what it proves:
|
|
8
|
+
*
|
|
9
|
+
* { actor: "agent:<id>", workspace, grants, session: { name, id } }
|
|
10
|
+
*
|
|
11
|
+
* - One live token per agent: minting another (a resume, a restart)
|
|
12
|
+
* revokes the old one, so it stops working at once.
|
|
13
|
+
* - Revoked when the agent stops (`agents stop`, the page's Stop), and when
|
|
14
|
+
* its session ends: a token verifies only while the session it was minted
|
|
15
|
+
* for is listed as running in the session host's own record
|
|
16
|
+
* (`sessions/<id>.json`); an ended one's token is removed when presented.
|
|
17
|
+
* - It never derives from web.secret, so it proves nothing about the
|
|
18
|
+
* person. `web logout --all` (the "something leaked" button, SEC-1
|
|
19
|
+
* §6c.2) revokes every token as well as rotating the secret: each record
|
|
20
|
+
* is kept, marked `revokedAt`, so a running agent's next call is told
|
|
21
|
+
* `token_revoked` (restart or resume it) rather than a bare 401. Minting
|
|
22
|
+
* the agent's next token, or its session's end, drops the record.
|
|
23
|
+
*
|
|
24
|
+
* Nothing here prints or logs a token.
|
|
25
|
+
*/
|
|
26
|
+
import { createHash, randomBytes } from "crypto";
|
|
27
|
+
import * as fs from "fs";
|
|
28
|
+
import * as path from "path";
|
|
29
|
+
import { AGENT_ACTOR_RE, WORKSPACE_NAME_RE } from "./agent-actor.js";
|
|
30
|
+
import { sessionPaths } from "./sessions/paths.js";
|
|
31
|
+
|
|
32
|
+
export const ACTOR_TOKEN_PREFIX = "den_at_";
|
|
33
|
+
/** The agent's session carries its token here (AG spec §5a-env). */
|
|
34
|
+
export const ACTOR_TOKEN_ENV = "DEN_ACTOR_TOKEN";
|
|
35
|
+
/** A person's token in an env: never forwarded to any session. */
|
|
36
|
+
export const PERSON_TOKEN_ENV = "DEN_PERSON_TOKEN";
|
|
37
|
+
export const ACTOR_TOKENS_FILE = "actor-tokens.json";
|
|
38
|
+
|
|
39
|
+
const TOKEN_RE = /^den_at_[A-Za-z0-9_-]{43}$/;
|
|
40
|
+
/** web-secret.ts's PERSON_TOKEN_PREFIX and WEB_SECRET_FILE (it imports this module). */
|
|
41
|
+
const PERSON_PREFIX = "den_pt_";
|
|
42
|
+
const SECRET_FILE = "web.secret";
|
|
43
|
+
|
|
44
|
+
export interface ActorTokenRecord {
|
|
45
|
+
actor: `agent:${string}`;
|
|
46
|
+
workspace: string;
|
|
47
|
+
/** The agent's grants (agents.<id>.grants) when it was minted. */
|
|
48
|
+
grants: string[];
|
|
49
|
+
/** The session it was minted for: its name, and its id once started. */
|
|
50
|
+
session: { name: string; id?: string };
|
|
51
|
+
/** ISO 8601. */
|
|
52
|
+
createdAt: string;
|
|
53
|
+
/** ISO 8601: revoked by `web logout --all`. A revoked token never verifies. */
|
|
54
|
+
revokedAt?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface StoreFile {
|
|
58
|
+
v: 1;
|
|
59
|
+
/** sha256(token), hex → what it proves. */
|
|
60
|
+
tokens: Record<string, ActorTokenRecord>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function actorTokensPath(stateDir: string): string {
|
|
64
|
+
return path.join(stateDir, ACTOR_TOKENS_FILE);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The token's key in the store: sha256, hex. */
|
|
68
|
+
export function actorTokenKey(token: string): string {
|
|
69
|
+
return createHash("sha256").update(token).digest("hex");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Shaped like an actor token (says nothing about whether it is live). */
|
|
73
|
+
export function isActorTokenShape(v: string | undefined): v is string {
|
|
74
|
+
return typeof v === "string" && TOKEN_RE.test(v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function load(stateDir: string): StoreFile {
|
|
78
|
+
try {
|
|
79
|
+
const raw = JSON.parse(
|
|
80
|
+
fs.readFileSync(actorTokensPath(stateDir), "utf-8"),
|
|
81
|
+
) as Partial<StoreFile>;
|
|
82
|
+
const tokens =
|
|
83
|
+
raw.tokens && typeof raw.tokens === "object" && !Array.isArray(raw.tokens)
|
|
84
|
+
? raw.tokens
|
|
85
|
+
: {};
|
|
86
|
+
return { v: 1, tokens };
|
|
87
|
+
} catch {
|
|
88
|
+
return { v: 1, tokens: {} };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function save(stateDir: string, data: StoreFile): void {
|
|
93
|
+
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
94
|
+
const file = actorTokensPath(stateDir);
|
|
95
|
+
const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
96
|
+
fs.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
97
|
+
fs.chmodSync(tmp, 0o600);
|
|
98
|
+
fs.renameSync(tmp, file);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A fresh token for agent `agentId`, its hash recorded, every older token
|
|
103
|
+
* of the same agent revoked. The token itself is returned once and never
|
|
104
|
+
* stored.
|
|
105
|
+
*/
|
|
106
|
+
export function mintActorToken(
|
|
107
|
+
stateDir: string,
|
|
108
|
+
o: {
|
|
109
|
+
agentId: string;
|
|
110
|
+
workspace: string;
|
|
111
|
+
grants?: readonly string[];
|
|
112
|
+
sessionName: string;
|
|
113
|
+
},
|
|
114
|
+
): { token: string; key: string } {
|
|
115
|
+
const actor = `agent:${o.agentId}` as const;
|
|
116
|
+
if (!AGENT_ACTOR_RE.test(actor))
|
|
117
|
+
throw new Error(`"${o.agentId}" is not an agent id`);
|
|
118
|
+
if (!WORKSPACE_NAME_RE.test(o.workspace))
|
|
119
|
+
throw new Error(`"${o.workspace}" is not a workspace name`);
|
|
120
|
+
const token = ACTOR_TOKEN_PREFIX + randomBytes(32).toString("base64url");
|
|
121
|
+
const key = actorTokenKey(token);
|
|
122
|
+
const data = load(stateDir);
|
|
123
|
+
for (const [k, r] of Object.entries(data.tokens))
|
|
124
|
+
if (r.actor === actor) delete data.tokens[k];
|
|
125
|
+
data.tokens[key] = {
|
|
126
|
+
actor,
|
|
127
|
+
workspace: o.workspace,
|
|
128
|
+
grants: (o.grants ?? []).filter((g) => WORKSPACE_NAME_RE.test(g)),
|
|
129
|
+
session: { name: o.sessionName },
|
|
130
|
+
createdAt: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
save(stateDir, data);
|
|
133
|
+
return { token, key };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Bind a minted token to the session id the host gave its session. */
|
|
137
|
+
export function bindActorSession(
|
|
138
|
+
stateDir: string,
|
|
139
|
+
key: string,
|
|
140
|
+
sessionId: string,
|
|
141
|
+
): void {
|
|
142
|
+
const data = load(stateDir);
|
|
143
|
+
const rec = data.tokens[key];
|
|
144
|
+
if (!rec) return;
|
|
145
|
+
rec.session = { ...rec.session, id: sessionId };
|
|
146
|
+
save(stateDir, data);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Revoke one token (by its key). */
|
|
150
|
+
export function revokeActorToken(stateDir: string, key: string): boolean {
|
|
151
|
+
const data = load(stateDir);
|
|
152
|
+
if (!data.tokens[key]) return false;
|
|
153
|
+
delete data.tokens[key];
|
|
154
|
+
save(stateDir, data);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Revoke every token of agent `agentId`; how many there were. */
|
|
159
|
+
export function revokeAgentTokens(stateDir: string, agentId: string): number {
|
|
160
|
+
const data = load(stateDir);
|
|
161
|
+
const actor = `agent:${agentId}`;
|
|
162
|
+
let n = 0;
|
|
163
|
+
for (const [k, r] of Object.entries(data.tokens))
|
|
164
|
+
if (r.actor === actor) {
|
|
165
|
+
delete data.tokens[k];
|
|
166
|
+
n++;
|
|
167
|
+
}
|
|
168
|
+
if (n) save(stateDir, data);
|
|
169
|
+
return n;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Revoke every agent's token (`web logout --all`, SEC-1 §6c.2): each record
|
|
174
|
+
* is marked revoked, so its agent learns why. How many were live.
|
|
175
|
+
*/
|
|
176
|
+
export function revokeAllActorTokens(stateDir: string): number {
|
|
177
|
+
const data = load(stateDir);
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
let n = 0;
|
|
180
|
+
for (const r of Object.values(data.tokens))
|
|
181
|
+
if (!r.revokedAt) {
|
|
182
|
+
r.revokedAt = now;
|
|
183
|
+
n++;
|
|
184
|
+
}
|
|
185
|
+
if (n) save(stateDir, data);
|
|
186
|
+
return n;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The agents whose token `web logout --all` revoked and whose record is
|
|
191
|
+
* still kept (their session still runs): the Agents tab marks them, so a
|
|
192
|
+
* person knows to restart or resume them (AG-4a-term). Ids only.
|
|
193
|
+
*/
|
|
194
|
+
export function revokedAgentIds(stateDir: string): Set<string> {
|
|
195
|
+
const out = new Set<string>();
|
|
196
|
+
for (const r of Object.values(load(stateDir).tokens))
|
|
197
|
+
if (r.revokedAt && AGENT_ACTOR_RE.test(r.actor))
|
|
198
|
+
out.add(r.actor.slice("agent:".length));
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** What a presented token was minted as, live or not; null when unknown. */
|
|
203
|
+
export function lookupActorToken(
|
|
204
|
+
stateDir: string,
|
|
205
|
+
token: string,
|
|
206
|
+
): ActorTokenRecord | null {
|
|
207
|
+
if (!isActorTokenShape(token)) return null;
|
|
208
|
+
return load(stateDir).tokens[actorTokenKey(token)] ?? null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Whether the session host lists session `id` as running (its own record). */
|
|
212
|
+
export function sessionRunning(stateDir: string, id: string): boolean {
|
|
213
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(id)) return false;
|
|
214
|
+
try {
|
|
215
|
+
const rec = JSON.parse(
|
|
216
|
+
fs.readFileSync(
|
|
217
|
+
path.join(sessionPaths(stateDir).dir, `${id}.json`),
|
|
218
|
+
"utf-8",
|
|
219
|
+
),
|
|
220
|
+
) as { exited?: unknown };
|
|
221
|
+
return rec.exited !== true;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* What a presented token proves: its record; `"revoked"` when `web logout
|
|
229
|
+
* --all` revoked it and its session still runs (the agent is told to
|
|
230
|
+
* restart or resume); or null: unknown, not yet bound to a session, or its
|
|
231
|
+
* session has ended (then its record is removed here).
|
|
232
|
+
*/
|
|
233
|
+
export function checkActorToken(
|
|
234
|
+
stateDir: string,
|
|
235
|
+
token: string,
|
|
236
|
+
): ActorTokenRecord | "revoked" | null {
|
|
237
|
+
const rec = lookupActorToken(stateDir, token);
|
|
238
|
+
if (!rec?.session.id) return null;
|
|
239
|
+
if (!sessionRunning(stateDir, rec.session.id)) {
|
|
240
|
+
try {
|
|
241
|
+
revokeActorToken(stateDir, actorTokenKey(token));
|
|
242
|
+
} catch {
|
|
243
|
+
/* a read-only state dir: still refused */
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return rec.revokedAt ? "revoked" : rec;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The record a presented token proves, or null (checkActorToken, revoked or not). */
|
|
251
|
+
export function verifyActorToken(
|
|
252
|
+
stateDir: string,
|
|
253
|
+
token: string,
|
|
254
|
+
): ActorTokenRecord | null {
|
|
255
|
+
const r = checkActorToken(stateDir, token);
|
|
256
|
+
return r === "revoked" ? null : r;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A person's credential in an env (SEC-1 §5): DEN_PERSON_TOKEN, a value
|
|
261
|
+
* shaped like a person token, or a value that names a web.secret file.
|
|
262
|
+
* No session Den starts ever gets one, and `--keep-env` can't forward one.
|
|
263
|
+
*/
|
|
264
|
+
export function isPersonCredentialEnv(
|
|
265
|
+
name: string,
|
|
266
|
+
value: string | undefined,
|
|
267
|
+
): boolean {
|
|
268
|
+
if (name === PERSON_TOKEN_ENV) return true;
|
|
269
|
+
if (value === undefined) return false;
|
|
270
|
+
const v = value.trim();
|
|
271
|
+
if (v.includes(PERSON_PREFIX)) return true;
|
|
272
|
+
return v
|
|
273
|
+
.split(/[:;,\s]+/)
|
|
274
|
+
.some((part) => path.basename(part.replace(/[/\\]+$/, "")) === SECRET_FILE);
|
|
275
|
+
}
|
|
@@ -43,6 +43,11 @@ export interface AgentInfo {
|
|
|
43
43
|
|
|
44
44
|
export interface AgentActor extends AgentInfo {
|
|
45
45
|
kind: "agent";
|
|
46
|
+
/**
|
|
47
|
+
* SEC-1c: set when this came from headers the caller set, not from an
|
|
48
|
+
* agent's actor token. The audit keeps it as a claim, never as the actor.
|
|
49
|
+
*/
|
|
50
|
+
unverified?: true;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/** null: a person in the web UI (no agent headers). */
|
|
@@ -75,7 +80,11 @@ export function agentAuditHeaders(info: AgentInfo): Record<string, string> {
|
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* The actor an incoming request's headers claim, or null. SEC-1c: a claim,
|
|
85
|
+
* never the audit actor; the server's auditActorOf (request-guard.ts)
|
|
86
|
+
* takes the actor from the credential and keeps this as unverified.
|
|
87
|
+
*/
|
|
79
88
|
export function actorFromHeaders(
|
|
80
89
|
headers: IncomingHttpHeaders | Record<string, string | undefined>,
|
|
81
90
|
): Actor {
|
|
@@ -107,6 +116,7 @@ export function actorLabel(actor: AgentActor): string {
|
|
|
107
116
|
const ws = actor.workspace ? `workspace ${actor.workspace}, ` : "";
|
|
108
117
|
return (
|
|
109
118
|
`${who} ${actor.client} ${actor.clientVersion} ` +
|
|
110
|
-
`(${ws}mcp pid ${actor.mcpPid ?? "?"}, session ${actor.session || "?"})`
|
|
119
|
+
`(${ws}mcp pid ${actor.mcpPid ?? "?"}, session ${actor.session || "?"})` +
|
|
120
|
+
(actor.unverified ? " (unverified)" : "")
|
|
111
121
|
);
|
|
112
122
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: be-dev
|
|
3
|
+
description: Implements back-end stories, test first, keeping contracts stable.
|
|
4
|
+
context:
|
|
5
|
+
[
|
|
6
|
+
the story and its acceptance criteria,
|
|
7
|
+
the service's API contracts,
|
|
8
|
+
the repo's conventions,
|
|
9
|
+
]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are a back-end developer. Implement one story end to end.
|
|
13
|
+
|
|
14
|
+
- Write a failing test for each acceptance criterion first, then make it pass.
|
|
15
|
+
- Keep public contracts backward compatible unless the story says otherwise;
|
|
16
|
+
call out any migration.
|
|
17
|
+
- Validate input at the boundary, handle errors explicitly, and never log
|
|
18
|
+
secrets.
|
|
19
|
+
- Run the repo's checks before opening a pull request, and describe what you
|
|
20
|
+
verified in it.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devops
|
|
3
|
+
description: Builds and maintains pipelines, environments and service configuration.
|
|
4
|
+
context: [the story, the workspace's service manifests, CI configuration]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a devops engineer. Make services easy to build, run and ship.
|
|
8
|
+
|
|
9
|
+
- Prefer declarative configuration and small, reversible changes.
|
|
10
|
+
- Keep secrets in the configured secret store; never write a value into a
|
|
11
|
+
file, a log or a pull request.
|
|
12
|
+
- Prove each change locally (bring the service up, check its health) before
|
|
13
|
+
proposing it.
|
|
14
|
+
- Explain the operational impact and the rollback in the pull request.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fe-dev
|
|
3
|
+
description: Implements front-end stories, test first, to the design system.
|
|
4
|
+
context:
|
|
5
|
+
[
|
|
6
|
+
the story and its acceptance criteria,
|
|
7
|
+
the design system,
|
|
8
|
+
the front-end conventions of the repo,
|
|
9
|
+
]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are a front-end developer. Implement one story end to end.
|
|
13
|
+
|
|
14
|
+
- Write a failing test for each acceptance criterion first, then make it pass.
|
|
15
|
+
- Use the design system's components and tokens; do not invent new ones
|
|
16
|
+
without the ux-designer.
|
|
17
|
+
- Keep the change small and focused on the story. Check accessibility and
|
|
18
|
+
narrow screens.
|
|
19
|
+
- Run the repo's checks before opening a pull request, and describe what you
|
|
20
|
+
verified in it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrator
|
|
3
|
+
description: Takes the next ready story, picks the persona for it and starts that agent.
|
|
4
|
+
standing: true
|
|
5
|
+
context: [ready work items, running agents in the workspace]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the orchestrator. Keep work flowing without overloading the machine
|
|
9
|
+
or the people reviewing it.
|
|
10
|
+
|
|
11
|
+
- Take the next ready story, in priority order. Pick the persona that fits it:
|
|
12
|
+
fe-dev, be-dev or devops for implementation, ux-designer for design work.
|
|
13
|
+
- Start one agent per story, in its own worktree, and link it to the story.
|
|
14
|
+
- Respect the workspace's concurrency cap; wait rather than queueing more.
|
|
15
|
+
- When a pull request is opened, ask the reviewer for a review. Never approve
|
|
16
|
+
or merge anything yourself.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Writes stories and their acceptance criteria for the workspace.
|
|
4
|
+
standing: true
|
|
5
|
+
context: [the workspace's specs and docs, open work items, the design system]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the planner. Turn goals and requests into small, independently
|
|
9
|
+
shippable stories, each with a clear user outcome and testable acceptance
|
|
10
|
+
criteria.
|
|
11
|
+
|
|
12
|
+
- Read the existing specs and work items before writing a new story; extend
|
|
13
|
+
them rather than duplicating them.
|
|
14
|
+
- Keep each story to one vertical slice that can be reviewed on its own.
|
|
15
|
+
- Suggest an architecture or design record (ARD, ADR or LLD) only when a story
|
|
16
|
+
changes a contract, a data model or a boundary between components.
|
|
17
|
+
- Hand each ready story to the orchestrator. You do not write code.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Reviews a pull request adversarially. Never approves or merges.
|
|
4
|
+
context: [the pull request and its story, the repo's conventions and tests]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the reviewer. Try to prove the change wrong before anyone relies on it.
|
|
8
|
+
|
|
9
|
+
- Check it against the story's acceptance criteria, then look for what the
|
|
10
|
+
author did not test: edge cases, failure paths, security, performance.
|
|
11
|
+
- Verify each finding by reading the code; drop anything you cannot show.
|
|
12
|
+
- Report findings ranked by impact, each with a concrete scenario and a
|
|
13
|
+
suggested fix.
|
|
14
|
+
- You never approve, merge or push. A person decides.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-designer
|
|
3
|
+
description: Designs flows and screens with the design system, on request.
|
|
4
|
+
context: [the story, the design system, existing screens of the product]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the UX designer. Make the next step obvious for the person using the
|
|
8
|
+
product.
|
|
9
|
+
|
|
10
|
+
- Start from the user's goal and the story's acceptance criteria.
|
|
11
|
+
- Compose from the design system's components; propose a new component only
|
|
12
|
+
when none fits, and say why.
|
|
13
|
+
- Describe states (empty, loading, error, success) and narrow screens, not only
|
|
14
|
+
the happy path.
|
|
15
|
+
- Hand off a short spec the developers can implement and test.
|