@dyzsasd/dev-loop 0.22.0 → 0.23.0

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.
Files changed (36) hide show
  1. package/README.md +30 -10
  2. package/dist/agentops.js +5 -68
  3. package/dist/cli.js +4 -0
  4. package/dist/db.js +0 -26
  5. package/dist/doctor.js +2 -2
  6. package/dist/install-claude-plugin.js +78 -0
  7. package/dist/mcp-merge.js +18 -19
  8. package/dist/mirrorstore.js +1 -1
  9. package/dist/plugin/.claude-plugin/marketplace.json +13 -0
  10. package/dist/plugin/.claude-plugin/plugin.json +11 -0
  11. package/dist/plugin/config/mcp.codex.toml.example +33 -0
  12. package/dist/plugin/config/mcp.example.json +15 -0
  13. package/dist/plugin/config/mcp.opencode.json.example +16 -0
  14. package/dist/plugin/config/projects.example.json +82 -0
  15. package/dist/plugin/hooks/hooks.json +16 -0
  16. package/dist/plugin/references/codex-integration.md +282 -0
  17. package/dist/plugin/references/config-schema.md +358 -0
  18. package/dist/plugin/references/conventions.md +2159 -0
  19. package/dist/plugin/skills/architect-agent/SKILL.md +231 -0
  20. package/dist/plugin/skills/communication-agent/SKILL.md +247 -0
  21. package/dist/plugin/skills/dev-agent/SKILL.md +373 -0
  22. package/dist/plugin/skills/init/SKILL.md +496 -0
  23. package/dist/plugin/skills/junior-dev-agent/SKILL.md +348 -0
  24. package/dist/plugin/skills/ops-agent/SKILL.md +219 -0
  25. package/dist/plugin/skills/pm-agent/SKILL.md +427 -0
  26. package/dist/plugin/skills/qa-agent/SKILL.md +299 -0
  27. package/dist/plugin/skills/reflect-agent/SKILL.md +271 -0
  28. package/dist/plugin/skills/senior-dev-agent/SKILL.md +353 -0
  29. package/dist/plugin/skills/sweep-agent/SKILL.md +180 -0
  30. package/dist/run-agents.js +373 -0
  31. package/dist/seed.js +4 -3
  32. package/dist/server.js +1 -1
  33. package/dist/shim.js +3 -4
  34. package/dist/tooldefs.js +3 -25
  35. package/package.json +5 -5
  36. package/dist/topicstore.js +0 -174
