@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.
- package/README.md +30 -10
- package/dist/agentops.js +5 -68
- package/dist/cli.js +4 -0
- package/dist/db.js +0 -26
- package/dist/doctor.js +2 -2
- package/dist/install-claude-plugin.js +78 -0
- package/dist/mcp-merge.js +18 -19
- package/dist/mirrorstore.js +1 -1
- package/dist/plugin/.claude-plugin/marketplace.json +13 -0
- package/dist/plugin/.claude-plugin/plugin.json +11 -0
- package/dist/plugin/config/mcp.codex.toml.example +33 -0
- package/dist/plugin/config/mcp.example.json +15 -0
- package/dist/plugin/config/mcp.opencode.json.example +16 -0
- package/dist/plugin/config/projects.example.json +82 -0
- package/dist/plugin/hooks/hooks.json +16 -0
- package/dist/plugin/references/codex-integration.md +282 -0
- package/dist/plugin/references/config-schema.md +358 -0
- package/dist/plugin/references/conventions.md +2159 -0
- package/dist/plugin/skills/architect-agent/SKILL.md +231 -0
- package/dist/plugin/skills/communication-agent/SKILL.md +247 -0
- package/dist/plugin/skills/dev-agent/SKILL.md +373 -0
- package/dist/plugin/skills/init/SKILL.md +496 -0
- package/dist/plugin/skills/junior-dev-agent/SKILL.md +348 -0
- package/dist/plugin/skills/ops-agent/SKILL.md +219 -0
- package/dist/plugin/skills/pm-agent/SKILL.md +427 -0
- package/dist/plugin/skills/qa-agent/SKILL.md +299 -0
- package/dist/plugin/skills/reflect-agent/SKILL.md +271 -0
- package/dist/plugin/skills/senior-dev-agent/SKILL.md +353 -0
- package/dist/plugin/skills/sweep-agent/SKILL.md +180 -0
- package/dist/run-agents.js +373 -0
- package/dist/seed.js +4 -3
- package/dist/server.js +1 -1
- package/dist/shim.js +3 -4
- package/dist/tooldefs.js +3 -25
- package/package.json +5 -5
- package/dist/topicstore.js +0 -174
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# dev-loop
|
|
2
2
|
|
|
3
3
|
The standalone local **coordination hub** for the [dev-loop](https://github.com/dyzsasd/dev-loop)
|
|
4
|
-
agents
|
|
5
|
-
**per-agent identity**, a localhost **web
|
|
6
|
-
and a
|
|
4
|
+
agents. It is a **zero-build, zero-native-dependency** MCP system of record over `node:sqlite`,
|
|
5
|
+
with **per-agent identity**, a localhost **web UI daemon**, an opt-in agent **op-API + thin stdio
|
|
6
|
+
shim**, and a transport that works across CLIs (Claude Code · Codex · opencode).
|
|
7
7
|
|
|
8
|
-
> One trusted host, localhost-only. Identity is **cooperative attribution
|
|
9
|
-
> live in env by **name** only. See the security envelope in
|
|
8
|
+
> One trusted host, localhost-only. Identity is **cooperative attribution**, not anti-spoofing.
|
|
9
|
+
> Secrets live in env by **name** only. See the security envelope in
|
|
10
10
|
> [`docs/HUB-ARCHITECTURE.md`](https://github.com/dyzsasd/dev-loop/blob/main/docs/HUB-ARCHITECTURE.md).
|
|
11
11
|
|
|
12
12
|
## Install
|
|
@@ -15,7 +15,14 @@ and a **CLI-portable** transport (Claude Code · Codex · opencode).
|
|
|
15
15
|
npm install -g @dyzsasd/dev-loop # requires Node >= 23.6 (built-in node:sqlite + .ts type-stripping; zero build); installs the `dev-loop` + `dev-loop-hub` bins
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
This
|
|
18
|
+
This installs two binaries on `PATH`: **`dev-loop`** for the CLI and **`dev-loop-hub`** for the
|
|
19
|
+
MCP server entrypoint. The package also ships the agent skills + shared references used by
|
|
20
|
+
`dev-loop run`, so the **scheduler mode needs no plugin** (it injects the skills + the hub MCP
|
|
21
|
+
itself). For Claude Code **interactive** slash commands, register the plugin from npm (no GitHub):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
dev-loop install-claude-plugin # writes a local npm-source marketplace, then prints the /plugin commands to run
|
|
25
|
+
```
|
|
19
26
|
|
|
20
27
|
## CLI
|
|
21
28
|
|
|
@@ -24,6 +31,8 @@ dev-loop serve run the stdio MCP server (the agent transpo
|
|
|
24
31
|
dev-loop shim the thin stdio MCP shim → the loopback daemon op-API
|
|
25
32
|
dev-loop daemon up|down|status per-project daemon lifecycle — idempotent, auto web UI
|
|
26
33
|
dev-loop init-service <key> <name> <PREFIX> turnkey-bootstrap a service-backend project
|
|
34
|
+
dev-loop run --cli claude|codex [--project <key>] [--agents core,outward] [--max-fires N] schedule agents (self-injects the hub MCP; no plugin)
|
|
35
|
+
dev-loop install-claude-plugin register a local npm-source marketplace so /plugin install loads it
|
|
27
36
|
dev-loop mcp-merge <args> merge dev-loop-hub into a product .mcp.json (never clobbers)
|
|
28
37
|
dev-loop seed <key> <name> [PREFIX] seed a project + actors + labels
|
|
29
38
|
dev-loop doctor health-check the system-of-record (DOCTOR_OK)
|
|
@@ -33,18 +42,29 @@ dev-loop version | help
|
|
|
33
42
|
|
|
34
43
|
## Identity & project (the env contract)
|
|
35
44
|
|
|
36
|
-
Every launcher sets
|
|
45
|
+
Every launcher sets the write identity **per pane**:
|
|
37
46
|
|
|
38
47
|
| Env var | Meaning |
|
|
39
48
|
|---|---|
|
|
40
|
-
| `DEVLOOP_ACTOR` | the per-agent identity (`pm`/`qa`/`dev
|
|
49
|
+
| `DEVLOOP_ACTOR` | the per-agent identity (`pm`/`qa`/`dev`/...) — the attribution |
|
|
41
50
|
| `DEVLOOP_PROJECT` | the pinned project key (or resolved from the cwd) |
|
|
42
51
|
| `DEVLOOP_HUB_DB` | the SQLite system-of-record (default `~/.dev-loop/hub.db`) |
|
|
43
52
|
|
|
44
|
-
Register it as an MCP server for your CLI
|
|
45
|
-
`["shim"]` for the daemon transport
|
|
53
|
+
Register it as an MCP server for your CLI with `{ "command": "dev-loop", "args": ["serve"] }`,
|
|
54
|
+
or use `["shim"]` for the daemon transport. Per-CLI recipes and the identity gate live in:
|
|
46
55
|
[`docs/PORTABILITY.md`](https://github.com/dyzsasd/dev-loop/blob/main/docs/PORTABILITY.md).
|
|
47
56
|
|
|
57
|
+
For an unattended loop without Claude/Codex `/loop`, run the built-in scheduler:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd /path/to/product-repo # project is inferred from repoPath / repos[].path
|
|
61
|
+
dev-loop run --cli claude --agents core,communication
|
|
62
|
+
dev-loop run --cli codex --agents core,outward
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It owns cadence itself and shells out to the selected CLI once per agent fire. Use
|
|
66
|
+
`--project <key>` only when launching from outside the repo or overriding cwd detection.
|
|
67
|
+
|
|
48
68
|
## Docs
|
|
49
69
|
|
|
50
70
|
- [Architecture + safety envelope](https://github.com/dyzsasd/dev-loop/blob/main/docs/HUB-ARCHITECTURE.md)
|
package/dist/agentops.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// since DL-69 (the dispatch-sharing refactor) — the stdio MCP server (server.ts), whose 27 op-backed tool
|
|
4
4
|
// handlers are now thin call-throughs to agentOp() (server.ts's toMcp() maps {status,body}→MCP ok()/err()).
|
|
5
5
|
// So each policy — the read SELECTs, the save_issue/save_comment orchestration (the DL-24 per-transition
|
|
6
|
-
// assignTo + the DL-32 prod-promotion gate + the REPLACE-labels/APPEND-relatedTo merge), and the doc/
|
|
7
|
-
// channel/mirror/label families (which also reuse the shared ticketwrite/docstore/
|
|
6
|
+
// assignTo + the DL-32 prod-promotion gate + the REPLACE-labels/APPEND-relatedTo merge), and the doc/
|
|
7
|
+
// channel/mirror/label families (which also reuse the shared ticketwrite/docstore/channelstore/
|
|
8
8
|
// mirrorstore/labelstore) — has EXACTLY ONE definition. The old "edit both files" drift tripwire is RETIRED:
|
|
9
9
|
// a change to any policy now lands in ONE place, and the differential-parity suite (test/shim.ts +
|
|
10
|
-
// test/agent-api.ts, shim ≡ stdio for all
|
|
10
|
+
// test/agent-api.ts, shim ≡ stdio for all 23 tools) is the structural guard against a future re-divergence.
|
|
11
11
|
//
|
|
12
12
|
// Each function takes a hub connection + the caller's already-resolved+validated actor (server.ts resolves it
|
|
13
13
|
// from DEVLOOP_ACTOR + the G1 phantom-actor guard; the daemon from the X-Devloop-Actor header) and returns an
|
|
@@ -28,15 +28,9 @@ import { insertTicket, updateTicketRow, insertComment, loadRelease } from "./tic
|
|
|
28
28
|
// The doc READS (doc.list/get/history/diff) + list_events are the SINGLE definition of those SELECTs — since
|
|
29
29
|
// DL-69 server.ts's handlers dispatch through them (no longer a 1:1 duplicate of a server.ts copy).
|
|
30
30
|
import { resolveDoc, latestVersion, docSave, docPublish, statusForDocErr, DOC_KINDS } from "./docstore.js";
|
|
31
|
-
// DL-64 discussion-board family — the topic/post reads + writes (incl. the §25 chair/invited role gates +
|
|
32
|
-
// the round/append rules) + the error→HTTP-status map are reused VERBATIM from the shared, side-effect-free
|
|
33
|
-
// topicstore (exactly as the doc family reuses docstore.ts), so the op-API and the stdio server.ts can never
|
|
34
|
-
// drift on a gate or a response shape. The op-API parses raw JSON, so each handler hand-validates the input
|
|
35
|
-
// shapes server.ts gets from zod (the DL-63 read-handler lesson — a non-string id/body must 400, never a 500).
|
|
36
|
-
import { topicList, topicGet, topicOpen, postAdd, topicSynthesize, topicClose, statusForTopicErr } from "./topicstore.js";
|
|
37
31
|
// DL-67 channel family — the channel register/send/poll/ack/status HANDLER logic + the DL-4 roadmap bridge are
|
|
38
|
-
// reused VERBATIM from the shared, side-effect-free channelstore (exactly as the doc
|
|
39
|
-
// docstore
|
|
32
|
+
// reused VERBATIM from the shared, side-effect-free channelstore (exactly as the doc family reuses
|
|
33
|
+
// docstore), so the op-API and the stdio server.ts can never drift. channel.send/poll are ASYNC
|
|
40
34
|
// (network/dryrun), so agentOp returns OpResult|Promise<OpResult> and the daemon awaits it. The op-API parses
|
|
41
35
|
// raw JSON → each handler hand-validates the shapes server.ts gets from zod (DL-63: a non-string arg → 400, never a 500).
|
|
42
36
|
import { channelRegister, channelSend, channelPoll, channelAck, channelStatus, statusForChannelErr } from "./channelstore.js";
|
|
@@ -53,7 +47,6 @@ export const AGENT_OPS = TOOL_NAMES.filter((n) => n !== "whoami");
|
|
|
53
47
|
// never mutate, so they bypass both). Kept here next to AGENT_OPS so the two lists can't drift. doc.save /
|
|
54
48
|
// doc.publish join the ticket writes; the doc/event reads stay read-only (parity with the read ticket ops).
|
|
55
49
|
export const AGENT_WRITE_OPS = new Set(["save_issue", "save_comment", "doc.save", "doc.publish",
|
|
56
|
-
"topic.open", "post.add", "topic.synthesize", "topic.close", // DL-64: the 4 board writes
|
|
57
50
|
"channel.register", "channel.send", "channel.poll", "channel.ack", // DL-67: the 4 channel writes (register/send/poll/ack mutate the channels/channel_messages tables); channel.status stays a read (query_only)
|
|
58
51
|
"mirror.push", "create_issue_label"]); // DL-68: the 2 writes (mirror.push → mirror_map + the one-way Linear network write; create_issue_label → labels). mirror.status/list_issue_labels/get_project stay reads (query_only)
|
|
59
52
|
export const isAgentOp = (s) => AGENT_OPS.includes(s);
|
|
@@ -354,56 +347,6 @@ function opDocPublish(db, projectId, actor, a) {
|
|
|
354
347
|
const r = docPublish(db, projectId, actor, a);
|
|
355
348
|
return r.ok ? okR(r.data) : errR(statusForDocErr(r.error), r.error);
|
|
356
349
|
}
|
|
357
|
-
// ─── DL-64: the discussion-board family (topic.*/post.add) — thin op-API wrappers over the shared topicstore ──
|
|
358
|
-
// Mirror the doc-family pattern: hand-validate the raw-JSON inputs to a clean 400 (server.ts gets these from
|
|
359
|
-
// zod), then delegate to topicstore (which owns the §25 role gates + round/append rules); a TopicResult error
|
|
360
|
-
// maps to its HTTP status via statusForTopicErr. The reads (topic.list/topic.get) take the query_only db; the
|
|
361
|
-
// writes (topic.open/post.add/topic.synthesize/topic.close ∈ AGENT_WRITE_OPS) take writeDb — the daemon routes.
|
|
362
|
-
function opTopicList(db, projectId, actor, a) {
|
|
363
|
-
if (a.status !== undefined && a.status !== "open" && a.status !== "closed")
|
|
364
|
-
return errR(400, `status must be "open" or "closed"`);
|
|
365
|
-
return okR(topicList(db, projectId, actor, a.status));
|
|
366
|
-
}
|
|
367
|
-
function opTopicGet(db, projectId, projectKey, a) {
|
|
368
|
-
if (typeof a.id !== "string")
|
|
369
|
-
return errR(400, "id must be a string");
|
|
370
|
-
const r = topicGet(db, projectId, projectKey, a.id);
|
|
371
|
-
return r.ok ? okR(r.data) : errR(statusForTopicErr(r.error), r.error);
|
|
372
|
-
}
|
|
373
|
-
function opTopicOpen(db, projectId, actor, a) {
|
|
374
|
-
if (typeof a.question !== "string" || !a.question)
|
|
375
|
-
return errR(400, "question required (a non-empty string)");
|
|
376
|
-
if (!isStrArr(a.invited) || a.invited.length === 0)
|
|
377
|
-
return errR(400, "invited required (a non-empty array of strings)");
|
|
378
|
-
const r = topicOpen(db, projectId, actor, a);
|
|
379
|
-
return r.ok ? okR(r.data) : errR(statusForTopicErr(r.error), r.error);
|
|
380
|
-
}
|
|
381
|
-
function opPostAdd(db, projectId, projectKey, actor, a) {
|
|
382
|
-
if (typeof a.topicId !== "string")
|
|
383
|
-
return errR(400, "topicId must be a string");
|
|
384
|
-
if (typeof a.body !== "string" || !a.body)
|
|
385
|
-
return errR(400, "body required (a non-empty string)");
|
|
386
|
-
const r = postAdd(db, projectId, projectKey, actor, a);
|
|
387
|
-
return r.ok ? okR(r.data) : errR(statusForTopicErr(r.error), r.error);
|
|
388
|
-
}
|
|
389
|
-
function opTopicSynthesize(db, projectId, projectKey, actor, a) {
|
|
390
|
-
if (typeof a.topicId !== "string")
|
|
391
|
-
return errR(400, "topicId must be a string");
|
|
392
|
-
if (typeof a.body !== "string" || !a.body)
|
|
393
|
-
return errR(400, "body required (a non-empty string)");
|
|
394
|
-
if (a.nextRound !== undefined && typeof a.nextRound !== "boolean")
|
|
395
|
-
return errR(400, "nextRound must be a boolean");
|
|
396
|
-
const r = topicSynthesize(db, projectId, projectKey, actor, a);
|
|
397
|
-
return r.ok ? okR(r.data) : errR(statusForTopicErr(r.error), r.error);
|
|
398
|
-
}
|
|
399
|
-
function opTopicClose(db, projectId, projectKey, actor, a) {
|
|
400
|
-
if (typeof a.topicId !== "string")
|
|
401
|
-
return errR(400, "topicId must be a string");
|
|
402
|
-
if (typeof a.decision !== "string" || !a.decision)
|
|
403
|
-
return errR(400, "decision required (a non-empty string)");
|
|
404
|
-
const r = topicClose(db, projectId, projectKey, actor, a);
|
|
405
|
-
return r.ok ? okR(r.data) : errR(statusForTopicErr(r.error), r.error);
|
|
406
|
-
}
|
|
407
350
|
// ─── DL-67: the IM channel family (channel.*) — thin op-API wrappers over the shared channelstore ──
|
|
408
351
|
// Mirror the doc/topic pattern: hand-validate the raw-JSON inputs to a clean 400 (server.ts gets these from
|
|
409
352
|
// zod — the DL-63 lesson), then delegate to channelstore (which owns the §16 line-build, the DL-4 roadmap
|
|
@@ -531,12 +474,6 @@ export function agentOp(op, db, projectId, projectKey, actor, args) {
|
|
|
531
474
|
case "doc.diff": return opDocDiff(db, projectId, args);
|
|
532
475
|
case "doc.save": return opDocSave(db, projectId, actor, args);
|
|
533
476
|
case "doc.publish": return opDocPublish(db, projectId, actor, args);
|
|
534
|
-
case "topic.list": return opTopicList(db, projectId, actor, args);
|
|
535
|
-
case "topic.get": return opTopicGet(db, projectId, projectKey, args);
|
|
536
|
-
case "topic.open": return opTopicOpen(db, projectId, actor, args);
|
|
537
|
-
case "post.add": return opPostAdd(db, projectId, projectKey, actor, args);
|
|
538
|
-
case "topic.synthesize": return opTopicSynthesize(db, projectId, projectKey, actor, args);
|
|
539
|
-
case "topic.close": return opTopicClose(db, projectId, projectKey, actor, args);
|
|
540
477
|
case "channel.register": return opChannelRegister(db, projectId, actor, args);
|
|
541
478
|
case "channel.send": return opChannelSend(db, projectId, projectKey, actor, args);
|
|
542
479
|
case "channel.poll": return opChannelPoll(db, projectId, projectKey, actor);
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,8 @@ const ROUTES = {
|
|
|
20
20
|
daemon: ["server", "daemon"], // up | down | status | ensure (DL-41)
|
|
21
21
|
doctor: ["server", "doctor"],
|
|
22
22
|
seed: ["seed"],
|
|
23
|
+
run: ["run-agents"], // scheduler: own cadence + shells out to claude/codex once per fire
|
|
24
|
+
"install-claude-plugin": ["install-claude-plugin"], // register a local npm-source marketplace so Claude Code loads the published plugin
|
|
23
25
|
"init-service": ["init-service"], // turnkey bootstrap (DL-60)
|
|
24
26
|
"mcp-merge": ["mcp-merge"], // merge into a product .mcp.json, never clobbers (DL-61)
|
|
25
27
|
"identity-check": ["server", "identity-check"], // the portability gate (PORTABILITY.md §4)
|
|
@@ -47,6 +49,8 @@ Usage: dev-loop <command> [args]
|
|
|
47
49
|
shim run the thin stdio MCP shim → the loopback daemon op-API (hub.transport:"daemon")
|
|
48
50
|
daemon up|down|status per-project daemon lifecycle — idempotent, auto-starts the localhost web UI
|
|
49
51
|
init-service <key> <name> <PREFIX> turnkey-bootstrap a service-backend project (seed → doctor → daemon up)
|
|
52
|
+
run --cli claude|codex [--project <key>] [--agents core,outward] schedule agents by calling the selected CLI
|
|
53
|
+
install-claude-plugin register a local npm-source marketplace so /plugin install can load it
|
|
50
54
|
mcp-merge <args> merge dev-loop-hub into a product .mcp.json (never clobbers other servers)
|
|
51
55
|
seed <key> <name> [PREFIX] seed a project + actors + labels into the hub db
|
|
52
56
|
doctor health-check the hub system-of-record (DOCTOR_OK)
|
package/dist/db.js
CHANGED
|
@@ -116,32 +116,6 @@ CREATE TABLE IF NOT EXISTS document_versions (
|
|
|
116
116
|
UNIQUE(doc_id, version)
|
|
117
117
|
);
|
|
118
118
|
CREATE INDEX IF NOT EXISTS idx_docversions_doc ON document_versions(doc_id, version);
|
|
119
|
-
-- ── P5 discussion board: the Director chairs; invited agents post per round ────
|
|
120
|
-
CREATE TABLE IF NOT EXISTS topics (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
123
|
-
question TEXT NOT NULL,
|
|
124
|
-
invited TEXT NOT NULL DEFAULT '[]', -- JSON array of actor handles
|
|
125
|
-
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','closed')),
|
|
126
|
-
round INTEGER NOT NULL DEFAULT 1,
|
|
127
|
-
round_opened_at TEXT NOT NULL, -- wall-clock for the state-free termination budget
|
|
128
|
-
opened_by TEXT NOT NULL, -- the chair (authority = opened_by)
|
|
129
|
-
opened_at TEXT NOT NULL,
|
|
130
|
-
closed_at TEXT,
|
|
131
|
-
decision TEXT -- inline terminal decision (set on close); DATA, never auto-applied (§17)
|
|
132
|
-
);
|
|
133
|
-
CREATE INDEX IF NOT EXISTS idx_topics_project_status ON topics(project_id, status);
|
|
134
|
-
CREATE TABLE IF NOT EXISTS posts (
|
|
135
|
-
id TEXT PRIMARY KEY,
|
|
136
|
-
topic_id TEXT NOT NULL REFERENCES topics(id),
|
|
137
|
-
round INTEGER NOT NULL,
|
|
138
|
-
author TEXT NOT NULL, -- actor HANDLE (attribution)
|
|
139
|
-
kind TEXT NOT NULL DEFAULT 'perspective' CHECK(kind IN ('perspective','synthesis')),
|
|
140
|
-
body TEXT NOT NULL,
|
|
141
|
-
created_at TEXT NOT NULL,
|
|
142
|
-
UNIQUE(topic_id, round, author, kind) -- one perspective per (round, author); chair's synthesis coexists
|
|
143
|
-
);
|
|
144
|
-
CREATE INDEX IF NOT EXISTS idx_posts_topic ON posts(topic_id, round, created_at);
|
|
145
119
|
-- ── P6 IM channel: per-project provider-agnostic two-way plane (§9/§16/§25) ───
|
|
146
120
|
-- §16 STRUCTURAL: this table holds the ENV-VAR NAME (config_ref/secret_ref) + the room id
|
|
147
121
|
-- (channel_ref), NEVER a token/secret/URL. The secret is read from process.env[config_ref]
|
package/dist/doctor.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function runDoctor(dbPath, opts = {}) {
|
|
|
41
41
|
// 2b. A 0-byte file IS a valid (empty) SQLite db, so the read-only open above SUCCEEDS on a
|
|
42
42
|
// truncated / zeroed / placeholder file — it just carries no schema; a non-SQLite file throws
|
|
43
43
|
// on the first read. Either way it is not a system-of-record: report INVALID and write nothing.
|
|
44
|
-
const HUB_TABLES = ["projects", "tickets", "documents", "
|
|
44
|
+
const HUB_TABLES = ["projects", "tickets", "documents", "actors", "events"]; // every table step 4 below counts — so a partial/foreign db fails HERE, cleanly, not mid-check
|
|
45
45
|
let missing;
|
|
46
46
|
try {
|
|
47
47
|
const present = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((r) => r.name));
|
|
@@ -67,7 +67,7 @@ export async function runDoctor(dbPath, opts = {}) {
|
|
|
67
67
|
Object.values(qc)[0] === "ok" ? pass("quick_check ok (no corruption)") : fail(`quick_check: ${JSON.stringify(qc)}`);
|
|
68
68
|
// 4. Counts + per-project, and the unique-prefix integrity check (the real multi-project guard)
|
|
69
69
|
const c = (sql) => db.prepare(sql).get().c;
|
|
70
|
-
info(`projects=${c("SELECT count(*) c FROM projects")} tickets=${c("SELECT count(*) c FROM tickets")} docs=${c("SELECT count(*) c FROM documents")}
|
|
70
|
+
info(`projects=${c("SELECT count(*) c FROM projects")} tickets=${c("SELECT count(*) c FROM tickets")} docs=${c("SELECT count(*) c FROM documents")} actors=${c("SELECT count(*) c FROM actors")} events=${c("SELECT count(*) c FROM events")}`);
|
|
71
71
|
const projects = db.prepare("SELECT id, key, ticket_prefix FROM projects ORDER BY key").all();
|
|
72
72
|
const countByProject = db.prepare("SELECT count(*) c FROM tickets WHERE project_id = ?");
|
|
73
73
|
for (const p of projects) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `dev-loop install-claude-plugin` — register a LOCAL marketplace whose single plugin has an `npm`
|
|
3
|
+
// source, so Claude Code installs the published @dyzsasd/dev-loop plugin from npm (no GitHub, no
|
|
4
|
+
// file-copy that drifts from the npm version). Claude Code marketplaces support an npm plugin source
|
|
5
|
+
// (docs: plugin-marketplaces). We write the tiny marketplace.json + print the two `/plugin` commands
|
|
6
|
+
// (those are interactive — this CLI can't run them).
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
const MARKETPLACE = "dev-loop-npm";
|
|
12
|
+
const PLUGIN = "dev-loop";
|
|
13
|
+
const defaultDest = () => join(homedir(), ".claude", "plugins", "marketplaces", MARKETPLACE);
|
|
14
|
+
function usage() {
|
|
15
|
+
console.log(`dev-loop install-claude-plugin — register a local npm-source marketplace for the Claude plugin
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
dev-loop install-claude-plugin [--dest <dir>] [--package <name>] [--version <semver>] [--dry-run]
|
|
19
|
+
|
|
20
|
+
Writes a marketplace.json whose plugin pulls from npm (default @dyzsasd/dev-loop), then prints the
|
|
21
|
+
two interactive /plugin commands to run. No GitHub, no file copy — the npm package is the single
|
|
22
|
+
source of truth for the plugin version.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--dest <dir> marketplace dir (default: ~/.claude/plugins/marketplaces/${MARKETPLACE})
|
|
26
|
+
--package <name> npm package (default: @dyzsasd/dev-loop)
|
|
27
|
+
--version <semver> pin a version (default: latest)
|
|
28
|
+
--dry-run print the marketplace.json + commands without writing`);
|
|
29
|
+
}
|
|
30
|
+
function die(msg, code = 2) {
|
|
31
|
+
console.error(`dev-loop install-claude-plugin: ${msg}`);
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
export function installClaudePlugin(argv = process.argv.slice(2)) {
|
|
35
|
+
const opts = { dest: defaultDest(), pkg: "@dyzsasd/dev-loop", version: "", dryRun: false };
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
const next = () => argv[++i] ?? die(`${a} requires a value`);
|
|
39
|
+
if (a === "--help" || a === "-h") {
|
|
40
|
+
usage();
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
else if (a === "--dest")
|
|
44
|
+
opts.dest = resolve(next());
|
|
45
|
+
else if (a === "--package")
|
|
46
|
+
opts.pkg = next();
|
|
47
|
+
else if (a === "--version")
|
|
48
|
+
opts.version = next();
|
|
49
|
+
else if (a === "--dry-run")
|
|
50
|
+
opts.dryRun = true;
|
|
51
|
+
else
|
|
52
|
+
die(`unknown option '${a}'`);
|
|
53
|
+
}
|
|
54
|
+
const source = { source: "npm", package: opts.pkg };
|
|
55
|
+
if (opts.version)
|
|
56
|
+
source.version = opts.version;
|
|
57
|
+
const marketplace = { name: MARKETPLACE, owner: { name: "Shuai" }, plugins: [{ name: PLUGIN, source }] };
|
|
58
|
+
const file = join(opts.dest, ".claude-plugin", "marketplace.json");
|
|
59
|
+
const json = JSON.stringify(marketplace, null, 2) + "\n";
|
|
60
|
+
if (opts.dryRun) {
|
|
61
|
+
console.log(`would write ${file}:\n${json}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
mkdirSync(join(opts.dest, ".claude-plugin"), { recursive: true });
|
|
65
|
+
writeFileSync(file, json);
|
|
66
|
+
console.log(`wrote ${file}`);
|
|
67
|
+
}
|
|
68
|
+
console.log(`\nNow run these two interactive Claude Code commands:`);
|
|
69
|
+
console.log(` /plugin marketplace add ${opts.dest}`);
|
|
70
|
+
console.log(` /plugin install ${PLUGIN}@${MARKETPLACE}`);
|
|
71
|
+
console.log(`\nThen /reload-plugins (or restart). Skills appear as /dev-loop:pm-agent … /dev-loop:init.`);
|
|
72
|
+
if (!opts.dryRun && !existsSync(file))
|
|
73
|
+
die(`failed to write ${file}`, 1);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
77
|
+
process.exit(installClaudePlugin());
|
|
78
|
+
}
|
package/dist/mcp-merge.js
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
// so init's `service` auto-wiring registers the hub server WITHOUT destroying any other MCP servers the
|
|
3
3
|
// product already declares. Composes onto DL-60's init-service seam (c). §16: env-NAME-only — the entry
|
|
4
4
|
// carries only `${VAR:-default}` env references (copied from the committed template), never a literal secret;
|
|
5
|
-
// the hub DB path is intentionally omitted (the server defaults to ~/.dev-loop/hub.db).
|
|
5
|
+
// the hub DB path is intentionally omitted (the server defaults to ~/.dev-loop/hub.db). The normal installed
|
|
6
|
+
// shape is `command:"dev-loop", args:["serve"]`; old source templates with a `server.ts` arg are still patched
|
|
7
|
+
// in place. §17: this is a
|
|
6
8
|
// data-file utility — it can only ever write the product `.mcp.json`, never a SKILL/conventions/code file.
|
|
7
9
|
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
8
10
|
import { join, dirname } from "node:path";
|
|
9
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
12
|
const SERVER_NAME = "dev-loop-hub";
|
|
11
|
-
// The published npm package
|
|
12
|
-
//
|
|
13
|
-
// canonical dev-loop-hub entry shape (env NAME-only; an args slot ending in server.ts that buildEntry rewrites
|
|
14
|
-
// to the real absolute path). Keep it in sync with config/mcp.example.json's dev-loop-hub entry.
|
|
13
|
+
// The published npm package may not have the repo's config/ beside dist, so this embedded default is the
|
|
14
|
+
// fallback. Keep it in sync with config/mcp.example.json's dev-loop-hub entry.
|
|
15
15
|
const DEFAULT_TEMPLATE = {
|
|
16
16
|
mcpServers: {
|
|
17
17
|
[SERVER_NAME]: {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
type: "stdio",
|
|
19
|
+
command: "dev-loop",
|
|
20
|
+
args: ["serve"],
|
|
20
21
|
env: { DEVLOOP_ACTOR: "${DEVLOOP_ACTOR:-operator}" },
|
|
21
22
|
},
|
|
22
23
|
},
|
|
@@ -36,8 +37,9 @@ function resolveTemplate(explicitPath, repoPath) {
|
|
|
36
37
|
return DEFAULT_TEMPLATE;
|
|
37
38
|
}
|
|
38
39
|
// Build the dev-loop-hub entry FROM the resolved template (the single source of truth for its shape — so a
|
|
39
|
-
// future template change propagates),
|
|
40
|
-
//
|
|
40
|
+
// future template change propagates), pinning the DEVLOOP_PROJECT default to the project key (matches the
|
|
41
|
+
// dogfood `.mcp.json` `${DEVLOOP_PROJECT:-<key>}`). Old templates with a `server.ts` arg are rewritten to the
|
|
42
|
+
// supplied source path for back-compat; current templates already use the PATH bin (`dev-loop serve`).
|
|
41
43
|
function buildEntry(tmpl, hubServerPath, projectKey) {
|
|
42
44
|
const src = tmpl.mcpServers?.[SERVER_NAME];
|
|
43
45
|
if (!src || typeof src !== "object")
|
|
@@ -47,19 +49,16 @@ function buildEntry(tmpl, hubServerPath, projectKey) {
|
|
|
47
49
|
// than write a malformed config. Real project keys are plain identifiers, so this never bites in practice.
|
|
48
50
|
if (/[${}]/.test(projectKey))
|
|
49
51
|
throw new Error(`project key ${JSON.stringify(projectKey)} contains '$', '{', or '}', which would break the .mcp.json \${VAR:-default} interpolation (DL-44) — use a plain identifier key`);
|
|
50
|
-
// DL-66: the hub server path lands verbatim in the entry's `args` (line below) — another interpolated
|
|
51
|
-
// .mcp.json string position — so a path carrying `$`/`{`/`}` would nest a `${...}` that Claude Code
|
|
52
|
-
// mis-expands at parse-time interpolation, corrupting the resolved hub path and breaking the launch in that
|
|
53
|
-
// pane. Guard it symmetrically with projectKey above; a real absolute checkout path never contains these
|
|
54
|
-
// (same defense-in-depth bar the team set for the projectKey side in DL-44).
|
|
55
|
-
if (/[${}]/.test(hubServerPath))
|
|
56
|
-
throw new Error(`hub server path ${JSON.stringify(hubServerPath)} contains '$', '{', or '}', which would nest a \${...} in the .mcp.json args that Claude Code mis-expands at parse-time interpolation, corrupting the resolved hub path (DL-66) — use a path without those characters`);
|
|
57
52
|
const e = structuredClone(src);
|
|
58
53
|
const args = (e.args ?? []);
|
|
59
54
|
const idx = args.findIndex((a) => typeof a === "string" && a.endsWith("server.ts"));
|
|
60
|
-
if (idx
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
if (idx >= 0) {
|
|
56
|
+
// DL-66: a legacy server path lands verbatim in an interpolated .mcp.json string position, so keep the
|
|
57
|
+
// old guard for old templates. Current `dev-loop serve` templates do not write this path at all.
|
|
58
|
+
if (/[${}]/.test(hubServerPath))
|
|
59
|
+
throw new Error(`hub server path ${JSON.stringify(hubServerPath)} contains '$', '{', or '}', which would nest a \${...} in the .mcp.json args that Claude Code mis-expands at parse-time interpolation, corrupting the resolved hub path (DL-66) — use a path without those characters`);
|
|
60
|
+
args[idx] = hubServerPath; // legacy placeholder replacement
|
|
61
|
+
}
|
|
63
62
|
e.args = args;
|
|
64
63
|
// env stays NAME-only; pin the project key as the DEVLOOP_PROJECT default (single-level, no nested ${...} — DL-44)
|
|
65
64
|
e.env = { ...(e.env ?? {}), DEVLOOP_PROJECT: `\${DEVLOOP_PROJECT:-${projectKey}}` };
|
package/dist/mirrorstore.js
CHANGED
|
@@ -28,7 +28,7 @@ import { isEnvName } from "./channelstore.js";
|
|
|
28
28
|
// mirror.push side-effect-free: it previews the would-push `ops`, hits NO network, and persists NO mirror_map
|
|
29
29
|
// row (DL-11). Set it in the spawned process env (the MIRROR_OK suite + the agent-api/shim npm scripts do).
|
|
30
30
|
const MIRROR_DRYRUN = process.env.DEVLOOP_MIRROR_DRYRUN === "1";
|
|
31
|
-
const MIRROR_BANNER = "> 🤖 Mirrored from the dev-loop hub — edits here are IGNORED and overwritten on the next push. Give direction
|
|
31
|
+
const MIRROR_BANNER = "> 🤖 Mirrored from the dev-loop hub — edits here are IGNORED and overwritten on the next push. Give direction by filing a Todo to PM (conventions §9a).";
|
|
32
32
|
const toTicket = (r) => ({
|
|
33
33
|
id: r.id, project_id: r.project_id, title: r.title, description: r.description, type: r.type,
|
|
34
34
|
state: r.state, assignee: r.assignee, priority: r.priority,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-loop",
|
|
3
|
+
"owner": { "name": "Shuai" },
|
|
4
|
+
"description": "Autonomous multi-agent SDLC loop with PM/QA/Dev, outward Ops/Architect/Communication roles, optional two-tier Dev, a Codex-portable service hub, and a built-in scheduler.",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "dev-loop",
|
|
8
|
+
"source": "./",
|
|
9
|
+
"version": "0.23.0",
|
|
10
|
+
"description": "Runs a software-development loop coordinated through ticket state. Supports Linear, a local file board, or a node:sqlite service hub with per-agent identity, localhost UI, Lark/Slack channel, one-way Linear mirror, Codex/opencode portability, and a scheduler that calls Claude/Codex CLI without using /loop. Includes Communication for daily public product article drafts."
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-loop",
|
|
3
|
+
"displayName": "dev-loop — autonomous SDLC agents",
|
|
4
|
+
"description": "A multi-agent software delivery loop coordinated through ticket state. PM, QA, Dev, Sweep, Reflect, Ops, Architect, and Communication cover product discovery, testing, implementation, hygiene, retrospectives, production watch, technical-health audits, and public article drafts. The optional senior/junior Dev split lets a stronger model design while a cheaper model implements. Coordination can run on Linear, a local file board, or the service hub with per-agent identity, a localhost web UI, documents, Lark/Slack channel, one-way Linear mirror, Codex/opencode portability, and a built-in scheduler that shells out to Claude or Codex without relying on /loop.",
|
|
5
|
+
"version": "0.23.0",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Shuai"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["linear", "agents", "pm", "qa", "dev", "automation", "sdlc", "workflow"],
|
|
10
|
+
"license": "MIT"
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# TEMPLATE for backend:"service" on the CODEX CLI (dev-loop P8 portability; see docs/PORTABILITY.md).
|
|
2
|
+
# Recommended install: `npm i -g @dyzsasd/dev-loop`, then merge this into ~/.codex/config.toml.
|
|
3
|
+
#
|
|
4
|
+
# CERTIFIED 2026-06-25 on codex-cli 0.142.0 (docs/PORTABILITY.md §4a). KEY FINDING: Codex spawns the MCP
|
|
5
|
+
# subprocess with ONLY this file's `env` block — it does NOT inherit the launching shell's process env.
|
|
6
|
+
# So DEVLOOP_ACTOR canNOT ride the process env on Codex (the gate returns "operator"). Per-pane identity
|
|
7
|
+
# rides a `-c` OVERRIDE instead, which MERGES a dotted key into the env table below:
|
|
8
|
+
# # one pane = one agent identity:
|
|
9
|
+
# codex exec -c 'mcp_servers.dev-loop-hub.env.DEVLOOP_ACTOR="dev"' \
|
|
10
|
+
# --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$PROMPT"
|
|
11
|
+
# → whoami returns {"actor":"dev","project":"dev-loop",…} (project/db preserved from the static env).
|
|
12
|
+
# # communication-agent pane:
|
|
13
|
+
# codex exec -c 'mcp_servers.dev-loop-hub.env.DEVLOOP_ACTOR="communication"' \
|
|
14
|
+
# --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$PROMPT"
|
|
15
|
+
# Leave DEVLOOP_ACTOR ABSENT below (the -c override supplies it per pane). VERIFY the [mcp_servers.<name>]
|
|
16
|
+
# schema against your Codex version (formats drift). Confirm with the §4 gate before trusting writes.
|
|
17
|
+
|
|
18
|
+
# OPT-IN daemon transport (DL-55, P2): to route the core ticket tools through the ONE running daemon
|
|
19
|
+
# instead of opening hub.db directly, change args to ["shim"] (from ["serve"]).
|
|
20
|
+
# REQUIRES the per-project daemon up (DL-42 auto-start or `dev-loop daemon up`) + settings_json.hub.transport=
|
|
21
|
+
# "daemon" (DL-43). The shim discovers the loopback port from the DL-41 runfile `~/.dev-loop/daemon-<key>.json`
|
|
22
|
+
# (override with DEVLOOP_HUB_PORT) — never hardcodes 8787. Scope this increment: the 5 core ticket tools +
|
|
23
|
+
# whoami; doc.*/topic.*/channel.* still need the default `serve` entry below.
|
|
24
|
+
[mcp_servers.dev-loop-hub]
|
|
25
|
+
command = "dev-loop"
|
|
26
|
+
args = ["serve"]
|
|
27
|
+
# STATIC, shared-across-panes vars ONLY. DEVLOOP_ACTOR is intentionally ABSENT — on Codex it does NOT ride
|
|
28
|
+
# the process env (CERTIFIED above: Codex doesn't inherit it); supply it per-pane via the `-c` override.
|
|
29
|
+
# Use an ABSOLUTE db path (TOML env is not shell-expanded).
|
|
30
|
+
# DEVLOOP_PROJECT = "" (literal empty — TOML is NOT shell-expanded, so never use `${DEVLOOP_PROJECT:-}`,
|
|
31
|
+
# which would be a literal string and fail the G2 phantom-project guard). Empty ⇒ the hub auto-resolves
|
|
32
|
+
# the project from the spawned process's cwd (DL-13); set a non-empty value here to pin one explicitly.
|
|
33
|
+
env = { DEVLOOP_PROJECT = "", DEVLOOP_HUB_DB = "/ABSOLUTE/PATH/.dev-loop/hub.db" }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "TEMPLATE for backend:\"service\" (conventions §18; docs/HUB-ARCHITECTURE.md). Copy to your PRODUCT repo root as `.mcp.json` (or pass via `claude --mcp-config`). Recommended install: `npm i -g @dyzsasd/dev-loop`, then use command:\"dev-loop\", args:[\"serve\"]. The ${VAR} values are expanded by Claude Code at config-parse time from each pane's launching shell, so a per-pane `DEVLOOP_ACTOR` attributes writes to the right agent. Keep these single-level (`${VAR:-literal}`) — do NOT nest `${...}` inside a default (DL-44). DEVLOOP_PROJECT defaults to EMPTY (`:-`): when unset the hub auto-resolves the project from the spawned process's cwd (DL-13). To pin one explicitly, export DEVLOOP_PROJECT. The hub DB path is intentionally NOT set here: the server defaults to `~/.dev-loop/hub.db`; to point at a non-default DB, export DEVLOOP_HUB_DB in the launching shell. Source-checkout fallback for plugin developers: command:\"node\", args:[\"/abs/path/to/dev-loop/hub/src/server.ts\"].",
|
|
3
|
+
"_shim": "OPT-IN daemon transport (DL-55): to route the core ticket tools through the ONE running daemon instead of each pane opening hub.db directly, use args:[\"shim\"] instead of [\"serve\"] (or point a source checkout at hub/src/shim.ts). REQUIRES the per-project daemon running and settings_json.hub.transport=\"daemon\" (DL-43). Scope: the 5 core ticket tools + whoami — doc.*/topic.*/channel.*/mirror.*/list_events still need serve.",
|
|
4
|
+
"mcpServers": {
|
|
5
|
+
"dev-loop-hub": {
|
|
6
|
+
"type": "stdio",
|
|
7
|
+
"command": "dev-loop",
|
|
8
|
+
"args": ["serve"],
|
|
9
|
+
"env": {
|
|
10
|
+
"DEVLOOP_ACTOR": "${DEVLOOP_ACTOR:-operator}",
|
|
11
|
+
"DEVLOOP_PROJECT": "${DEVLOOP_PROJECT:-}"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "TEMPLATE for backend:\"service\" on OPENCODE (dev-loop P8 portability; see docs/PORTABILITY.md). Recommended install: `npm i -g @dyzsasd/dev-loop`, then merge the `mcp` entry into your opencode config (e.g. ~/.config/opencode/config.json or a repo-local opencode.json). VERIFY against your installed opencode version: (1) the exact `mcp` server schema (this uses type:\"local\" + a command ARRAY + an `environment` map), and (2) whether opencode propagates the launching process env to the MCP subprocess. Per-pane identity is load-bearing: DEVLOOP_ACTOR differs per agent pane, so it must ride the launcher/env or an opencode-specific override. Confirm with the identity gate in docs/PORTABILITY.md.",
|
|
3
|
+
"mcp": {
|
|
4
|
+
"dev-loop-hub": {
|
|
5
|
+
"type": "local",
|
|
6
|
+
"command": ["dev-loop", "serve"],
|
|
7
|
+
"environment": {
|
|
8
|
+
"DEVLOOP_PROJECT": "",
|
|
9
|
+
"DEVLOOP_HUB_DB": "/ABSOLUTE/PATH/.dev-loop/hub.db"
|
|
10
|
+
},
|
|
11
|
+
"_DEVLOOP_PROJECT_note": "literal empty (NOT shell-expanded here) ⇒ the hub auto-resolves the project from the spawned process's cwd (DL-13); set a non-empty value to pin one explicitly, or omit the key.",
|
|
12
|
+
"_shim_note": "OPT-IN daemon transport (DL-55): to route the core ticket tools through the running daemon instead of opening hub.db directly, change command to [\"dev-loop\", \"shim\"]. REQUIRES the per-project daemon up + settings_json.hub.transport=\"daemon\" (DL-43). Scope: the 5 core ticket tools + whoami — doc.*/topic.*/channel.* still need serve.",
|
|
13
|
+
"enabled": true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultProject": "monpick",
|
|
3
|
+
"projects": {
|
|
4
|
+
"monpick": {
|
|
5
|
+
"linearTeam": "Citronetic",
|
|
6
|
+
"linearProject": "MonPick",
|
|
7
|
+
"repoPath": "/Users/shuai/workspace/citronetic/monpick",
|
|
8
|
+
"strategyDoc": "shopmy-analysis/15-parity-report.md",
|
|
9
|
+
"mode": "dry-run",
|
|
10
|
+
"autonomy": "ask",
|
|
11
|
+
"testEnv": {
|
|
12
|
+
"baseUrl": "https://monpick.vercel.app",
|
|
13
|
+
"setup": "python3 -m venv .venv && .venv/bin/pip install -q playwright && .venv/bin/playwright install chromium",
|
|
14
|
+
"testCommand": ".venv/bin/python3 tests/{suite}",
|
|
15
|
+
"notes": "Playwright Python suites in tests/. Run testEnv.setup once if the .venv/playwright harness is missing. Personas: demo-creator@monpick.dev / password123 (creator), demo-brand@monpick.dev / password123 (brand), demo-shopper@monpick.dev / password123 (shopper)."
|
|
16
|
+
},
|
|
17
|
+
"build": {
|
|
18
|
+
"typecheck": "npx tsc --noEmit",
|
|
19
|
+
"build": "pnpm build",
|
|
20
|
+
"test": "pnpm exec tsx tests/*.test.ts"
|
|
21
|
+
},
|
|
22
|
+
"git": {
|
|
23
|
+
"defaultBranch": "main",
|
|
24
|
+
"autoCommit": true,
|
|
25
|
+
"autoPush": true,
|
|
26
|
+
"autoDeploy": true
|
|
27
|
+
},
|
|
28
|
+
"deploy": {
|
|
29
|
+
"command": "vercel --prod --yes"
|
|
30
|
+
},
|
|
31
|
+
"codex": {
|
|
32
|
+
"_comment": "OPTIONAL Codex companion (conventions §24). Absent OR enabled:false OR codex CLI not on PATH ⇒ never invoked (100% unchanged). Needs the `codex` CLI (npm i -g @openai/codex; codex login) + the codex-plugin-cc plugin.",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"review": true,
|
|
35
|
+
"rescue": false,
|
|
36
|
+
"imageGen": true,
|
|
37
|
+
"assetsDir": "public/generated",
|
|
38
|
+
"model": null,
|
|
39
|
+
"effort": null
|
|
40
|
+
},
|
|
41
|
+
"communication": {
|
|
42
|
+
"_comment": "OPTIONAL communication-agent: drafts one public-facing product article per cadence. Draft-only: never publishes externally, never commits/pushes/deploys. Codex launch uses DEVLOOP_ACTOR=communication via the PORTABILITY.md identity recipe.",
|
|
43
|
+
"cadence": "daily",
|
|
44
|
+
"language": "en",
|
|
45
|
+
"audience": "current and prospective users",
|
|
46
|
+
"tone": "clear, concrete, human, and restrained",
|
|
47
|
+
"maxWords": 900,
|
|
48
|
+
"sourceWindowDays": 7,
|
|
49
|
+
"output": "data",
|
|
50
|
+
"outputDir": "communications",
|
|
51
|
+
"repoOutputDir": "docs/communications",
|
|
52
|
+
"includeUnreleased": false
|
|
53
|
+
},
|
|
54
|
+
"notify": {
|
|
55
|
+
"_comment": "OPTIONAL (conventions §9): ping the operator on a human-park (blocked+needs-pm+Bail-shape: external-prereq) via a Slack/Lark webhook. webhookEnv names an env var holding the webhook URL (a §16 secret — NEVER inline a real URL in a committed file). Absent => no-op. type: slack | lark.",
|
|
56
|
+
"type": "lark",
|
|
57
|
+
"webhookEnv": "DEVLOOP_NOTIFY_WEBHOOK",
|
|
58
|
+
"events": ["human-parked"]
|
|
59
|
+
},
|
|
60
|
+
"blockedStateName": null
|
|
61
|
+
},
|
|
62
|
+
"acme-suite": {
|
|
63
|
+
"_comment": "MULTI-REPO EXAMPLE (conventions §19). monpick above stays single-repo via top-level repoPath/build/git — back-compat. Here repos[] replaces a single repoPath; each ticket targets a repo via a repo:<name> label. role docs/primary picks the doc-home repo for strategyDoc; lang is informational; build/defaultBranch/deploy/contributorSkill resolve per-repo else top-level; autoCommit/autoPush/autoDeploy stay product-level in git.",
|
|
64
|
+
"linearTeam": "Acme",
|
|
65
|
+
"linearProject": "Acme Suite",
|
|
66
|
+
"repos": [
|
|
67
|
+
{ "name": "web", "path": "/abs/path/to/acme-web", "role": "primary", "lang": "ts", "defaultBranch": "main", "deploy": { "command": "vercel --prod --yes", "healthCheck": "https://acme.example.com" } },
|
|
68
|
+
{ "name": "api", "path": "/abs/path/to/acme-api", "role": "docs", "lang": "go", "defaultBranch": "main", "build": { "typecheck": "go build ./...", "test": "go test ./..." }, "deploy": { "command": "flyctl deploy", "healthCheck": "https://api.acme.example.com/healthz" } }
|
|
69
|
+
],
|
|
70
|
+
"strategyDoc": "docs/strategy.md",
|
|
71
|
+
"mode": "dry-run",
|
|
72
|
+
"autonomy": "ask",
|
|
73
|
+
"testEnv": {
|
|
74
|
+
"baseUrl": "https://acme.example.com",
|
|
75
|
+
"notes": "One product baseUrl (conventions §19 limit: the api repo has no URL of its own). Personas in a vault — ask user."
|
|
76
|
+
},
|
|
77
|
+
"build": { "typecheck": "pnpm -r typecheck", "build": "pnpm -r build" },
|
|
78
|
+
"git": { "defaultBranch": "main", "autoCommit": true, "autoPush": true, "autoDeploy": false },
|
|
79
|
+
"blockedStateName": null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "dev-loop turnkey auto-start (DL-42, §17 operator-applied [pm-proposal]). On session start, ensure the per-project hub daemon + web UI is up via DL-41's idempotent `daemon up`. Safe-by-construction: `up` is cwd-resolved and a clean no-op for a non-service / Linear / local project (prints 'no project resolved … nothing to start', exit 0), so a project not using the hub is byte-for-byte unaffected. localhost-only (§16); output is swallowed and the exit forced 0 so a SessionStart never pollutes context or aborts startup.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hub/src/server.ts\" daemon up >/dev/null 2>&1 || true",
|
|
10
|
+
"timeout": 15
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|