@twin-build-orchestrate/cli 1.0.0-prerelease.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/cli.js +5609 -0
- package/dist/cli.js.map +1 -0
- package/package.json +45 -0
- package/scripts/orchestrator-status-line.mjs +179 -0
- package/templates/CLAUDE.md +43 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@twin-build-orchestrate/cli",
|
|
3
|
+
"version": "1.0.0-prerelease.1",
|
|
4
|
+
"description": "twin-build framework CLI (init / sync / doctor) + local orchestrator (dispatcher + agent worker)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Kei Yoshimoto",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"twin-build-orchestrate": "dist/cli.js",
|
|
10
|
+
"twin-build": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"scripts",
|
|
15
|
+
"templates",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20.0.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"commander": "^14.0.3",
|
|
29
|
+
"node-diff3": "^3.1.2",
|
|
30
|
+
"pino": "^10.3.1",
|
|
31
|
+
"zod": "^3.24.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.5.0",
|
|
35
|
+
"typescript": "^5.7.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/YoshimotoKei/twin-build-server.git",
|
|
43
|
+
"directory": "packages/orchestrate"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code statusLine 用: 稼働中の dispatcher 一覧を 1 行で出力する。
|
|
3
|
+
//
|
|
4
|
+
// 設計選択 (Stage 5 Theme 1 後継):
|
|
5
|
+
// - cross-platform (Mac / Win / Linux 共通)。bash 不要。Node 20+ で動く。
|
|
6
|
+
// - cwd 直下の `.orchestrator.pid` (legacy) と `.orchestrator-<short>.pid`
|
|
7
|
+
// (Stage 5 Theme 2 A 案) を全部 scan。
|
|
8
|
+
// - 各 PID file の中身を kill(pid, 0) で alive 確認。
|
|
9
|
+
// - 出力例:
|
|
10
|
+
// 🟢 orchestrator: 21ffb8a1:11628
|
|
11
|
+
// 🟢 orchestrator: 21ffb8a1:11628 87654321:22001 ← 複数並列
|
|
12
|
+
// ⚪ orchestrator: idle (not running)
|
|
13
|
+
// ⚠️ orchestrator: 21ffb8a1:11628 stale-pidfile (default:99999)
|
|
14
|
+
//
|
|
15
|
+
// 色の使い分け:
|
|
16
|
+
// 🟢 = 正常稼働中
|
|
17
|
+
// ⚪ = 未起動 (エラーではない、意図的に止めている / まだ起動していない)
|
|
18
|
+
// ⚠️ = stale-pidfile (PID file は残ってるが process は死んでいる、要 cleanup)
|
|
19
|
+
//
|
|
20
|
+
// statusLine は Claude Code が prompt 表示前に毎回呼ぶため fast-path を維持
|
|
21
|
+
// (PID file scan + kill(pid, 0) のみで I/O は数ファイル分)。
|
|
22
|
+
|
|
23
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* テスト時に TWIN_BUILD_EVENTS_DIR で上書き可能なイベントディレクトリパスを返す。
|
|
29
|
+
* 本番環境では ~/.twin-build/events/ を使用する。
|
|
30
|
+
*/
|
|
31
|
+
function eventsDir() {
|
|
32
|
+
return process.env.TWIN_BUILD_EVENTS_DIR ?? join(homedir(), ".twin-build", "events");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** event type → 表示アイコンのマップ。未知 type は 📦 にフォールバック。 */
|
|
36
|
+
const EVENT_TYPE_ICONS = {
|
|
37
|
+
pr_blocked: "⚠️",
|
|
38
|
+
agent_halt: "⚠️",
|
|
39
|
+
pr_review_required: "🔍",
|
|
40
|
+
chain_completed: "✅",
|
|
41
|
+
chain_completed_no_pr: "✅",
|
|
42
|
+
pr_merged: "✅",
|
|
43
|
+
gate_decided: "✅",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* file-based completion event の未読 type 別アイコン+件数 (B 案、別 PR)。
|
|
48
|
+
* `~/.twin-build/events/` に dispatcher が emit した json file を type 別に集計。
|
|
49
|
+
*
|
|
50
|
+
* audience フィルタ (75de84bf): event payload の `audience` フィールドが `"ai"` の
|
|
51
|
+
* ものは statusLine から除外し、人間が ack すべきものだけ表示する。
|
|
52
|
+
* 後方互換: 古い event ファイル (audience フィールドなし) は "human" として扱う。
|
|
53
|
+
*
|
|
54
|
+
* 出力例: ` | 📬 ⚠️1 🔍1 ✅2`(重要度降順: ⚠️→🔍→✅→📦)
|
|
55
|
+
* 9 まで生数字、10+ は "9+" で省略。
|
|
56
|
+
*/
|
|
57
|
+
export function unreadEventsBadge() {
|
|
58
|
+
try {
|
|
59
|
+
const dir = eventsDir();
|
|
60
|
+
if (!existsSync(dir)) return "";
|
|
61
|
+
const files = readdirSync(dir).filter((n) => n.endsWith(".json"));
|
|
62
|
+
const counts = new Map();
|
|
63
|
+
for (const name of files) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = readFileSync(join(dir, name), "utf8");
|
|
66
|
+
const evt = JSON.parse(raw);
|
|
67
|
+
// audience が "ai" でなければ表示対象 (default = human、後方互換)。
|
|
68
|
+
if (evt.audience === "ai") continue;
|
|
69
|
+
const icon = EVENT_TYPE_ICONS[evt.type] ?? "📦";
|
|
70
|
+
counts.set(icon, (counts.get(icon) ?? 0) + 1);
|
|
71
|
+
} catch {
|
|
72
|
+
// パース失敗時は安全側 = 📦 グループにカウント。
|
|
73
|
+
counts.set("📦", (counts.get("📦") ?? 0) + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (counts.size === 0) return "";
|
|
77
|
+
const ORDER = ["⚠️", "🔍", "✅", "📦"];
|
|
78
|
+
const parts = ORDER.flatMap((icon) => {
|
|
79
|
+
const n = counts.get(icon) ?? 0;
|
|
80
|
+
return n > 0 ? [`${icon}${n > 9 ? "9+" : n}`] : [];
|
|
81
|
+
});
|
|
82
|
+
if (parts.length === 0) return "";
|
|
83
|
+
return ` | 📬 ${parts.join(" ")}`;
|
|
84
|
+
} catch {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Harness violation badge (型1/5/11 軽量 scanner の結果).
|
|
91
|
+
* Stop hook (`scripts/pattern-adoption-scan.ts`) が cwd 配下の changed .ts に対して
|
|
92
|
+
* scan した結果を `.claude/cache/pattern-adoption-summary.json` に書く。statusLine が
|
|
93
|
+
* それを読んで件数のみ表示する。block しないので Claude のコンテキストを汚染しない。
|
|
94
|
+
*
|
|
95
|
+
* **schemaVersion 2 (HarnessReport)**: bySeverity で 🔴/🟡/🟢 に分類して表示する。
|
|
96
|
+
* 旧 schema (count のみ) も後方互換で読み、count > 0 なら ⚠️ に fallback する。
|
|
97
|
+
*/
|
|
98
|
+
function patternAdoptionBadge() {
|
|
99
|
+
try {
|
|
100
|
+
const cacheFile = join(process.cwd(), ".claude", "cache", "pattern-adoption-summary.json");
|
|
101
|
+
if (!existsSync(cacheFile)) return "";
|
|
102
|
+
const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
103
|
+
const count = Number(data.count ?? 0);
|
|
104
|
+
if (!Number.isFinite(count) || count <= 0) return "";
|
|
105
|
+
const sev = data.bySeverity;
|
|
106
|
+
if (sev && typeof sev === "object") {
|
|
107
|
+
const err = Number(sev.error ?? 0);
|
|
108
|
+
const warn = Number(sev.warning ?? 0);
|
|
109
|
+
const info = Number(sev.info ?? 0);
|
|
110
|
+
const parts = [];
|
|
111
|
+
if (err > 0) parts.push(`🔴 ${err > 9 ? "9+" : err}`);
|
|
112
|
+
if (warn > 0) parts.push(`🟡 ${warn > 9 ? "9+" : warn}`);
|
|
113
|
+
if (info > 0) parts.push(`🟢 ${info > 9 ? "9+" : info}`);
|
|
114
|
+
if (parts.length > 0) return ` | 📬 harness: ${parts.join(" ")}`;
|
|
115
|
+
}
|
|
116
|
+
// schemaVersion 1 fallback (count only)
|
|
117
|
+
const display = count > 9 ? "9+" : String(count);
|
|
118
|
+
return ` | ⚠️ pattern: ${display}`;
|
|
119
|
+
} catch {
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function main() {
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = readdirSync(process.cwd());
|
|
128
|
+
} catch {
|
|
129
|
+
// cwd が読めない (異常) — 出力ナシで終わる (statusLine は空文字を許容)
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pidFiles = entries.filter(
|
|
134
|
+
(n) => n === ".orchestrator.pid" || /^\.orchestrator-[0-9a-f]+\.pid$/.test(n),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (pidFiles.length === 0) {
|
|
138
|
+
return "⚪ orchestrator: idle";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const alive = [];
|
|
142
|
+
const stale = [];
|
|
143
|
+
for (const file of pidFiles.sort()) {
|
|
144
|
+
const suffix = file === ".orchestrator.pid"
|
|
145
|
+
? "default"
|
|
146
|
+
: file.replace(/^\.orchestrator-([0-9a-f]+)\.pid$/, "$1");
|
|
147
|
+
let pid;
|
|
148
|
+
try {
|
|
149
|
+
pid = parseInt(readFileSync(file, "utf-8").trim(), 10);
|
|
150
|
+
} catch {
|
|
151
|
+
stale.push(`${suffix}:?`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
155
|
+
stale.push(`${suffix}:?`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
process.kill(pid, 0);
|
|
160
|
+
alive.push(`${suffix}:${pid}`);
|
|
161
|
+
} catch {
|
|
162
|
+
stale.push(`${suffix}:${pid}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (alive.length === 0 && stale.length > 0) {
|
|
167
|
+
return `⚠️ orchestrator: stale-pidfile (${stale.join(" ")})`;
|
|
168
|
+
}
|
|
169
|
+
let line = `🟢 orchestrator: ${alive.join(" ")}`;
|
|
170
|
+
if (stale.length > 0) {
|
|
171
|
+
line += ` ⚠️ stale (${stale.join(" ")})`;
|
|
172
|
+
}
|
|
173
|
+
return line;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const out = main();
|
|
177
|
+
const badge = unreadEventsBadge();
|
|
178
|
+
const patternBadge = patternAdoptionBadge();
|
|
179
|
+
if (out || badge || patternBadge) console.log(`${out}${badge}${patternBadge}`);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
> Project instructions for Claude Code. Edited locally, kept in sync with
|
|
4
|
+
> upstream `@twin-build-orchestrate/cli` via `npx twin-build sync`.
|
|
5
|
+
|
|
6
|
+
## Project Summary
|
|
7
|
+
|
|
8
|
+
<!-- Describe what this project does in 1-3 sentences. -->
|
|
9
|
+
|
|
10
|
+
## Repository Layout
|
|
11
|
+
|
|
12
|
+
<!-- Outline top-level directories and what lives where. -->
|
|
13
|
+
|
|
14
|
+
## Tech Stack
|
|
15
|
+
|
|
16
|
+
<!-- Languages, frameworks, infra worth flagging up front. -->
|
|
17
|
+
|
|
18
|
+
## Phase Workflow
|
|
19
|
+
|
|
20
|
+
This project follows the twin-build translation-chain process:
|
|
21
|
+
|
|
22
|
+
- Phase 0–4: Non-technical requirements and design.
|
|
23
|
+
- Phase 5–8: Technical design, implementation, verification, release.
|
|
24
|
+
|
|
25
|
+
Use `/phase{0..8}` skills from inside Claude Code to advance phases.
|
|
26
|
+
|
|
27
|
+
## Absolute Rules
|
|
28
|
+
|
|
29
|
+
1. Do not skip phases.
|
|
30
|
+
2. Each phase consumes only the prior phase's artefacts.
|
|
31
|
+
3. Decisions are made by humans; AI surfaces options.
|
|
32
|
+
4. Structural / design decisions must include reasoning.
|
|
33
|
+
5. Do not impose "best practices" over user intent.
|
|
34
|
+
|
|
35
|
+
## Local Conventions
|
|
36
|
+
|
|
37
|
+
<!-- Project-specific testing, branching, naming rules. -->
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
_This template ships with @twin-build-orchestrate/cli. Run
|
|
42
|
+
`npx twin-build sync` to pull upstream updates; conflicts are resolved
|
|
43
|
+
via 3-way merge._
|