@@ -1,174 +0,0 @@
1
- // Shared discussion-board (P5/§25) store — the topic/post read+write logic + the two cooperative role
2
- // gates, used by BOTH the MCP server (server.ts) and the read+write daemon op-API (agentops.ts, DL-64).
3
- // SIDE-EFFECT-FREE (no env read, no transport, no top-level db) so either entrypoint can import it; identity
4
- // (actor) and scope (projectId/projectKey) are passed in by the caller — exactly the docstore.ts precedent
5
- // that lets the stdio server and the daemon op-API share ONE implementation and never drift.
6
- //
7
- // §17 firewall (structural): every write here is an INSERT/UPDATE on the `topics` / `posts` tables (a CHECKed
8
- // `kind` enum {perspective,synthesis}, db.ts) — there is NO filesystem path anywhere in this module, so a
9
- // board write can never target a SKILL / conventions / code file. A discussion DECISION (topic.close) is DATA,
10
- // never an auto-applied change. The two role gates live here ONCE so the two callers can't diverge on them:
11
- // • chair-gate = actor === topic.opened_by (only the chair synthesizes/closes)
12
- // • invited-gate = actor ∈ topic.invited (your-lane: post only AS yourself, once per round)
13
- // Both are cooperative single-host attribution (§18 / HUB-ARCHITECTURE §16), not anti-spoof.
14
- import { randomUUID } from "node:crypto";
15
- import { nowIso, logEvent, actorExists, listActorHandles } from "./db.js";
16
- // Map a topicstore error message (prose, not codes) to an HTTP status, mirroring statusForDocErr: the role
17
- // gate → 403, a missing topic → 404, a state/dup conflict (closed / already posted / already synthesized /
18
- // stale) → 409, else a create-precondition (an unknown invited handle) → 400.
19
- export const statusForTopicErr = (msg) => msg.startsWith("FORBIDDEN") ? 403
20
- : /^no topic\b/.test(msg) ? 404
21
- : (msg.startsWith("CONFLICT") || /^already /.test(msg)) ? 409
22
- : 400;
23
- const getTopic = (db, projectId, id) => db.prepare("SELECT * FROM topics WHERE id=? AND project_id=?").get(id, projectId);
24
- const pendingFor = (db, t) => {
25
- const invited = JSON.parse(t.invited);
26
- const answered = new Set(db.prepare("SELECT author FROM posts WHERE topic_id=? AND round=? AND kind='perspective'").all(t.id, t.round)
27
- .map((r) => r.author));
28
- return invited.filter((h) => !answered.has(h));
29
- };
30
- // ── reads (shaped HERE so server.ts and the op-API return byte-identical bodies — the parity tripwire) ──
31
- // topic.list row: …invited, pending, youArePending (per-actor). topic.get adds posts and omits youArePending.
32
- export function topicList(db, projectId, actor, status) {
33
- const rows = (status
34
- ? db.prepare("SELECT * FROM topics WHERE project_id=? AND status=? ORDER BY opened_at DESC").all(projectId, status)
35
- : db.prepare("SELECT * FROM topics WHERE project_id=? ORDER BY opened_at DESC").all(projectId));
36
- return rows.map((t) => {
37
- const pending = t.status === "open" ? pendingFor(db, t) : [];
38
- return {
39
- id: t.id, question: t.question, status: t.status, round: t.round, round_opened_at: t.round_opened_at,
40
- opened_by: t.opened_by, opened_at: t.opened_at, closed_at: t.closed_at, decision: t.decision,
41
- invited: JSON.parse(t.invited), pending, youArePending: pending.includes(actor),
42
- };
43
- });
44
- }
45
- export function topicGet(db, projectId, projectKey, id) {
46
- const t = getTopic(db, projectId, id);
47
- if (!t)
48
- return { ok: false, error: `no topic ${id} in ${projectKey}` };
49
- const posts = db.prepare("SELECT round,author,kind,body,created_at FROM posts WHERE topic_id=? ORDER BY round, created_at").all(id);
50
- return { ok: true, data: {
51
- id: t.id, question: t.question, status: t.status, round: t.round, round_opened_at: t.round_opened_at,
52
- opened_by: t.opened_by, opened_at: t.opened_at, closed_at: t.closed_at, decision: t.decision,
53
- invited: JSON.parse(t.invited), pending: t.status === "open" ? pendingFor(db, t) : [], posts,
54
- } };
55
- }
56
- export function topicOpen(db, projectId, actor, a) {
57
- const bad = a.invited.filter((h) => !actorExists(db, h));
58
- if (bad.length)
59
- return { ok: false, error: `unknown invited actor(s): ${bad.join(", ")} — valid: ${listActorHandles(db).join(", ")}` };
60
- const id = randomUUID();
61
- const t = nowIso();
62
- db.prepare("INSERT INTO topics(id,project_id,question,invited,status,round,round_opened_at,opened_by,opened_at) VALUES (?,?,?,?,'open',1,?,?,?)")
63
- .run(id, projectId, a.question, JSON.stringify([...new Set(a.invited)]), t, actor, t);
64
- logEvent(db, { project_id: projectId, actor, kind: "topic.open", data: { id, invited: a.invited } });
65
- return { ok: true, data: { id, question: a.question, invited: [...new Set(a.invited)], status: "open", round: 1, opened_by: actor } };
66
- }
67
- export function postAdd(db, projectId, projectKey, actor, a) {
68
- const ts = nowIso();
69
- db.exec("BEGIN IMMEDIATE"); // read round+status then insert atomically vs a concurrent synthesize round-bump (§7)
70
- try {
71
- const t = db.prepare("SELECT * FROM topics WHERE id=? AND project_id=?").get(a.topicId, projectId);
72
- if (!t) {
73
- db.exec("ROLLBACK");
74
- return { ok: false, error: `no topic ${a.topicId} in ${projectKey}` };
75
- }
76
- if (t.status !== "open") {
77
- db.exec("ROLLBACK");
78
- return { ok: false, error: `CONFLICT: topic ${a.topicId} is closed` };
79
- }
80
- if (!JSON.parse(t.invited).includes(actor)) {
81
- db.exec("ROLLBACK");
82
- return { ok: false, error: `FORBIDDEN: '${actor}' is not invited to topic ${a.topicId}` };
83
- }
84
- const dup = db.prepare("SELECT 1 FROM posts WHERE topic_id=? AND round=? AND author=? AND kind='perspective'").get(a.topicId, t.round, actor);
85
- if (dup) {
86
- db.exec("ROLLBACK");
87
- return { ok: false, error: `already posted in round ${t.round} — append-only, one perspective per round` };
88
- }
89
- db.prepare("INSERT INTO posts(id,topic_id,round,author,kind,body,created_at) VALUES (?,?,?,?,'perspective',?,?)")
90
- .run(randomUUID(), a.topicId, t.round, actor, a.body, ts);
91
- logEvent(db, { project_id: projectId, actor, kind: "post.add", data: { topicId: a.topicId, round: t.round } });
92
- db.exec("COMMIT");
93
- return { ok: true, data: { topicId: a.topicId, round: t.round, author: actor, kind: "perspective", created_at: ts } };
94
- }
95
- catch (e) {
96
- try {
97
- db.exec("ROLLBACK");
98
- }
99
- catch { /* */ }
100
- throw e;
101
- }
102
- }
103
- export function topicSynthesize(db, projectId, projectKey, actor, a) {
104
- const ts = nowIso();
105
- db.exec("BEGIN IMMEDIATE");
106
- try {
107
- const t = db.prepare("SELECT * FROM topics WHERE id=? AND project_id=?").get(a.topicId, projectId);
108
- if (!t) {
109
- db.exec("ROLLBACK");
110
- return { ok: false, error: `no topic ${a.topicId} in ${projectKey}` };
111
- }
112
- if (t.status !== "open") {
113
- db.exec("ROLLBACK");
114
- return { ok: false, error: `CONFLICT: topic ${a.topicId} is closed` };
115
- }
116
- if (t.opened_by !== actor) {
117
- db.exec("ROLLBACK");
118
- return { ok: false, error: `FORBIDDEN: only the chair '${t.opened_by}' may synthesize topic ${a.topicId}` };
119
- }
120
- // pre-check the once-per-round synthesis (Codex review): a clean CONFLICT, not a raw UNIQUE error
121
- const dupSyn = db.prepare("SELECT 1 FROM posts WHERE topic_id=? AND round=? AND author=? AND kind='synthesis'").get(a.topicId, t.round, actor);
122
- if (dupSyn) {
123
- db.exec("ROLLBACK");
124
- return { ok: false, error: `CONFLICT: already synthesized round ${t.round} — bump with nextRound:true or close` };
125
- }
126
- db.prepare("INSERT INTO posts(id,topic_id,round,author,kind,body,created_at) VALUES (?,?,?,?,'synthesis',?,?)")
127
- .run(randomUUID(), a.topicId, t.round, actor, a.body, ts);
128
- let round = t.round;
129
- if (a.nextRound) {
130
- round = t.round + 1;
131
- db.prepare("UPDATE topics SET round=?, round_opened_at=? WHERE id=?").run(round, ts, t.id);
132
- }
133
- logEvent(db, { project_id: projectId, actor, kind: "topic.synthesize", data: { topicId: a.topicId, round: t.round, nextRound: !!a.nextRound } });
134
- db.exec("COMMIT");
135
- return { ok: true, data: { topicId: a.topicId, synthesizedRound: t.round, round, status: "open" } };
136
- }
137
- catch (e) {
138
- try {
139
- db.exec("ROLLBACK");
140
- }
141
- catch { /* */ }
142
- throw e;
143
- }
144
- }
145
- export function topicClose(db, projectId, projectKey, actor, a) {
146
- const ts = nowIso();
147
- db.exec("BEGIN IMMEDIATE");
148
- try {
149
- const t = db.prepare("SELECT * FROM topics WHERE id=? AND project_id=?").get(a.topicId, projectId);
150
- if (!t) {
151
- db.exec("ROLLBACK");
152
- return { ok: false, error: `no topic ${a.topicId} in ${projectKey}` };
153
- }
154
- if (t.status !== "open") {
155
- db.exec("ROLLBACK");
156
- return { ok: false, error: `CONFLICT: topic ${a.topicId} is already closed` };
157
- }
158
- if (t.opened_by !== actor) {
159
- db.exec("ROLLBACK");
160
- return { ok: false, error: `FORBIDDEN: only the chair '${t.opened_by}' may close topic ${a.topicId}` };
161
- }
162
- db.prepare("UPDATE topics SET status='closed', decision=?, closed_at=? WHERE id=?").run(a.decision, ts, t.id);
163
- logEvent(db, { project_id: projectId, actor, kind: "topic.close", data: { topicId: a.topicId, round: t.round } });
164
- db.exec("COMMIT");
165
- return { ok: true, data: { topicId: a.topicId, status: "closed", decision: a.decision, closed_at: ts } };
166
- }
167
- catch (e) {
168
- try {
169
- db.exec("ROLLBACK");
170
- }
171
- catch { /* */ }
172
- throw e;
173
- }
174
- }