@dawipong/opcflow 0.1.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/LICENSE +21 -0
- package/README.en.md +253 -0
- package/README.md +239 -0
- package/dist/cli.mjs +4550 -0
- package/package.json +81 -0
- package/preset/.editorconfig +16 -0
- package/preset/.gitattributes +15 -0
- package/preset/.prettierignore +7 -0
- package/preset/.prettierrc +8 -0
- package/preset/package.json +8 -0
- package/templates/agents/en/architect.md +65 -0
- package/templates/agents/en/designer.md +56 -0
- package/templates/agents/en/developer.md +60 -0
- package/templates/agents/en/product-manager.md +58 -0
- package/templates/agents/en/qa.md +62 -0
- package/templates/agents/zh/architect.md +65 -0
- package/templates/agents/zh/designer.md +56 -0
- package/templates/agents/zh/developer.md +60 -0
- package/templates/agents/zh/product-manager.md +58 -0
- package/templates/agents/zh/qa.md +62 -0
- package/web/dist/assets/abnfDiagram-VRR7QNED-CM_SmMdS.js +1 -0
- package/web/dist/assets/arc-a2d6VGyS.js +1 -0
- package/web/dist/assets/architectureDiagram-ZJ3FMSHR-CKwso5u1.js +36 -0
- package/web/dist/assets/blockDiagram-677ZJIJ3-DOlRPtCE.js +132 -0
- package/web/dist/assets/c4Diagram-LMCZKHZV-BVWP4jKU.js +10 -0
- package/web/dist/assets/channel-Bv2v5Ka7.js +1 -0
- package/web/dist/assets/chunk-2Q5K7J3B-Dg1gXqD_.js +1 -0
- package/web/dist/assets/chunk-32BRIVSS-DMEBIyv0.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-BfNutGrI.js +15 -0
- package/web/dist/assets/chunk-EX3LRPZG-B0LUaNef.js +231 -0
- package/web/dist/assets/chunk-JWPE2WC7-RWpAfnwW.js +1 -0
- package/web/dist/assets/chunk-MOJQB5TN-CHzjH2SN.js +88 -0
- package/web/dist/assets/chunk-RYQCIY6F-CB84H40n.js +1 -0
- package/web/dist/assets/chunk-V7JOEXUC-hDfVeloC.js +206 -0
- package/web/dist/assets/chunk-VR4S4FIN-C-ISU9mB.js +1 -0
- package/web/dist/assets/chunk-XXDRQBXY-Q-1uA8_v.js +1 -0
- package/web/dist/assets/classDiagram-OUVF2IWQ-C5GMtsqN.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-C5GMtsqN.js +1 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-D4aRsqbg.js +1 -0
- package/web/dist/assets/cynefin-VYW2F7L2-Bb_blZYP.js +166 -0
- package/web/dist/assets/cynefinDiagram-TSTJHNR4-CRX9tSwx.js +62 -0
- package/web/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
- package/web/dist/assets/dagre-VKFMJZFB-BHcw1uwS.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-FQU43EPY-BbFIcSGy.js +3 -0
- package/web/dist/assets/diagram-G47NLZAW-D6SEnl9Y.js +24 -0
- package/web/dist/assets/diagram-NH7WQ7WH-Dq4ObETB.js +24 -0
- package/web/dist/assets/diagram-OA4YK3LP-EF9zUKb4.js +30 -0
- package/web/dist/assets/diagram-WEI45ONY-9MSuI1lJ.js +41 -0
- package/web/dist/assets/ebnfDiagram-CCIWWBDH-BKTvNs-v.js +1 -0
- package/web/dist/assets/erDiagram-Q63AITRT-BRZ7eOih.js +85 -0
- package/web/dist/assets/flowDiagram-23GEKE2U-n6RjlBIU.js +156 -0
- package/web/dist/assets/ganttDiagram-NO4QXBWP-DJ8LFx2q.js +292 -0
- package/web/dist/assets/gitGraphDiagram-IHSO6WYX-PBNVnu__.js +106 -0
- package/web/dist/assets/graph-DOmOIIwC.js +1 -0
- package/web/dist/assets/index-BkpgTe8Q.js +674 -0
- package/web/dist/assets/index-CfSIxCYt.css +1 -0
- package/web/dist/assets/infoDiagram-FWYZ7A6U-DpF1ykim.js +2 -0
- package/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/web/dist/assets/ishikawaDiagram-FXEZZL3T-BcLaz8vn.js +70 -0
- package/web/dist/assets/journeyDiagram-5HDEW3XC-DhDa2tr6.js +139 -0
- package/web/dist/assets/kanban-definition-HUTT4EX6-Cj5MBccm.js +89 -0
- package/web/dist/assets/katex-HP8lGamR.js +257 -0
- package/web/dist/assets/layout-D-LzfAck.js +1 -0
- package/web/dist/assets/linear-D-RAQuF7.js +1 -0
- package/web/dist/assets/map-DxJ2ADlA.js +1 -0
- package/web/dist/assets/mindmap-definition-LN4V7U3C-CDHapI3O.js +96 -0
- package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web/dist/assets/pegDiagram-2B236MQR-Cga1GKF6.js +1 -0
- package/web/dist/assets/pieDiagram-ENE6RG2P-BtzEbqHl.js +39 -0
- package/web/dist/assets/quadrantDiagram-ABIIQ3AL-CMHXJPd5.js +7 -0
- package/web/dist/assets/railroadDiagram-RFXS5EU6-C5dKOd7a.js +1 -0
- package/web/dist/assets/requirementDiagram-TGXJPOKE-Cjx6LPCa.js +84 -0
- package/web/dist/assets/sankeyDiagram-HTMAVEWB-C8kzAycX.js +40 -0
- package/web/dist/assets/sequenceDiagram-DBY2YBRQ-DwEDppgS.js +162 -0
- package/web/dist/assets/sizeCapture-X5ZJPWSS-CUgsJ-ry.js +1 -0
- package/web/dist/assets/stateDiagram-2N3HPSRC-B4fcb7Qa.js +1 -0
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-IQXX7rVH.js +1 -0
- package/web/dist/assets/swimlanes-5IMT3BWC-Bw27O3PP.js +2 -0
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DRko3G58.js +8 -0
- package/web/dist/assets/timeline-definition-FHXFAJF6-Cra4Bj9n.js +120 -0
- package/web/dist/assets/vennDiagram-L72KCM5P-B_Nmy9aH.js +34 -0
- package/web/dist/assets/wardleyDiagram-EHGQE667-BE1omAzb.js +78 -0
- package/web/dist/assets/xychartDiagram-FW5EYKEG-CsNs1p2c.js +7 -0
- package/web/dist/index.html +22 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,4550 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// core/config.ts
|
|
18
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, join, resolve } from "node:path";
|
|
20
|
+
function findProjectRoot(from = process.cwd()) {
|
|
21
|
+
let dir = resolve(from);
|
|
22
|
+
while (true) {
|
|
23
|
+
if (existsSync(join(dir, CONFIG_FILENAME))) return dir;
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir) return resolve(from);
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function loadConfig(root) {
|
|
30
|
+
const file = join(root, CONFIG_FILENAME);
|
|
31
|
+
if (!existsSync(file)) return { ...DEFAULTS };
|
|
32
|
+
const raw = JSON.parse(
|
|
33
|
+
readFileSync(file, "utf-8")
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
...DEFAULTS,
|
|
37
|
+
...raw,
|
|
38
|
+
docs: { ...DEFAULTS.docs, ...raw.docs },
|
|
39
|
+
gates: { ...DEFAULTS.gates, ...raw.gates },
|
|
40
|
+
machineChecks: { ...DEFAULTS.machineChecks, ...raw.machineChecks },
|
|
41
|
+
roleProduces: { ...DEFAULTS.roleProduces, ...raw.roleProduces },
|
|
42
|
+
pipeline: raw.pipeline ?? DEFAULTS.pipeline,
|
|
43
|
+
git: { ...DEFAULTS.git, ...raw.git },
|
|
44
|
+
platforms: raw.platforms ?? DEFAULTS.platforms
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
var CONFIG_FILENAME, PKG_NAME, WORKBENCH_BIN, WORKBENCH_DIR, DEFAULTS;
|
|
48
|
+
var init_config = __esm({
|
|
49
|
+
"core/config.ts"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
CONFIG_FILENAME = "workbench.config.json";
|
|
52
|
+
PKG_NAME = "@dawipong/opcflow";
|
|
53
|
+
WORKBENCH_BIN = `npx -y ${PKG_NAME}`;
|
|
54
|
+
WORKBENCH_DIR = resolve(join(import.meta.dirname, ".."));
|
|
55
|
+
DEFAULTS = {
|
|
56
|
+
endpoints: ["service", "admin", "weapp", "app"],
|
|
57
|
+
docs: {
|
|
58
|
+
prd: "docs/prd",
|
|
59
|
+
architecture: "docs/architecture",
|
|
60
|
+
design: "docs/design",
|
|
61
|
+
acceptance: "docs/acceptance"
|
|
62
|
+
},
|
|
63
|
+
codeRoots: {},
|
|
64
|
+
machineChecks: { enabled: false },
|
|
65
|
+
protocolLints: [],
|
|
66
|
+
moduleMapping: {},
|
|
67
|
+
feedbackHalfLifeDays: 15,
|
|
68
|
+
gates: { approvalMode: "warn", writeGate: "observe" },
|
|
69
|
+
git: { taskTrailer: "off", trailerKey: "Task" },
|
|
70
|
+
legacyDb: "tasks/task.db",
|
|
71
|
+
dataDir: ".workbench",
|
|
72
|
+
cli: WORKBENCH_BIN,
|
|
73
|
+
pipeline: ["product-manager", "architect", "designer", "developer", "qa"],
|
|
74
|
+
roleProduces: {
|
|
75
|
+
"product-manager": [
|
|
76
|
+
"project",
|
|
77
|
+
"roles",
|
|
78
|
+
"glossary",
|
|
79
|
+
"flow",
|
|
80
|
+
"module-prd",
|
|
81
|
+
"page-prd"
|
|
82
|
+
],
|
|
83
|
+
architect: ["db-doc", "api-doc"],
|
|
84
|
+
designer: ["design-system", "design-prompt", "prototype"],
|
|
85
|
+
developer: ["code"],
|
|
86
|
+
qa: ["acceptance"]
|
|
87
|
+
},
|
|
88
|
+
platforms: ["claude"],
|
|
89
|
+
language: "zh"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// core/db.ts
|
|
95
|
+
var db_exports = {};
|
|
96
|
+
__export(db_exports, {
|
|
97
|
+
CURRENT_SCHEMA_VERSION: () => CURRENT_SCHEMA_VERSION,
|
|
98
|
+
MIGRATIONS: () => MIGRATIONS,
|
|
99
|
+
SCHEMA_V1: () => SCHEMA_V1,
|
|
100
|
+
ensureSchema: () => ensureSchema,
|
|
101
|
+
openWorkbench: () => openWorkbench,
|
|
102
|
+
openWorkbenchAt: () => openWorkbenchAt
|
|
103
|
+
});
|
|
104
|
+
import Database from "better-sqlite3";
|
|
105
|
+
import { mkdirSync } from "node:fs";
|
|
106
|
+
import { join as join2 } from "node:path";
|
|
107
|
+
function ensureSchema(db) {
|
|
108
|
+
db.exec(
|
|
109
|
+
"CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY, applied_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
|
|
110
|
+
);
|
|
111
|
+
let current = db.prepare("SELECT MAX(version) AS v FROM schema_version").get().v ?? 0;
|
|
112
|
+
if (current === 0) {
|
|
113
|
+
const hasArtifacts = db.prepare("SELECT COUNT(*) AS c FROM sqlite_master WHERE type = 'table' AND name = 'artifacts'").get();
|
|
114
|
+
if (hasArtifacts.c > 0) {
|
|
115
|
+
const cols = db.prepare("PRAGMA table_info(artifacts)").all().map((c) => c.name);
|
|
116
|
+
if (!cols.includes("submitted_hash")) {
|
|
117
|
+
db.exec("ALTER TABLE artifacts ADD COLUMN submitted_hash TEXT");
|
|
118
|
+
}
|
|
119
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (1)").run();
|
|
120
|
+
current = 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const m of MIGRATIONS) {
|
|
124
|
+
if (m.version <= current) continue;
|
|
125
|
+
const tx = db.transaction(() => {
|
|
126
|
+
m.up(db);
|
|
127
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(m.version);
|
|
128
|
+
});
|
|
129
|
+
tx();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function openDb(root, config) {
|
|
133
|
+
const dataDir = join2(root, config.dataDir);
|
|
134
|
+
mkdirSync(dataDir, { recursive: true });
|
|
135
|
+
const db = new Database(join2(dataDir, "workbench.db"));
|
|
136
|
+
db.pragma("journal_mode = WAL");
|
|
137
|
+
db.pragma("foreign_keys = ON");
|
|
138
|
+
db.pragma("busy_timeout = 5000");
|
|
139
|
+
ensureSchema(db);
|
|
140
|
+
return db;
|
|
141
|
+
}
|
|
142
|
+
function openWorkbench(rootArg) {
|
|
143
|
+
const root = findProjectRoot(rootArg);
|
|
144
|
+
const config = loadConfig(root);
|
|
145
|
+
return { root, config, db: openDb(root, config) };
|
|
146
|
+
}
|
|
147
|
+
function openWorkbenchAt(root) {
|
|
148
|
+
const config = loadConfig(root);
|
|
149
|
+
return { root, config, db: openDb(root, config) };
|
|
150
|
+
}
|
|
151
|
+
var SCHEMA_V1, MIGRATION_2, MIGRATIONS, CURRENT_SCHEMA_VERSION;
|
|
152
|
+
var init_db = __esm({
|
|
153
|
+
"core/db.ts"() {
|
|
154
|
+
"use strict";
|
|
155
|
+
init_config();
|
|
156
|
+
SCHEMA_V1 = `
|
|
157
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
158
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
159
|
+
kind TEXT NOT NULL,
|
|
160
|
+
module TEXT,
|
|
161
|
+
endpoint TEXT,
|
|
162
|
+
page TEXT,
|
|
163
|
+
path TEXT NOT NULL UNIQUE,
|
|
164
|
+
content_hash TEXT NOT NULL,
|
|
165
|
+
approved_hash TEXT,
|
|
166
|
+
reviewed_by TEXT,
|
|
167
|
+
reviewed_at DATETIME,
|
|
168
|
+
submitted_at DATETIME,
|
|
169
|
+
submitted_hash TEXT,
|
|
170
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
171
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
CREATE TABLE IF NOT EXISTS artifact_edges (
|
|
175
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
176
|
+
from_id INTEGER NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
|
|
177
|
+
to_id INTEGER NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
|
|
178
|
+
UNIQUE(from_id, to_id)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
182
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
183
|
+
module TEXT,
|
|
184
|
+
role TEXT NOT NULL,
|
|
185
|
+
endpoint TEXT NOT NULL,
|
|
186
|
+
page TEXT,
|
|
187
|
+
type TEXT NOT NULL DEFAULT 'build',
|
|
188
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
189
|
+
assignee TEXT,
|
|
190
|
+
creator TEXT NOT NULL,
|
|
191
|
+
content TEXT,
|
|
192
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
193
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
194
|
+
CHECK (role IN ('product-manager', 'architect', 'designer', 'developer', 'qa')),
|
|
195
|
+
CHECK (type IN ('build', 'review', 'qa', 'hotfix', 'baseline', 'legacy')),
|
|
196
|
+
CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled'))
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
CREATE TABLE IF NOT EXISTS task_inputs (
|
|
200
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
201
|
+
artifact_id INTEGER NOT NULL REFERENCES artifacts(id),
|
|
202
|
+
input_hash TEXT NOT NULL,
|
|
203
|
+
UNIQUE(task_id, artifact_id)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
CREATE TABLE IF NOT EXISTS task_outputs (
|
|
207
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
208
|
+
artifact_id INTEGER NOT NULL REFERENCES artifacts(id),
|
|
209
|
+
UNIQUE(task_id, artifact_id)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
213
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
214
|
+
entity_type TEXT NOT NULL,
|
|
215
|
+
entity_id INTEGER NOT NULL,
|
|
216
|
+
event TEXT NOT NULL,
|
|
217
|
+
actor TEXT NOT NULL,
|
|
218
|
+
payload TEXT,
|
|
219
|
+
module TEXT,
|
|
220
|
+
endpoint TEXT,
|
|
221
|
+
page TEXT,
|
|
222
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
CREATE TABLE IF NOT EXISTS artifact_feedback (
|
|
226
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
227
|
+
artifact_id INTEGER NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
|
|
228
|
+
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
|
|
229
|
+
verdict INTEGER NOT NULL CHECK (verdict IN (1, -1)),
|
|
230
|
+
comment TEXT,
|
|
231
|
+
content_hash TEXT NOT NULL,
|
|
232
|
+
actor TEXT NOT NULL,
|
|
233
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_coord ON artifacts(module, endpoint, page);
|
|
237
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_kind ON artifacts(kind);
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_coord ON tasks(module, role, endpoint, status);
|
|
239
|
+
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_type, entity_id);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_artifact ON artifact_feedback(artifact_id);
|
|
241
|
+
`;
|
|
242
|
+
MIGRATION_2 = `
|
|
243
|
+
CREATE TABLE tasks_new (
|
|
244
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
245
|
+
module TEXT,
|
|
246
|
+
role TEXT NOT NULL,
|
|
247
|
+
endpoint TEXT,
|
|
248
|
+
page TEXT,
|
|
249
|
+
type TEXT NOT NULL DEFAULT 'build',
|
|
250
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
251
|
+
assignee TEXT,
|
|
252
|
+
creator TEXT NOT NULL,
|
|
253
|
+
content TEXT,
|
|
254
|
+
external_ref TEXT,
|
|
255
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
256
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
257
|
+
CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled'))
|
|
258
|
+
);
|
|
259
|
+
INSERT INTO tasks_new (id, module, role, endpoint, page, type, status, assignee, creator, content, external_ref, created_at, updated_at)
|
|
260
|
+
SELECT id, module, role, endpoint, page, type, status, assignee, creator, content, NULL, created_at, updated_at FROM tasks;
|
|
261
|
+
DROP TABLE tasks;
|
|
262
|
+
ALTER TABLE tasks_new RENAME TO tasks;
|
|
263
|
+
CREATE INDEX idx_tasks_coord ON tasks(module, role, endpoint, status);
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_events_module ON events(module);
|
|
265
|
+
CREATE INDEX IF NOT EXISTS idx_events_event ON events(event);
|
|
266
|
+
`;
|
|
267
|
+
MIGRATIONS = [
|
|
268
|
+
{ version: 1, up: (db) => db.exec(SCHEMA_V1) },
|
|
269
|
+
{
|
|
270
|
+
version: 2,
|
|
271
|
+
up: (db) => {
|
|
272
|
+
db.pragma("foreign_keys = OFF");
|
|
273
|
+
db.exec(MIGRATION_2);
|
|
274
|
+
db.pragma("foreign_keys = ON");
|
|
275
|
+
const violations = db.pragma("foreign_key_check");
|
|
276
|
+
if (violations.length > 0) {
|
|
277
|
+
throw new Error(`\u8FC1\u79FB 2 \u540E\u5916\u952E\u6821\u9A8C\u5931\u8D25: ${JSON.stringify(violations)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
// 迁移 3:claim 时记录 git HEAD,供 hotfix 契约触碰检测与 trailer 交叉验证
|
|
282
|
+
{ version: 3, up: (db) => db.exec("ALTER TABLE tasks ADD COLUMN claim_commit TEXT") }
|
|
283
|
+
];
|
|
284
|
+
CURRENT_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// core/types.ts
|
|
289
|
+
var init_types = __esm({
|
|
290
|
+
"core/types.ts"() {
|
|
291
|
+
"use strict";
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// core/hash.ts
|
|
296
|
+
import { createHash } from "node:crypto";
|
|
297
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
|
|
298
|
+
import { join as join3 } from "node:path";
|
|
299
|
+
function sha1(input) {
|
|
300
|
+
return createHash("sha1").update(input).digest("hex");
|
|
301
|
+
}
|
|
302
|
+
function isBinary(buf) {
|
|
303
|
+
const len = Math.min(buf.length, 8192);
|
|
304
|
+
for (let i = 0; i < len; i++) {
|
|
305
|
+
if (buf[i] === 0) return true;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
function hashFile(absPath) {
|
|
310
|
+
const buf = readFileSync2(absPath);
|
|
311
|
+
if (isBinary(buf)) return sha1(buf);
|
|
312
|
+
return sha1(buf.toString("utf-8").replace(/\r\n?/g, "\n"));
|
|
313
|
+
}
|
|
314
|
+
function hashDirectory(absDir) {
|
|
315
|
+
const entries = [];
|
|
316
|
+
const walk = (dir, rel) => {
|
|
317
|
+
for (const name of readdirSync(dir).sort()) {
|
|
318
|
+
if (IGNORE_DIRS.has(name) || IGNORE_FILES.has(name)) continue;
|
|
319
|
+
const full = join3(dir, name);
|
|
320
|
+
const relPath = rel ? `${rel}/${name}` : name;
|
|
321
|
+
const st = statSync(full);
|
|
322
|
+
if (st.isDirectory()) {
|
|
323
|
+
walk(full, relPath);
|
|
324
|
+
} else {
|
|
325
|
+
entries.push(`${relPath}:${hashFile(full)}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
walk(absDir, "");
|
|
330
|
+
return sha1(entries.join("\n"));
|
|
331
|
+
}
|
|
332
|
+
function hashPath(absPath) {
|
|
333
|
+
if (!existsSync2(absPath)) return null;
|
|
334
|
+
return statSync(absPath).isDirectory() ? hashDirectory(absPath) : hashFile(absPath);
|
|
335
|
+
}
|
|
336
|
+
var IGNORE_DIRS, IGNORE_FILES;
|
|
337
|
+
var init_hash = __esm({
|
|
338
|
+
"core/hash.ts"() {
|
|
339
|
+
"use strict";
|
|
340
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".workbench"]);
|
|
341
|
+
IGNORE_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", ".DS_Store", "Thumbs.db"]);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// core/events.ts
|
|
346
|
+
var events_exports = {};
|
|
347
|
+
__export(events_exports, {
|
|
348
|
+
listEvents: () => listEvents,
|
|
349
|
+
logEvent: () => logEvent
|
|
350
|
+
});
|
|
351
|
+
function logEvent(db, e) {
|
|
352
|
+
const result = db.prepare(
|
|
353
|
+
`INSERT INTO events (entity_type, entity_id, event, actor, payload, module, endpoint, page, created_at)
|
|
354
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, COALESCE(?, CURRENT_TIMESTAMP))`
|
|
355
|
+
).run(
|
|
356
|
+
e.entityType,
|
|
357
|
+
e.entityId,
|
|
358
|
+
e.event,
|
|
359
|
+
e.actor,
|
|
360
|
+
e.payload === void 0 ? null : JSON.stringify(e.payload),
|
|
361
|
+
e.module ?? null,
|
|
362
|
+
e.endpoint ?? null,
|
|
363
|
+
e.page ?? null,
|
|
364
|
+
e.createdAt ?? null
|
|
365
|
+
);
|
|
366
|
+
return result.lastInsertRowid;
|
|
367
|
+
}
|
|
368
|
+
function listEvents(db, f = {}) {
|
|
369
|
+
let query = "SELECT * FROM events WHERE 1=1";
|
|
370
|
+
const params = [];
|
|
371
|
+
if (f.entityType) {
|
|
372
|
+
query += " AND entity_type = ?";
|
|
373
|
+
params.push(f.entityType);
|
|
374
|
+
}
|
|
375
|
+
if (f.entityId !== void 0) {
|
|
376
|
+
query += " AND entity_id = ?";
|
|
377
|
+
params.push(f.entityId);
|
|
378
|
+
}
|
|
379
|
+
if (f.module) {
|
|
380
|
+
query += " AND module = ?";
|
|
381
|
+
params.push(f.module);
|
|
382
|
+
}
|
|
383
|
+
if (f.event) {
|
|
384
|
+
query += " AND event = ?";
|
|
385
|
+
params.push(f.event);
|
|
386
|
+
}
|
|
387
|
+
if (f.afterId !== void 0) {
|
|
388
|
+
query += " AND id > ?";
|
|
389
|
+
params.push(f.afterId);
|
|
390
|
+
}
|
|
391
|
+
query += " ORDER BY id " + (f.afterId !== void 0 ? "ASC" : "DESC");
|
|
392
|
+
if (f.limit) {
|
|
393
|
+
query += " LIMIT ?";
|
|
394
|
+
params.push(f.limit);
|
|
395
|
+
}
|
|
396
|
+
return db.prepare(query).all(...params);
|
|
397
|
+
}
|
|
398
|
+
var init_events = __esm({
|
|
399
|
+
"core/events.ts"() {
|
|
400
|
+
"use strict";
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// core/platforms.ts
|
|
405
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
406
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
407
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
408
|
+
function claudeMemory(role, lang) {
|
|
409
|
+
return lang === "en" ? `# Persistent Agent Memory
|
|
410
|
+
|
|
411
|
+
With \`memory: project\`, your cross-session memory lives in \`.claude/agent-memory/${role}/\`, indexed by \`MEMORY.md\`. Write directly (no mkdir); verify a memory file exists before relying on it.` : `# \u6301\u4E45\u8BB0\u5FC6(Agent Memory)
|
|
412
|
+
|
|
413
|
+
\u542F\u7528 \`memory: project\` \u540E,\u4F60\u7684\u8DE8\u4F1A\u8BDD\u8BB0\u5FC6\u5728 \`.claude/agent-memory/${role}/\`,\u4EE5 \`MEMORY.md\` \u4E3A\u7D22\u5F15\u3002\u76F4\u63A5 Write(\u52FF mkdir);\u547D\u540D\u5177\u4F53\u6587\u4EF6\u7684\u8BB0\u5FC6\u4F7F\u7528\u524D\u5148\u9A8C\u8BC1\u5B58\u5728\u3002`;
|
|
414
|
+
}
|
|
415
|
+
function agentsMdMemory(name, lang) {
|
|
416
|
+
return lang === "en" ? `# Cross-session lessons (AGENTS.md)
|
|
417
|
+
|
|
418
|
+
${name} has no per-agent memory directory \u2014 the durable layer is \`AGENTS.md\`. Persist the cross-session lessons below by appending them to its relevant section (append only, never overwrite existing instructions).` : `# \u8DE8\u4F1A\u8BDD\u7ECF\u9A8C(\u5199\u5165 AGENTS.md)
|
|
419
|
+
|
|
420
|
+
${name} \u6CA1\u6709 per-agent \u8BB0\u5FC6\u76EE\u5F55,\u6301\u4E45\u5C42\u662F\u9879\u76EE\u6839 \`AGENTS.md\`\u3002\u628A\u4E0B\u9762\u8FD9\u7C7B\u8DE8\u4F1A\u8BDD\u7ECF\u9A8C\u8FFD\u52A0\u8FDB\u5B83\u7684\u5BF9\u5E94\u5C0F\u8282(\u53EA\u8FFD\u52A0,\u52FF\u8986\u76D6\u65E2\u6709\u6307\u4EE4)\u3002`;
|
|
421
|
+
}
|
|
422
|
+
function cursorMemory(lang) {
|
|
423
|
+
return lang === "en" ? `# Cross-session lessons (Cursor Memories / rules)
|
|
424
|
+
|
|
425
|
+
Cursor has no per-agent memory. Persist the cross-session lessons below via Cursor's native Memories (auto-generated & auto-recalled) or a committable rule under \`.cursor/rules/\`.` : `# \u8DE8\u4F1A\u8BDD\u7ECF\u9A8C(Cursor Memories / rules)
|
|
426
|
+
|
|
427
|
+
Cursor \u65E0 per-agent \u8BB0\u5FC6\u3002\u628A\u4E0B\u9762\u8FD9\u7C7B\u8DE8\u4F1A\u8BDD\u7ECF\u9A8C\u4EA4\u7ED9 Cursor \u539F\u751F Memories(\u81EA\u52A8\u751F\u6210\u3001\u81EA\u52A8\u53EC\u56DE),\u6216\u5199\u8FDB\u53EF\u63D0\u4EA4\u7684 \`.cursor/rules/\` \u89C4\u5219\u6587\u4EF6\u3002`;
|
|
428
|
+
}
|
|
429
|
+
function ensureDir(abs) {
|
|
430
|
+
mkdirSync2(dirname2(abs), { recursive: true });
|
|
431
|
+
}
|
|
432
|
+
function writeFile(root, rel, content) {
|
|
433
|
+
const abs = join4(root, rel);
|
|
434
|
+
ensureDir(abs);
|
|
435
|
+
writeFileSync(abs, content);
|
|
436
|
+
return rel;
|
|
437
|
+
}
|
|
438
|
+
function readJson(root, rel) {
|
|
439
|
+
const abs = join4(root, rel);
|
|
440
|
+
if (!existsSync3(abs)) return {};
|
|
441
|
+
try {
|
|
442
|
+
return JSON.parse(readFileSync3(abs, "utf-8"));
|
|
443
|
+
} catch {
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function readToml(root, rel) {
|
|
448
|
+
const abs = join4(root, rel);
|
|
449
|
+
if (!existsSync3(abs)) return {};
|
|
450
|
+
try {
|
|
451
|
+
return parseToml(readFileSync3(abs, "utf-8"));
|
|
452
|
+
} catch {
|
|
453
|
+
return {};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function hasCommand(list, cmd) {
|
|
457
|
+
if (!Array.isArray(list)) return false;
|
|
458
|
+
return JSON.stringify(list).includes(cmd);
|
|
459
|
+
}
|
|
460
|
+
function yamlFrontmatter(fields, body) {
|
|
461
|
+
const fm = fields.map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
462
|
+
return `---
|
|
463
|
+
${fm}
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
${body.replace(/^\n+/, "")}
|
|
467
|
+
`;
|
|
468
|
+
}
|
|
469
|
+
function getAdapter(id) {
|
|
470
|
+
const a = ADAPTERS[id];
|
|
471
|
+
if (!a) throw new Error(`\u672A\u77E5\u5E73\u53F0: ${id}(\u652F\u6301 ${PLATFORM_IDS.join(", ")})`);
|
|
472
|
+
return a;
|
|
473
|
+
}
|
|
474
|
+
function resolvePlatforms(platforms) {
|
|
475
|
+
const ids = platforms && platforms.length ? platforms : ["claude"];
|
|
476
|
+
return ids.map((id) => getAdapter(id));
|
|
477
|
+
}
|
|
478
|
+
function resolveModel(model, adapter) {
|
|
479
|
+
if (!model) return adapter.defaultModel;
|
|
480
|
+
if (typeof model === "string") return model;
|
|
481
|
+
return model[adapter.id] ?? adapter.defaultModel;
|
|
482
|
+
}
|
|
483
|
+
var PLATFORM_IDS, claude, codex, opencode, cursor, ADAPTERS;
|
|
484
|
+
var init_platforms = __esm({
|
|
485
|
+
"core/platforms.ts"() {
|
|
486
|
+
"use strict";
|
|
487
|
+
PLATFORM_IDS = ["claude", "codex", "opencode", "cursor"];
|
|
488
|
+
claude = {
|
|
489
|
+
id: "claude",
|
|
490
|
+
dotDir: ".claude",
|
|
491
|
+
agentsDir: ".claude/agents",
|
|
492
|
+
skillsDir: ".claude/skills",
|
|
493
|
+
hooksScanDir: ".claude/hooks",
|
|
494
|
+
defaultModel: "opus",
|
|
495
|
+
memoryBlock: (role, lang) => claudeMemory(role, lang),
|
|
496
|
+
notes: [],
|
|
497
|
+
agentFile: (role) => `${role}.md`,
|
|
498
|
+
renderAgent: (spec) => yamlFrontmatter(
|
|
499
|
+
[
|
|
500
|
+
["name", spec.name],
|
|
501
|
+
["description", spec.description],
|
|
502
|
+
["model", spec.model],
|
|
503
|
+
["memory", "project"],
|
|
504
|
+
["tools", spec.tools.join(", ")]
|
|
505
|
+
],
|
|
506
|
+
spec.body
|
|
507
|
+
),
|
|
508
|
+
writeMcp(root, server) {
|
|
509
|
+
const rel = ".mcp.json";
|
|
510
|
+
const json2 = readJson(root, rel);
|
|
511
|
+
json2.mcpServers = json2.mcpServers ?? {};
|
|
512
|
+
json2.mcpServers[server.name] = { command: server.command, args: server.args };
|
|
513
|
+
return writeFile(root, rel, JSON.stringify(json2, null, 2) + "\n");
|
|
514
|
+
},
|
|
515
|
+
writeHooks(root, wire) {
|
|
516
|
+
const rel = ".claude/settings.json";
|
|
517
|
+
const json2 = readJson(root, rel);
|
|
518
|
+
json2.hooks = json2.hooks ?? {};
|
|
519
|
+
const matcher = "Edit|Write|MultiEdit";
|
|
520
|
+
for (const [event, cmd] of [
|
|
521
|
+
["PreToolUse", wire.preCommand],
|
|
522
|
+
["PostToolUse", wire.postCommand]
|
|
523
|
+
]) {
|
|
524
|
+
json2.hooks[event] = json2.hooks[event] ?? [];
|
|
525
|
+
if (!hasCommand(json2.hooks[event], cmd))
|
|
526
|
+
json2.hooks[event].push({ matcher, hooks: [{ type: "command", command: cmd }] });
|
|
527
|
+
}
|
|
528
|
+
return [writeFile(root, rel, JSON.stringify(json2, null, 2) + "\n")];
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
codex = {
|
|
532
|
+
id: "codex",
|
|
533
|
+
dotDir: ".codex",
|
|
534
|
+
agentsDir: ".codex/agents",
|
|
535
|
+
skillsDir: ".agents/skills",
|
|
536
|
+
hooksScanDir: null,
|
|
537
|
+
defaultModel: "gpt-5.1-codex",
|
|
538
|
+
memoryBlock: (_role, lang) => agentsMdMemory("Codex", lang),
|
|
539
|
+
notes: [
|
|
540
|
+
'Codex:\u9879\u76EE\u7EA7 .codex/* \u4EC5\u5F53\u9879\u76EE\u88AB\u6807\u8BB0 trusted \u624D\u52A0\u8F7D \u2014\u2014 \u5728 ~/.codex/config.toml \u91CC\u4E3A\u672C\u9879\u76EE\u8BBE trust_level="trusted"',
|
|
541
|
+
"Codex:skill \u8D70 .agents/skills/(\u975E .codex/skills)"
|
|
542
|
+
],
|
|
543
|
+
agentFile: (role) => `${role}.toml`,
|
|
544
|
+
renderAgent: (spec) => stringifyToml({
|
|
545
|
+
name: spec.name,
|
|
546
|
+
description: spec.description,
|
|
547
|
+
model: spec.model,
|
|
548
|
+
developer_instructions: spec.body.replace(/^\n+/, "")
|
|
549
|
+
}) + "\n",
|
|
550
|
+
writeMcp(root, server) {
|
|
551
|
+
const rel = ".codex/config.toml";
|
|
552
|
+
const cfg = readToml(root, rel);
|
|
553
|
+
cfg.mcp_servers = cfg.mcp_servers ?? {};
|
|
554
|
+
cfg.mcp_servers[server.name] = { command: server.command, args: server.args };
|
|
555
|
+
return writeFile(root, rel, stringifyToml(cfg) + "\n");
|
|
556
|
+
},
|
|
557
|
+
writeHooks(root, wire) {
|
|
558
|
+
const rel = ".codex/config.toml";
|
|
559
|
+
const cfg = readToml(root, rel);
|
|
560
|
+
cfg.hooks = cfg.hooks ?? {};
|
|
561
|
+
for (const [event, cmd] of [
|
|
562
|
+
["PreToolUse", wire.preCommand],
|
|
563
|
+
["PostToolUse", wire.postCommand]
|
|
564
|
+
]) {
|
|
565
|
+
cfg.hooks[event] = Array.isArray(cfg.hooks[event]) ? cfg.hooks[event] : [];
|
|
566
|
+
if (!hasCommand(cfg.hooks[event], cmd)) cfg.hooks[event].push({ hooks: [{ command: cmd }] });
|
|
567
|
+
}
|
|
568
|
+
return [writeFile(root, rel, stringifyToml(cfg) + "\n")];
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
opencode = {
|
|
572
|
+
id: "opencode",
|
|
573
|
+
dotDir: ".opencode",
|
|
574
|
+
agentsDir: ".opencode/agents",
|
|
575
|
+
skillsDir: ".opencode/skills",
|
|
576
|
+
hooksScanDir: null,
|
|
577
|
+
defaultModel: "anthropic/claude-opus-4-8",
|
|
578
|
+
memoryBlock: (_role, lang) => agentsMdMemory("OpenCode", lang),
|
|
579
|
+
notes: [
|
|
580
|
+
"OpenCode:\u6A21\u578B\u4E32\u662F provider/model \u683C\u5F0F(\u89C1 models.dev);API key \u5EFA\u8BAE\u8D70\u73AF\u5883\u53D8\u91CF\u6216 {env:...}"
|
|
581
|
+
],
|
|
582
|
+
agentFile: (role) => `${role}.md`,
|
|
583
|
+
renderAgent: (spec) => {
|
|
584
|
+
const canEdit = spec.tools.some((t) => t === "Edit" || t === "Write");
|
|
585
|
+
const canBash = spec.tools.includes("Bash");
|
|
586
|
+
const body = spec.body.replace(/^\n+/, "");
|
|
587
|
+
return `---
|
|
588
|
+
description: ${spec.description}
|
|
589
|
+
mode: subagent
|
|
590
|
+
model: ${spec.model}
|
|
591
|
+
permission:
|
|
592
|
+
edit: ${canEdit ? "allow" : "deny"}
|
|
593
|
+
bash: ${canBash ? "allow" : "deny"}
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
${body}
|
|
597
|
+
`;
|
|
598
|
+
},
|
|
599
|
+
writeMcp(root, server) {
|
|
600
|
+
const rel = "opencode.json";
|
|
601
|
+
const json2 = readJson(root, rel);
|
|
602
|
+
json2["$schema"] = json2["$schema"] ?? "https://opencode.ai/config.json";
|
|
603
|
+
json2.mcp = json2.mcp ?? {};
|
|
604
|
+
json2.mcp[server.name] = {
|
|
605
|
+
type: "local",
|
|
606
|
+
command: [server.command, ...server.args],
|
|
607
|
+
enabled: true
|
|
608
|
+
};
|
|
609
|
+
return writeFile(root, rel, JSON.stringify(json2, null, 2) + "\n");
|
|
610
|
+
},
|
|
611
|
+
writeHooks(root, wire) {
|
|
612
|
+
const rel = ".opencode/plugins/opcflow.ts";
|
|
613
|
+
const plugin = `// opcflow:\u628A\u5DE5\u5177\u8C03\u7528\u524D\u540E\u4E8B\u4EF6\u8F6C\u53D1\u7ED9 hook \u811A\u672C(\u89C2\u6D4B\u5199\u95E8\u7981 + \u5237\u65B0 hash)
|
|
614
|
+
import { spawn } from "node:child_process"
|
|
615
|
+
|
|
616
|
+
function runHook(cmd: string, payload: unknown): Promise<void> {
|
|
617
|
+
return new Promise(resolve => {
|
|
618
|
+
const parts = cmd.split(" ")
|
|
619
|
+
const p = spawn(parts[0], parts.slice(1), { stdio: ["pipe", "ignore", "ignore"] })
|
|
620
|
+
p.on("error", () => resolve())
|
|
621
|
+
p.on("close", () => resolve())
|
|
622
|
+
p.stdin.write(JSON.stringify(payload))
|
|
623
|
+
p.stdin.end()
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export const opcflow = async () => ({
|
|
628
|
+
"tool.execute.before": async (_input: unknown, output: unknown) => {
|
|
629
|
+
await runHook(${JSON.stringify(wire.preCommand)}, output)
|
|
630
|
+
},
|
|
631
|
+
"tool.execute.after": async (_input: unknown, output: unknown) => {
|
|
632
|
+
await runHook(${JSON.stringify(wire.postCommand)}, output)
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
`;
|
|
636
|
+
return [writeFile(root, rel, plugin)];
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
cursor = {
|
|
640
|
+
id: "cursor",
|
|
641
|
+
dotDir: ".cursor",
|
|
642
|
+
agentsDir: ".cursor/agents",
|
|
643
|
+
skillsDir: ".cursor/skills",
|
|
644
|
+
hooksScanDir: null,
|
|
645
|
+
defaultModel: "claude-opus-4-8",
|
|
646
|
+
memoryBlock: (_role, lang) => cursorMemory(lang),
|
|
647
|
+
notes: [
|
|
648
|
+
"Cursor:\u4E3B agent \u6A21\u578B\u7531 UI \u9009,--model \u53EA\u4F5C\u7528\u4E8E\u751F\u6210\u7684 subagent(.cursor/agents/*.md)"
|
|
649
|
+
],
|
|
650
|
+
agentFile: (role) => `${role}.md`,
|
|
651
|
+
renderAgent: (spec) => yamlFrontmatter(
|
|
652
|
+
[
|
|
653
|
+
["name", spec.name],
|
|
654
|
+
["description", spec.description],
|
|
655
|
+
["model", spec.model]
|
|
656
|
+
],
|
|
657
|
+
spec.body
|
|
658
|
+
),
|
|
659
|
+
writeMcp(root, server) {
|
|
660
|
+
const rel = ".cursor/mcp.json";
|
|
661
|
+
const json2 = readJson(root, rel);
|
|
662
|
+
json2.mcpServers = json2.mcpServers ?? {};
|
|
663
|
+
json2.mcpServers[server.name] = { command: server.command, args: server.args };
|
|
664
|
+
return writeFile(root, rel, JSON.stringify(json2, null, 2) + "\n");
|
|
665
|
+
},
|
|
666
|
+
writeHooks(root, wire) {
|
|
667
|
+
const rel = ".cursor/hooks.json";
|
|
668
|
+
const json2 = readJson(root, rel);
|
|
669
|
+
json2.version = json2.version ?? 1;
|
|
670
|
+
json2.hooks = json2.hooks ?? {};
|
|
671
|
+
for (const [event, cmd] of [
|
|
672
|
+
["preToolUse", wire.preCommand],
|
|
673
|
+
["afterFileEdit", wire.postCommand]
|
|
674
|
+
]) {
|
|
675
|
+
json2.hooks[event] = Array.isArray(json2.hooks[event]) ? json2.hooks[event] : [];
|
|
676
|
+
if (!hasCommand(json2.hooks[event], cmd)) json2.hooks[event].push({ command: cmd });
|
|
677
|
+
}
|
|
678
|
+
return [writeFile(root, rel, JSON.stringify(json2, null, 2) + "\n")];
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
ADAPTERS = { claude, codex, opencode, cursor };
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// core/kind.ts
|
|
686
|
+
var kind_exports = {};
|
|
687
|
+
__export(kind_exports, {
|
|
688
|
+
DEFAULT_KIND_REGISTRY: () => DEFAULT_KIND_REGISTRY,
|
|
689
|
+
KIND_TIERS: () => KIND_TIERS,
|
|
690
|
+
META_KINDS: () => META_KINDS,
|
|
691
|
+
PM_KINDS: () => PM_KINDS,
|
|
692
|
+
contractKinds: () => contractKinds,
|
|
693
|
+
getKindRegistry: () => getKindRegistry,
|
|
694
|
+
inferKind: () => inferKind,
|
|
695
|
+
kindSpec: () => kindSpec,
|
|
696
|
+
normalizeModule: () => normalizeModule
|
|
697
|
+
});
|
|
698
|
+
function getKindRegistry(config) {
|
|
699
|
+
const merged = { ...DEFAULT_KIND_REGISTRY };
|
|
700
|
+
if (config.kinds) {
|
|
701
|
+
for (const [kind, override] of Object.entries(config.kinds)) {
|
|
702
|
+
const base = merged[kind];
|
|
703
|
+
if (base) merged[kind] = { ...base, ...override };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const adapters = resolvePlatforms(config.platforms);
|
|
707
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
708
|
+
merged["agent-def"] = { ...merged["agent-def"], pathPatterns: uniq(adapters.map((a) => `${a.agentsDir}/`)) };
|
|
709
|
+
merged.skill = { ...merged.skill, pathPatterns: uniq(adapters.map((a) => `${a.skillsDir}/`)) };
|
|
710
|
+
merged["hook-script"] = {
|
|
711
|
+
...merged["hook-script"],
|
|
712
|
+
pathPatterns: uniq(adapters.map((a) => a.hooksScanDir).filter((d) => !!d).map((d) => `${d}/`))
|
|
713
|
+
};
|
|
714
|
+
return merged;
|
|
715
|
+
}
|
|
716
|
+
function kindSpec(config, kind) {
|
|
717
|
+
return getKindRegistry(config)[kind] ?? DEFAULT_KIND_REGISTRY.doc;
|
|
718
|
+
}
|
|
719
|
+
function contractKinds(config) {
|
|
720
|
+
const reg = getKindRegistry(config);
|
|
721
|
+
return Object.keys(reg).filter((k) => reg[k].approval === "human" && !reg[k].meta);
|
|
722
|
+
}
|
|
723
|
+
function expandPattern(pattern, config) {
|
|
724
|
+
return pattern.replace("{prd}", config.docs.prd).replace("{architecture}", config.docs.architecture).replace("{design}", config.docs.design).replace("{acceptance}", config.docs.acceptance);
|
|
725
|
+
}
|
|
726
|
+
function inferKind(relPath, config) {
|
|
727
|
+
const p = relPath.replace(/\\/g, "/");
|
|
728
|
+
const registry = getKindRegistry(config);
|
|
729
|
+
for (const [kind, spec] of Object.entries(registry)) {
|
|
730
|
+
for (const raw of spec.pathPatterns ?? []) {
|
|
731
|
+
const pattern = expandPattern(raw, config);
|
|
732
|
+
if (pattern.endsWith("/") ? p.startsWith(pattern) : p === pattern) return kind;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (p.endsWith(".html")) return "prototype";
|
|
736
|
+
if (p.endsWith(".md")) return "doc";
|
|
737
|
+
return "code";
|
|
738
|
+
}
|
|
739
|
+
function normalizeModule(module, config) {
|
|
740
|
+
if (!module) return null;
|
|
741
|
+
return config.moduleMapping[module] ?? module;
|
|
742
|
+
}
|
|
743
|
+
var DEFAULT_KIND_REGISTRY, META_KINDS, PM_KINDS, KIND_TIERS;
|
|
744
|
+
var init_kind = __esm({
|
|
745
|
+
"core/kind.ts"() {
|
|
746
|
+
"use strict";
|
|
747
|
+
init_platforms();
|
|
748
|
+
DEFAULT_KIND_REGISTRY = {
|
|
749
|
+
// 元产物在前:与业务路径无重叠,但显式优先更稳
|
|
750
|
+
"agent-def": { level: "project", approval: "human", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "full", meta: true, pathPatterns: [".claude/agents/"] },
|
|
751
|
+
skill: { level: "project", approval: "human", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "full", meta: true, pathPatterns: [".claude/skills/"] },
|
|
752
|
+
"hook-script": { level: "project", approval: "human", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "full", meta: true, pathPatterns: [".claude/hooks/"] },
|
|
753
|
+
plan: { level: "project", approval: "human", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "summary", meta: true, pathPatterns: ["docs/workbench/PLAN.md"] },
|
|
754
|
+
baseline: { level: "project", approval: "human", parents: [], drivesStale: true, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["ARCHITECTURE.md", "TECH.md"] },
|
|
755
|
+
project: { level: "project", approval: "human", parents: [], drivesStale: true, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{prd}/project.md"] },
|
|
756
|
+
roles: { level: "project", approval: "human", parents: ["project"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{prd}/roles.md"] },
|
|
757
|
+
glossary: { level: "project", approval: "human", parents: ["project"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{prd}/glossary.md"] },
|
|
758
|
+
flow: { level: "module", approval: "human", parents: ["project", "roles", "glossary"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{prd}/flows/"] },
|
|
759
|
+
"module-prd": { level: "module", approval: "human", parents: ["flow"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{prd}/modules/"] },
|
|
760
|
+
"page-prd": { level: "page", approval: "human", parents: ["module-prd"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{prd}/pages/"] },
|
|
761
|
+
"db-doc": { level: "module", approval: "human", parents: ["module-prd"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{architecture}/database/"] },
|
|
762
|
+
"api-doc": { level: "module", approval: "human", parents: ["module-prd", "db-doc"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{architecture}/api/"] },
|
|
763
|
+
"design-system": { level: "endpoint", approval: "human", parents: ["baseline"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{design}/systems/"] },
|
|
764
|
+
"design-prompt": { level: "page", approval: "none", parents: ["page-prd", "api-doc", "design-system"], drivesStale: false, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{design}/prompts/"] },
|
|
765
|
+
prototype: { level: "page", approval: "thumbs", parents: ["design-prompt", "design-system"], drivesStale: true, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{design}/prototypes/"] },
|
|
766
|
+
acceptance: { level: "page", approval: "human", parents: ["page-prd", "prototype"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{acceptance}/"] },
|
|
767
|
+
code: { level: "module", approval: "machine", parents: ["baseline", "api-doc", "prototype"], drivesStale: false, hashMode: "directory", retrieval: "semantic" },
|
|
768
|
+
doc: { level: "module", approval: "none", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "summary" }
|
|
769
|
+
};
|
|
770
|
+
META_KINDS = Object.keys(DEFAULT_KIND_REGISTRY).filter(
|
|
771
|
+
(k) => DEFAULT_KIND_REGISTRY[k].meta
|
|
772
|
+
);
|
|
773
|
+
PM_KINDS = ["project", "roles", "glossary", "flow", "module-prd", "page-prd"];
|
|
774
|
+
KIND_TIERS = [
|
|
775
|
+
["baseline", "project", "roles", "glossary"],
|
|
776
|
+
["flow"],
|
|
777
|
+
["module-prd"],
|
|
778
|
+
["page-prd"],
|
|
779
|
+
["db-doc", "api-doc", "design-system"],
|
|
780
|
+
["design-prompt"],
|
|
781
|
+
["prototype", "acceptance"],
|
|
782
|
+
["code"]
|
|
783
|
+
];
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// core/derive.ts
|
|
788
|
+
var derive_exports = {};
|
|
789
|
+
__export(derive_exports, {
|
|
790
|
+
everApproved: () => everApproved,
|
|
791
|
+
moduleCleared: () => moduleCleared,
|
|
792
|
+
prototypeEndorsed: () => prototypeEndorsed,
|
|
793
|
+
reviewStatus: () => reviewStatus,
|
|
794
|
+
taskStaleness: () => taskStaleness
|
|
795
|
+
});
|
|
796
|
+
function reviewStatus(a) {
|
|
797
|
+
if (a.approved_hash !== null && a.approved_hash === a.content_hash) return "approved";
|
|
798
|
+
if (a.submitted_hash !== null && a.submitted_hash === a.content_hash) return "pending";
|
|
799
|
+
if (a.approved_hash !== null) return "invalidated";
|
|
800
|
+
return "draft";
|
|
801
|
+
}
|
|
802
|
+
function everApproved(a) {
|
|
803
|
+
return a.approved_hash !== null;
|
|
804
|
+
}
|
|
805
|
+
function taskStaleness(db, taskId) {
|
|
806
|
+
const rows = db.prepare(
|
|
807
|
+
`SELECT ti.artifact_id, ti.input_hash, a.path, a.content_hash
|
|
808
|
+
FROM task_inputs ti JOIN artifacts a ON a.id = ti.artifact_id
|
|
809
|
+
WHERE ti.task_id = ?`
|
|
810
|
+
).all(taskId);
|
|
811
|
+
const changed = rows.filter((r) => r.input_hash !== r.content_hash).map((r) => ({ artifactId: r.artifact_id, path: r.path, inputHash: r.input_hash, currentHash: r.content_hash }));
|
|
812
|
+
return { stale: changed.length > 0, changed };
|
|
813
|
+
}
|
|
814
|
+
function moduleCleared(db, module) {
|
|
815
|
+
const rows = db.prepare("SELECT approved_hash, content_hash, submitted_hash FROM artifacts WHERE kind = 'module-prd' AND module = ?").all(module);
|
|
816
|
+
return rows.some((r) => reviewStatus(r) === "approved");
|
|
817
|
+
}
|
|
818
|
+
function prototypeEndorsed(db, artifact) {
|
|
819
|
+
if (artifact.kind !== "prototype") return false;
|
|
820
|
+
const row = db.prepare(
|
|
821
|
+
`SELECT COUNT(*) AS c FROM artifact_feedback
|
|
822
|
+
WHERE artifact_id = ? AND verdict = 1 AND content_hash = ?`
|
|
823
|
+
).get(artifact.id, artifact.content_hash);
|
|
824
|
+
return row.c > 0;
|
|
825
|
+
}
|
|
826
|
+
var init_derive = __esm({
|
|
827
|
+
"core/derive.ts"() {
|
|
828
|
+
"use strict";
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
// core/lints.ts
|
|
833
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
834
|
+
import { join as join5 } from "node:path";
|
|
835
|
+
function walkFiles(abs, rel, out) {
|
|
836
|
+
const st = statSync2(abs);
|
|
837
|
+
if (st.isFile()) {
|
|
838
|
+
out.push(rel);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
for (const name of readdirSync2(abs).sort()) {
|
|
842
|
+
if (IGNORE.has(name) || name.startsWith(".")) continue;
|
|
843
|
+
walkFiles(join5(abs, name), `${rel}/${name}`, out);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function runProtocolLints(ctx, scope) {
|
|
847
|
+
const violations = [];
|
|
848
|
+
const rules = ctx.config.protocolLints.filter((rule) => {
|
|
849
|
+
const ruleRole = rule.role ?? "developer";
|
|
850
|
+
if (ruleRole !== scope.role) return false;
|
|
851
|
+
if (rule.endpoint && scope.endpoint && rule.endpoint !== scope.endpoint) return false;
|
|
852
|
+
if (rule.endpoint && !scope.endpoint) return false;
|
|
853
|
+
return true;
|
|
854
|
+
});
|
|
855
|
+
for (const rule of rules) {
|
|
856
|
+
const regex = new RegExp(rule.grep);
|
|
857
|
+
const allow = new Set((rule.allow ?? []).map((p) => p.replace(/\\/g, "/")));
|
|
858
|
+
for (const base of rule.paths) {
|
|
859
|
+
const abs = join5(ctx.root, base);
|
|
860
|
+
if (!existsSync4(abs)) continue;
|
|
861
|
+
const files = [];
|
|
862
|
+
walkFiles(abs, base.replace(/\\/g, "/"), files);
|
|
863
|
+
for (const file of files) {
|
|
864
|
+
if (allow.has(file)) continue;
|
|
865
|
+
if (/\.(png|jpg|jpeg|gif|webp|woff2?|ttf|ico|db)$/i.test(file)) continue;
|
|
866
|
+
const lines = readFileSync4(join5(ctx.root, file), "utf-8").split("\n");
|
|
867
|
+
lines.forEach((text, i) => {
|
|
868
|
+
if (regex.test(text)) {
|
|
869
|
+
violations.push({
|
|
870
|
+
lint: rule.name,
|
|
871
|
+
file,
|
|
872
|
+
line: i + 1,
|
|
873
|
+
text: text.trim().slice(0, 120),
|
|
874
|
+
message: rule.message ?? rule.name
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return violations;
|
|
882
|
+
}
|
|
883
|
+
var IGNORE;
|
|
884
|
+
var init_lints = __esm({
|
|
885
|
+
"core/lints.ts"() {
|
|
886
|
+
"use strict";
|
|
887
|
+
IGNORE = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".workbench"]);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// core/gates.ts
|
|
892
|
+
import { execSync } from "node:child_process";
|
|
893
|
+
function findByKinds(ctx, kinds, f) {
|
|
894
|
+
if (kinds.length === 0) return [];
|
|
895
|
+
let query = `SELECT * FROM artifacts WHERE kind IN (${kinds.map(() => "?").join(",")})`;
|
|
896
|
+
const params = [...kinds];
|
|
897
|
+
if (f.module) {
|
|
898
|
+
query += " AND module = ?";
|
|
899
|
+
params.push(f.module);
|
|
900
|
+
}
|
|
901
|
+
if (f.endpoint) {
|
|
902
|
+
query += " AND endpoint = ?";
|
|
903
|
+
params.push(f.endpoint);
|
|
904
|
+
}
|
|
905
|
+
if (f.page) {
|
|
906
|
+
query += " AND page = ?";
|
|
907
|
+
params.push(f.page);
|
|
908
|
+
}
|
|
909
|
+
return ctx.db.prepare(query).all(...params);
|
|
910
|
+
}
|
|
911
|
+
function levelFilter(level, task) {
|
|
912
|
+
switch (level) {
|
|
913
|
+
case "project":
|
|
914
|
+
return {};
|
|
915
|
+
case "module":
|
|
916
|
+
return { module: task.module };
|
|
917
|
+
case "endpoint":
|
|
918
|
+
return { endpoint: task.endpoint };
|
|
919
|
+
case "page":
|
|
920
|
+
return { module: task.module, endpoint: task.endpoint, page: task.page };
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function producedKinds(ctx, task) {
|
|
924
|
+
const declared = ctx.config.roleProduces[task.role] ?? [];
|
|
925
|
+
if (task.role === "designer") {
|
|
926
|
+
return task.page ? declared.filter((k) => k !== "design-system") : ["design-system"];
|
|
927
|
+
}
|
|
928
|
+
return declared;
|
|
929
|
+
}
|
|
930
|
+
function existRequirements(task) {
|
|
931
|
+
const m = task.module;
|
|
932
|
+
switch (task.role) {
|
|
933
|
+
case "architect":
|
|
934
|
+
case "designer":
|
|
935
|
+
return [{ desc: "PM \u4EA7\u51FA(\u6A21\u5757/\u9875\u9762 PRD)", level: "exist", kinds: PM_KINDS, filter: { module: m } }];
|
|
936
|
+
case "developer": {
|
|
937
|
+
if (task.endpoint === "service") {
|
|
938
|
+
return [{ desc: "\u6570\u636E\u5E93\u6587\u6863", level: "exist", kinds: ["db-doc"], filter: { module: m } }];
|
|
939
|
+
}
|
|
940
|
+
return [
|
|
941
|
+
{ desc: "\u6570\u636E\u5E93\u6587\u6863", level: "exist", kinds: ["db-doc"], filter: { module: m } },
|
|
942
|
+
{ desc: "API \u6587\u6863", level: "exist", kinds: ["api-doc"], filter: { module: m } },
|
|
943
|
+
{ desc: `designer ${task.endpoint} \u8BBE\u8BA1\u7A3F`, level: "exist", kinds: ["design-prompt", "prototype"], filter: { module: m, endpoint: task.endpoint } }
|
|
944
|
+
];
|
|
945
|
+
}
|
|
946
|
+
default:
|
|
947
|
+
return [];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function approvedRequirements(ctx, task) {
|
|
951
|
+
const registry = getKindRegistry(ctx.config);
|
|
952
|
+
const produced = producedKinds(ctx, task);
|
|
953
|
+
const parentSet = /* @__PURE__ */ new Set();
|
|
954
|
+
for (const kind of produced) {
|
|
955
|
+
for (const parent of registry[kind]?.parents ?? []) {
|
|
956
|
+
if (!produced.includes(parent)) parentSet.add(parent);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const reqs = [];
|
|
960
|
+
for (const parent of parentSet) {
|
|
961
|
+
const spec = registry[parent];
|
|
962
|
+
if (!spec || spec.approval === "none" || spec.approval === "machine") continue;
|
|
963
|
+
if (spec.level === "page" && !task.page) continue;
|
|
964
|
+
if (spec.level === "endpoint" && !task.endpoint) continue;
|
|
965
|
+
if (spec.level === "module" && !task.module) continue;
|
|
966
|
+
reqs.push({
|
|
967
|
+
desc: `\u4E0A\u6E38 ${parent} \u5DF2\u8FBE\u4FE1\u4EFB\u72B6\u6001(${spec.approval === "thumbs" ? "\u{1F44D} \u653E\u884C" : "approved"})`,
|
|
968
|
+
level: "approved",
|
|
969
|
+
kinds: [parent],
|
|
970
|
+
filter: levelFilter(spec.level, task)
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return reqs;
|
|
974
|
+
}
|
|
975
|
+
function claimRequirements(ctx, task) {
|
|
976
|
+
if (task.type !== "build" && task.type !== "qa") return [];
|
|
977
|
+
if (task.role === "product-manager") return [];
|
|
978
|
+
return [...existRequirements(task), ...approvedRequirements(ctx, task)];
|
|
979
|
+
}
|
|
980
|
+
function requirementSatisfied(ctx, req, matched) {
|
|
981
|
+
if (req.level === "exist") return matched.length > 0;
|
|
982
|
+
if (matched.length === 0) return false;
|
|
983
|
+
return matched.some(
|
|
984
|
+
(a) => kindSpec(ctx.config, a.kind).approval === "thumbs" ? prototypeEndorsed(ctx.db, a) : reviewStatus(a) === "approved"
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
function validateClaim(ctx, task) {
|
|
988
|
+
const warnings = [];
|
|
989
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
990
|
+
const mode = ctx.config.gates.approvalMode;
|
|
991
|
+
if (task.role === "qa" && task.type === "qa") {
|
|
992
|
+
const dev = ctx.db.prepare(
|
|
993
|
+
`SELECT COUNT(*) AS c FROM tasks
|
|
994
|
+
WHERE role = 'developer' AND status = 'completed' AND module IS ? AND endpoint IS ?
|
|
995
|
+
AND (? IS NULL OR page IS ?)`
|
|
996
|
+
).get(task.module, task.endpoint, task.page, task.page);
|
|
997
|
+
if (dev.c === 0) {
|
|
998
|
+
throw new Error(`[\u524D\u7F6E\u6761\u4EF6] \u5BF9\u5E94\u7684 developer ${task.endpoint} \u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210,\u65E0\u6CD5\u9886\u53D6 qa \u4EFB\u52A1\u3002`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (task.module && (task.type === "build" || task.type === "hotfix") && !moduleCleared(ctx.db, task.module)) {
|
|
1002
|
+
warnings.push(
|
|
1003
|
+
`[\u6E05\u7B97] \u6A21\u5757 ${task.module} \u672A\u6E05\u7B97(module-prd \u672A\u5BA1\u6279)\u3002\u5EFA\u8BAE\u5148\u5BF9\u8D26:${ctx.config.cli} audit --module=${task.module}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
for (const req of claimRequirements(ctx, task)) {
|
|
1007
|
+
const matched = findByKinds(ctx, req.kinds, req.filter);
|
|
1008
|
+
for (const a of matched) {
|
|
1009
|
+
if (kindSpec(ctx.config, a.kind).drivesStale) inputs.set(a.id, a);
|
|
1010
|
+
}
|
|
1011
|
+
if (requirementSatisfied(ctx, req, matched)) continue;
|
|
1012
|
+
if (req.level === "exist") {
|
|
1013
|
+
throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${req.desc} \u4E0D\u5B58\u5728,\u65E0\u6CD5\u9886\u53D6\u4EFB\u52A1\u3002`);
|
|
1014
|
+
}
|
|
1015
|
+
const msg = `[\u4FE1\u4EFB\u8B66\u544A] ${req.desc}:\u672A\u6EE1\u8DB3`;
|
|
1016
|
+
if (mode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
|
|
1017
|
+
warnings.push(msg);
|
|
1018
|
+
}
|
|
1019
|
+
for (const b of findByKinds(ctx, ["baseline"], {})) inputs.set(b.id, b);
|
|
1020
|
+
return { warnings, inputs: [...inputs.values()] };
|
|
1021
|
+
}
|
|
1022
|
+
function completeOutputRequirement(ctx, task) {
|
|
1023
|
+
if (task.role === "developer" || task.role === "qa") return null;
|
|
1024
|
+
const kinds = producedKinds(ctx, task);
|
|
1025
|
+
if (kinds.length === 0) return null;
|
|
1026
|
+
const spec = kindSpec(ctx.config, kinds[0]);
|
|
1027
|
+
return { desc: `${task.role} \u4EA7\u51FA`, level: "exist", kinds, filter: levelFilter(spec.level === "page" && !task.page ? "module" : spec.level, task) };
|
|
1028
|
+
}
|
|
1029
|
+
function validateComplete(ctx, task, status, opts = {}) {
|
|
1030
|
+
const warnings = [];
|
|
1031
|
+
if (status !== "completed") return { warnings, inputs: [] };
|
|
1032
|
+
if (task.role !== "product-manager" && task.assignee === null) {
|
|
1033
|
+
throw new Error(`\u4EFB\u52A1\u5C1A\u672A\u88AB\u9886\u53D6,\u65E0\u6CD5\u66F4\u65B0\u72B6\u6001`);
|
|
1034
|
+
}
|
|
1035
|
+
if (task.type === "legacy") return { warnings, inputs: [] };
|
|
1036
|
+
const staleness = taskStaleness(ctx.db, task.id);
|
|
1037
|
+
if (staleness.stale) {
|
|
1038
|
+
const detail = staleness.changed.map((c) => c.path).join(", ");
|
|
1039
|
+
if (!opts.force) {
|
|
1040
|
+
throw new Error(`[\u524D\u7F6E\u6761\u4EF6] \u4EFB\u52A1\u5DF2 stale(\u4E0A\u6E38\u53D8\u66F4: ${detail})\u3002\u8BF7\u5148\u5BF9\u9F50\u53D8\u66F4,\u6216 --force=true \u653E\u884C(\u7559\u75D5)\u3002`);
|
|
1041
|
+
}
|
|
1042
|
+
warnings.push(`[\u5F3A\u5236\u653E\u884C] \u5FFD\u7565 stale \u4E0A\u6E38: ${detail}`);
|
|
1043
|
+
}
|
|
1044
|
+
const req = completeOutputRequirement(ctx, task);
|
|
1045
|
+
if (req) {
|
|
1046
|
+
const linked = ctx.db.prepare("SELECT COUNT(*) AS c FROM task_outputs WHERE task_id = ?").get(task.id);
|
|
1047
|
+
if (linked.c === 0) {
|
|
1048
|
+
const matched = findByKinds(ctx, req.kinds, req.filter);
|
|
1049
|
+
if (matched.length === 0) {
|
|
1050
|
+
throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${task.role} \u4EFB\u52A1\u5FC5\u987B\u6DFB\u52A0\u4EA7\u51FA\u6587\u4EF6\u540E\u624D\u80FD\u6807\u8BB0\u4E3A\u5B8C\u6210\u3002`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (task.role === "designer" && task.type === "build" && task.page) {
|
|
1055
|
+
const prototypes = findByKinds(ctx, ["prototype"], { module: task.module, endpoint: task.endpoint, page: task.page });
|
|
1056
|
+
const endorsed = prototypes.some((p) => prototypeEndorsed(ctx.db, p));
|
|
1057
|
+
if (!endorsed) {
|
|
1058
|
+
const msg = "[\u4FE1\u4EFB\u8B66\u544A] \u539F\u578B\u5C1A\u672A\u83B7\u5F97 \u{1F44D} \u653E\u884C";
|
|
1059
|
+
if (ctx.config.gates.approvalMode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
|
|
1060
|
+
warnings.push(msg);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (task.role === "developer" || task.role === "architect") {
|
|
1064
|
+
const violations = runProtocolLints(ctx, { role: task.role, endpoint: task.endpoint });
|
|
1065
|
+
if (violations.length > 0) {
|
|
1066
|
+
const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
|
|
1067
|
+
${v.text}`).join("\n");
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
`[\u534F\u8BAE lint \u5931\u8D25] ${violations.length} \u5904\u8FDD\u4F8B,\u4FEE\u590D\u540E\u518D\u5B8C\u6210:
|
|
1070
|
+
${detail}${violations.length > 10 ? `
|
|
1071
|
+
...\u7B49 ${violations.length} \u5904` : ""}`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (task.role === "developer" && task.endpoint && ctx.config.machineChecks.enabled) {
|
|
1076
|
+
const cmds = ctx.config.machineChecks[task.endpoint];
|
|
1077
|
+
if (Array.isArray(cmds)) {
|
|
1078
|
+
for (const cmd of cmds) {
|
|
1079
|
+
try {
|
|
1080
|
+
execSync(cmd, { cwd: ctx.root, stdio: "pipe" });
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
const e = err;
|
|
1083
|
+
const detail = `${e.stdout ?? ""}${e.stderr ?? ""}`.slice(-2e3) || String(err);
|
|
1084
|
+
throw new Error(`[\u673A\u5668\u68C0\u67E5\u5931\u8D25] ${cmd}
|
|
1085
|
+
${detail}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return { warnings, inputs: [] };
|
|
1091
|
+
}
|
|
1092
|
+
var init_gates = __esm({
|
|
1093
|
+
"core/gates.ts"() {
|
|
1094
|
+
"use strict";
|
|
1095
|
+
init_derive();
|
|
1096
|
+
init_kind();
|
|
1097
|
+
init_lints();
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
// core/tree.ts
|
|
1102
|
+
function worse(a, b) {
|
|
1103
|
+
return HEALTH_RANK[a] >= HEALTH_RANK[b] ? a : b;
|
|
1104
|
+
}
|
|
1105
|
+
function coordFailed(ctx, module, endpoint, page) {
|
|
1106
|
+
const row = ctx.db.prepare(
|
|
1107
|
+
`SELECT event FROM events
|
|
1108
|
+
WHERE event IN ('qa_passed', 'qa_failed', 'machine_check_failed')
|
|
1109
|
+
AND module IS ? AND (? IS NULL OR endpoint IS ?) AND (? IS NULL OR page IS ?)
|
|
1110
|
+
ORDER BY id DESC LIMIT 1`
|
|
1111
|
+
).get(module, endpoint, endpoint, page, page);
|
|
1112
|
+
return row !== void 0 && row.event !== "qa_passed";
|
|
1113
|
+
}
|
|
1114
|
+
function taskBlocked(ctx, task) {
|
|
1115
|
+
if (task.status !== "pending" || task.type === "legacy") return false;
|
|
1116
|
+
try {
|
|
1117
|
+
validateClaim(ctx, task);
|
|
1118
|
+
return false;
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
return err instanceof Error && err.message.includes("\u4E0D\u5B58\u5728");
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function coordHealth(ctx, artifacts, tasks) {
|
|
1124
|
+
let health = "ok";
|
|
1125
|
+
if (artifacts.some((a) => a.review_status === "invalidated")) health = worse(health, "stale");
|
|
1126
|
+
if (tasks.some((t) => t.status !== "completed" && t.status !== "cancelled" && t.stale)) health = worse(health, "stale");
|
|
1127
|
+
if (tasks.some((t) => taskBlocked(ctx, t))) health = worse(health, "blocked");
|
|
1128
|
+
return health;
|
|
1129
|
+
}
|
|
1130
|
+
function coordPhase(ctx, tasks) {
|
|
1131
|
+
const en = ctx.config.language === "en";
|
|
1132
|
+
if (tasks.length === 0) return en ? "not dispatched" : "\u672A\u6D3E\u53D1";
|
|
1133
|
+
const labels = STATUS_LABEL[en ? "en" : "zh"];
|
|
1134
|
+
for (const role of ctx.config.pipeline) {
|
|
1135
|
+
const roleTasks = tasks.filter((t) => t.role === role && t.status !== "cancelled");
|
|
1136
|
+
if (roleTasks.length === 0) continue;
|
|
1137
|
+
const unfinished = roleTasks.find((t) => t.status !== "completed");
|
|
1138
|
+
if (unfinished) return `${role} ${labels[unfinished.status] ?? unfinished.status}`;
|
|
1139
|
+
}
|
|
1140
|
+
return en ? "all done" : "\u5168\u90E8\u5B8C\u6210";
|
|
1141
|
+
}
|
|
1142
|
+
function decorate(ctx, a) {
|
|
1143
|
+
return {
|
|
1144
|
+
...a,
|
|
1145
|
+
review_status: reviewStatus(a),
|
|
1146
|
+
ever_approved: everApproved(a),
|
|
1147
|
+
endorsed: a.kind === "prototype" ? prototypeEndorsed(ctx.db, a) : false
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function buildTree(ctx, opts = {}) {
|
|
1151
|
+
const registry = getKindRegistry(ctx.config);
|
|
1152
|
+
const artifacts = ctx.db.prepare("SELECT * FROM artifacts ORDER BY path").all().filter((a) => opts.includeMeta || !registry[a.kind]?.meta).map((a) => decorate(ctx, a));
|
|
1153
|
+
const tasks = ctx.db.prepare("SELECT * FROM tasks ORDER BY id").all().map((t) => ({
|
|
1154
|
+
...t,
|
|
1155
|
+
stale: t.status === "completed" || t.status === "cancelled" ? false : taskStaleness(ctx.db, t.id).stale
|
|
1156
|
+
}));
|
|
1157
|
+
const moduleNames = [
|
|
1158
|
+
...new Set([...artifacts.map((a) => a.module), ...tasks.map((t) => t.module)].filter((m) => !!m))
|
|
1159
|
+
].sort();
|
|
1160
|
+
const makeCounts = (as, ts) => ({
|
|
1161
|
+
artifacts: as.length,
|
|
1162
|
+
tasksDone: ts.filter((t) => t.status === "completed").length,
|
|
1163
|
+
tasksTotal: ts.filter((t) => t.status !== "cancelled").length
|
|
1164
|
+
});
|
|
1165
|
+
const moduleNodes = moduleNames.map((module) => {
|
|
1166
|
+
const mArtifacts = artifacts.filter((a) => a.module === module);
|
|
1167
|
+
const mTasks = tasks.filter((t) => t.module === module);
|
|
1168
|
+
const endpoints = [
|
|
1169
|
+
...new Set([...mArtifacts.map((a) => a.endpoint), ...mTasks.map((t) => t.endpoint)].filter((e) => !!e))
|
|
1170
|
+
].sort();
|
|
1171
|
+
const endpointNodes = endpoints.map((endpoint) => {
|
|
1172
|
+
const eArtifacts = mArtifacts.filter((a) => a.endpoint === endpoint);
|
|
1173
|
+
const eTasks = mTasks.filter((t) => t.endpoint === endpoint);
|
|
1174
|
+
const pages = [
|
|
1175
|
+
...new Set([...eArtifacts.map((a) => a.page), ...eTasks.map((t) => t.page)].filter((p) => !!p))
|
|
1176
|
+
].sort();
|
|
1177
|
+
const pageNodes = pages.map((page) => {
|
|
1178
|
+
const pArtifacts = eArtifacts.filter((a) => a.page === page);
|
|
1179
|
+
const pTasks = eTasks.filter((t) => t.page === page);
|
|
1180
|
+
let health3 = coordHealth(ctx, pArtifacts, pTasks);
|
|
1181
|
+
if (coordFailed(ctx, module, endpoint, page)) health3 = "failed";
|
|
1182
|
+
return {
|
|
1183
|
+
key: `${module}/${endpoint}/${page}`,
|
|
1184
|
+
title: page.includes("/") ? page.split("/").pop() : page,
|
|
1185
|
+
level: "page",
|
|
1186
|
+
module,
|
|
1187
|
+
endpoint,
|
|
1188
|
+
page,
|
|
1189
|
+
health: health3,
|
|
1190
|
+
phase: coordPhase(ctx, pTasks),
|
|
1191
|
+
counts: makeCounts(pArtifacts, pTasks),
|
|
1192
|
+
children: []
|
|
1193
|
+
};
|
|
1194
|
+
});
|
|
1195
|
+
let health2 = coordHealth(
|
|
1196
|
+
ctx,
|
|
1197
|
+
eArtifacts.filter((a) => !a.page),
|
|
1198
|
+
eTasks.filter((t) => !t.page)
|
|
1199
|
+
);
|
|
1200
|
+
for (const child of pageNodes) health2 = worse(health2, child.health);
|
|
1201
|
+
return {
|
|
1202
|
+
key: `${module}/${endpoint}`,
|
|
1203
|
+
title: endpoint,
|
|
1204
|
+
level: "endpoint",
|
|
1205
|
+
module,
|
|
1206
|
+
endpoint,
|
|
1207
|
+
page: null,
|
|
1208
|
+
health: health2,
|
|
1209
|
+
phase: coordPhase(ctx, eTasks),
|
|
1210
|
+
counts: makeCounts(eArtifacts, eTasks),
|
|
1211
|
+
children: pageNodes
|
|
1212
|
+
};
|
|
1213
|
+
});
|
|
1214
|
+
let health = coordHealth(
|
|
1215
|
+
ctx,
|
|
1216
|
+
mArtifacts.filter((a) => !a.endpoint),
|
|
1217
|
+
mTasks.filter((t) => !t.endpoint)
|
|
1218
|
+
);
|
|
1219
|
+
for (const child of endpointNodes) health = worse(health, child.health);
|
|
1220
|
+
if (coordFailed(ctx, module, null, null)) health = "failed";
|
|
1221
|
+
return {
|
|
1222
|
+
key: module,
|
|
1223
|
+
title: module,
|
|
1224
|
+
level: "module",
|
|
1225
|
+
module,
|
|
1226
|
+
endpoint: null,
|
|
1227
|
+
page: null,
|
|
1228
|
+
health,
|
|
1229
|
+
phase: coordPhase(ctx, mTasks),
|
|
1230
|
+
counts: makeCounts(mArtifacts, mTasks),
|
|
1231
|
+
children: endpointNodes
|
|
1232
|
+
};
|
|
1233
|
+
});
|
|
1234
|
+
const bucket = (key, title, rows) => rows.length > 0 ? {
|
|
1235
|
+
key,
|
|
1236
|
+
title,
|
|
1237
|
+
level: "endpoint",
|
|
1238
|
+
module: null,
|
|
1239
|
+
endpoint: null,
|
|
1240
|
+
page: null,
|
|
1241
|
+
health: rows.some((a) => a.review_status === "invalidated") ? "stale" : "ok",
|
|
1242
|
+
phase: "-",
|
|
1243
|
+
counts: { artifacts: rows.length, tasksDone: 0, tasksTotal: 0 },
|
|
1244
|
+
children: []
|
|
1245
|
+
} : null;
|
|
1246
|
+
const en = ctx.config.language === "en";
|
|
1247
|
+
const projectLevel = artifacts.filter((a) => !a.module);
|
|
1248
|
+
const projectBucket = bucket("__project__", en ? "Project contracts" : "\u9879\u76EE\u7EA7\u5951\u7EA6", projectLevel.filter((a) => !registry[a.kind]?.meta));
|
|
1249
|
+
const metaBucket = bucket("__meta__", en ? "Meta (agent/skill/plan)" : "\u5143\u4EA7\u7269(agent/skill/plan)", projectLevel.filter((a) => registry[a.kind]?.meta));
|
|
1250
|
+
let rootHealth = "ok";
|
|
1251
|
+
for (const b of [projectBucket, metaBucket]) if (b) rootHealth = worse(rootHealth, b.health);
|
|
1252
|
+
for (const m of moduleNodes) rootHealth = worse(rootHealth, m.health);
|
|
1253
|
+
return {
|
|
1254
|
+
key: "__root__",
|
|
1255
|
+
title: en ? "Project" : "\u9879\u76EE",
|
|
1256
|
+
level: "project",
|
|
1257
|
+
module: null,
|
|
1258
|
+
endpoint: null,
|
|
1259
|
+
page: null,
|
|
1260
|
+
health: rootHealth,
|
|
1261
|
+
phase: "-",
|
|
1262
|
+
counts: makeCounts(artifacts, tasks),
|
|
1263
|
+
children: [...projectBucket ? [projectBucket] : [], ...metaBucket ? [metaBucket] : [], ...moduleNodes]
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function nodeDetail(ctx, f) {
|
|
1267
|
+
const registry = getKindRegistry(ctx.config);
|
|
1268
|
+
let aq = "SELECT * FROM artifacts WHERE 1=1";
|
|
1269
|
+
let tq = "SELECT * FROM tasks WHERE 1=1";
|
|
1270
|
+
const ap = [];
|
|
1271
|
+
const tp = [];
|
|
1272
|
+
if (f.module !== void 0) {
|
|
1273
|
+
aq += " AND module IS ?";
|
|
1274
|
+
tq += " AND module IS ?";
|
|
1275
|
+
ap.push(f.module);
|
|
1276
|
+
tp.push(f.module);
|
|
1277
|
+
}
|
|
1278
|
+
if (f.endpoint != null) {
|
|
1279
|
+
aq += " AND endpoint IS ?";
|
|
1280
|
+
tq += " AND endpoint IS ?";
|
|
1281
|
+
ap.push(f.endpoint);
|
|
1282
|
+
tp.push(f.endpoint);
|
|
1283
|
+
}
|
|
1284
|
+
if (f.page != null) {
|
|
1285
|
+
aq += " AND page IS ?";
|
|
1286
|
+
tq += " AND page IS ?";
|
|
1287
|
+
ap.push(f.page);
|
|
1288
|
+
tp.push(f.page);
|
|
1289
|
+
}
|
|
1290
|
+
const artifacts = ctx.db.prepare(aq + " ORDER BY kind, path").all(...ap).filter((a) => {
|
|
1291
|
+
const isMeta = !!registry[a.kind]?.meta;
|
|
1292
|
+
if (f.metaOnly) return isMeta;
|
|
1293
|
+
return f.includeMeta || !isMeta;
|
|
1294
|
+
}).map((a) => decorate(ctx, a));
|
|
1295
|
+
const tasks = ctx.db.prepare(tq + " ORDER BY id DESC").all(...tp).map((t) => ({
|
|
1296
|
+
...t,
|
|
1297
|
+
stale: t.status === "completed" || t.status === "cancelled" ? false : taskStaleness(ctx.db, t.id).stale
|
|
1298
|
+
}));
|
|
1299
|
+
return { artifacts, tasks };
|
|
1300
|
+
}
|
|
1301
|
+
var HEALTH_RANK, STATUS_LABEL;
|
|
1302
|
+
var init_tree = __esm({
|
|
1303
|
+
"core/tree.ts"() {
|
|
1304
|
+
"use strict";
|
|
1305
|
+
init_derive();
|
|
1306
|
+
init_gates();
|
|
1307
|
+
init_kind();
|
|
1308
|
+
HEALTH_RANK = { ok: 0, stale: 1, blocked: 2, failed: 3 };
|
|
1309
|
+
STATUS_LABEL = {
|
|
1310
|
+
zh: { pending: "\u5F85\u9886\u53D6", in_progress: "\u8FDB\u884C\u4E2D", completed: "\u5DF2\u5B8C\u6210", cancelled: "\u5DF2\u53D6\u6D88" },
|
|
1311
|
+
en: { pending: "unclaimed", in_progress: "in progress", completed: "done", cancelled: "cancelled" }
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// core/git.ts
|
|
1317
|
+
import { execFileSync } from "node:child_process";
|
|
1318
|
+
function gitHead(cwd) {
|
|
1319
|
+
try {
|
|
1320
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
1321
|
+
} catch {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function touchedSince(cwd, sinceCommit) {
|
|
1326
|
+
const files = /* @__PURE__ */ new Set();
|
|
1327
|
+
try {
|
|
1328
|
+
if (sinceCommit) {
|
|
1329
|
+
const out = execFileSync("git", ["diff", "--name-only", `${sinceCommit}..HEAD`], {
|
|
1330
|
+
cwd,
|
|
1331
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1332
|
+
}).toString();
|
|
1333
|
+
for (const line of out.split("\n")) if (line.trim()) files.add(line.trim());
|
|
1334
|
+
}
|
|
1335
|
+
const status = execFileSync("git", ["status", "--porcelain"], { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
1336
|
+
for (const line of status.split("\n")) {
|
|
1337
|
+
const p = line.slice(3).trim().replace(/^"|"$/g, "");
|
|
1338
|
+
if (p) files.add(p);
|
|
1339
|
+
}
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
return [...files];
|
|
1343
|
+
}
|
|
1344
|
+
function headCommitInfo(cwd, trailerKey) {
|
|
1345
|
+
try {
|
|
1346
|
+
const hash = execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
1347
|
+
const trailer = execFileSync("git", ["log", "-1", `--format=%(trailers:key=${trailerKey},valueonly)`], {
|
|
1348
|
+
cwd,
|
|
1349
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1350
|
+
}).toString().trim();
|
|
1351
|
+
const files = execFileSync("git", ["show", "--name-only", "--format=", "HEAD"], {
|
|
1352
|
+
cwd,
|
|
1353
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1354
|
+
}).toString().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1355
|
+
return { hash, trailer: trailer || null, files };
|
|
1356
|
+
} catch {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
function touchedByTaskTrailer(cwd, sinceCommit, trailerKey, taskId) {
|
|
1361
|
+
const files = /* @__PURE__ */ new Set();
|
|
1362
|
+
try {
|
|
1363
|
+
const log = execFileSync(
|
|
1364
|
+
"git",
|
|
1365
|
+
["log", "--format=%H%x00%(trailers:key=" + trailerKey + ",valueonly)", `${sinceCommit}..HEAD`],
|
|
1366
|
+
{ cwd, stdio: ["ignore", "pipe", "ignore"] }
|
|
1367
|
+
).toString();
|
|
1368
|
+
const mine = [];
|
|
1369
|
+
for (const line of log.split("\n")) {
|
|
1370
|
+
const [hash, trailer] = line.split("\0");
|
|
1371
|
+
if (hash && trailer?.trim() === `#${taskId}`) mine.push(hash);
|
|
1372
|
+
}
|
|
1373
|
+
for (const hash of mine) {
|
|
1374
|
+
const out = execFileSync("git", ["show", "--name-only", "--format=", hash], {
|
|
1375
|
+
cwd,
|
|
1376
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1377
|
+
}).toString();
|
|
1378
|
+
for (const line of out.split("\n")) if (line.trim()) files.add(line.trim());
|
|
1379
|
+
}
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
return [...files];
|
|
1383
|
+
}
|
|
1384
|
+
var init_git = __esm({
|
|
1385
|
+
"core/git.ts"() {
|
|
1386
|
+
"use strict";
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
// core/gh.ts
|
|
1391
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1392
|
+
function closeLinkedIssue(ctx, externalRef, taskId) {
|
|
1393
|
+
const match = externalRef.match(/^gh#(\d+)$/);
|
|
1394
|
+
if (!match) return false;
|
|
1395
|
+
try {
|
|
1396
|
+
execFileSync2("gh", ["issue", "close", match[1], "--comment", `\u5DF2\u5904\u7406,\u5173\u8054\u4EFB\u52A1 #${taskId}`], {
|
|
1397
|
+
cwd: ctx.root,
|
|
1398
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1399
|
+
});
|
|
1400
|
+
return true;
|
|
1401
|
+
} catch {
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
var init_gh = __esm({
|
|
1406
|
+
"core/gh.ts"() {
|
|
1407
|
+
"use strict";
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
// core/commands/artifact.commands.ts
|
|
1412
|
+
var artifact_commands_exports = {};
|
|
1413
|
+
__export(artifact_commands_exports, {
|
|
1414
|
+
approveArtifact: () => approveArtifact,
|
|
1415
|
+
approvedContent: () => approvedContent,
|
|
1416
|
+
feedbackArtifact: () => feedbackArtifact,
|
|
1417
|
+
listArtifacts: () => listArtifacts,
|
|
1418
|
+
moveArtifact: () => moveArtifact,
|
|
1419
|
+
normalizeRelPath: () => normalizeRelPath,
|
|
1420
|
+
refreshArtifact: () => refreshArtifact,
|
|
1421
|
+
registerOutput: () => registerOutput,
|
|
1422
|
+
rejectArtifact: () => rejectArtifact,
|
|
1423
|
+
resolveArtifact: () => resolveArtifact,
|
|
1424
|
+
submitArtifact: () => submitArtifact
|
|
1425
|
+
});
|
|
1426
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1427
|
+
import { isAbsolute, join as join6, relative } from "node:path";
|
|
1428
|
+
function normalizeRelPath(ctx, p) {
|
|
1429
|
+
let abs = isAbsolute(p) ? p : join6(ctx.root, p);
|
|
1430
|
+
if (existsSync5(abs)) {
|
|
1431
|
+
try {
|
|
1432
|
+
abs = realpathSync.native(abs);
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return relative(realpathSync.native(ctx.root), abs).replace(/\\/g, "/");
|
|
1437
|
+
}
|
|
1438
|
+
function dumpApprovals(ctx) {
|
|
1439
|
+
const rows = ctx.db.prepare(
|
|
1440
|
+
`SELECT path, approved_hash, reviewed_by, reviewed_at FROM artifacts
|
|
1441
|
+
WHERE approved_hash IS NOT NULL ORDER BY path`
|
|
1442
|
+
).all();
|
|
1443
|
+
writeFileSync2(join6(ctx.root, ctx.config.dataDir, "approvals.json"), JSON.stringify(rows, null, 2) + "\n");
|
|
1444
|
+
}
|
|
1445
|
+
function resolveArtifact(ctx, ref) {
|
|
1446
|
+
let row;
|
|
1447
|
+
if (ref.id !== void 0) {
|
|
1448
|
+
row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(ref.id);
|
|
1449
|
+
} else if (ref.path) {
|
|
1450
|
+
row = ctx.db.prepare("SELECT * FROM artifacts WHERE path = ?").get(normalizeRelPath(ctx, ref.path));
|
|
1451
|
+
}
|
|
1452
|
+
if (!row) throw new Error(`\u4EA7\u7269\u4E0D\u5B58\u5728: ${ref.id ?? ref.path}`);
|
|
1453
|
+
return row;
|
|
1454
|
+
}
|
|
1455
|
+
function refreshArtifact(ctx, ref, actor = "system") {
|
|
1456
|
+
const row = resolveArtifact(ctx, ref);
|
|
1457
|
+
const current = hashPath(join6(ctx.root, row.path));
|
|
1458
|
+
if (current === null || current === row.content_hash) return row;
|
|
1459
|
+
const tx = ctx.db.transaction(() => {
|
|
1460
|
+
ctx.db.prepare("UPDATE artifacts SET content_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(current, row.id);
|
|
1461
|
+
logEvent(ctx.db, {
|
|
1462
|
+
entityType: "artifact",
|
|
1463
|
+
entityId: row.id,
|
|
1464
|
+
event: "content_changed",
|
|
1465
|
+
actor,
|
|
1466
|
+
payload: { from: row.content_hash, to: current },
|
|
1467
|
+
module: row.module,
|
|
1468
|
+
endpoint: row.endpoint,
|
|
1469
|
+
page: row.page
|
|
1470
|
+
});
|
|
1471
|
+
if (row.approved_hash !== null && row.approved_hash !== current) {
|
|
1472
|
+
logEvent(ctx.db, {
|
|
1473
|
+
entityType: "artifact",
|
|
1474
|
+
entityId: row.id,
|
|
1475
|
+
event: "approval_invalidated",
|
|
1476
|
+
actor,
|
|
1477
|
+
payload: { approvedHash: row.approved_hash, currentHash: current },
|
|
1478
|
+
module: row.module,
|
|
1479
|
+
endpoint: row.endpoint,
|
|
1480
|
+
page: row.page
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
if (row.submitted_hash !== null && row.submitted_hash !== current) {
|
|
1484
|
+
logEvent(ctx.db, {
|
|
1485
|
+
entityType: "artifact",
|
|
1486
|
+
entityId: row.id,
|
|
1487
|
+
event: "submission_stale",
|
|
1488
|
+
actor,
|
|
1489
|
+
payload: { submittedHash: row.submitted_hash, currentHash: current },
|
|
1490
|
+
module: row.module,
|
|
1491
|
+
endpoint: row.endpoint,
|
|
1492
|
+
page: row.page
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
tx();
|
|
1497
|
+
return resolveArtifact(ctx, { id: row.id });
|
|
1498
|
+
}
|
|
1499
|
+
function registerOutput(ctx, p) {
|
|
1500
|
+
if (p.role === "developer") {
|
|
1501
|
+
throw new Error(`developer \u89D2\u8272\u7684\u4EA7\u51FA\u4E0D\u9700\u8981\u8BB0\u5F55\u5230 task_outputs,\u4EE3\u7801\u76F4\u63A5\u5B9E\u73B0\u5230\u9879\u76EE\u8DEF\u5F84\u5373\u53EF\u3002`);
|
|
1502
|
+
}
|
|
1503
|
+
const relPath = normalizeRelPath(ctx, p.filePath);
|
|
1504
|
+
const abs = join6(ctx.root, relPath);
|
|
1505
|
+
if (!existsSync5(abs)) {
|
|
1506
|
+
throw new Error(`\u4EA7\u51FA\u6587\u4EF6\u4E0D\u5B58\u5728: ${relPath},\u8BF7\u5148\u5199\u6587\u4EF6\u518D\u767B\u8BB0`);
|
|
1507
|
+
}
|
|
1508
|
+
const existing = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(relPath);
|
|
1509
|
+
if (existing) {
|
|
1510
|
+
throw new Error(`\u4EA7\u51FA\u6587\u4EF6\u5DF2\u5B58\u5728: ${relPath}`);
|
|
1511
|
+
}
|
|
1512
|
+
const kind = inferKind(relPath, ctx.config);
|
|
1513
|
+
const level = kindSpec(ctx.config, kind).level;
|
|
1514
|
+
const module = level === "project" || level === "endpoint" ? null : normalizeModule(p.module, ctx.config);
|
|
1515
|
+
const hash = hashPath(abs);
|
|
1516
|
+
const actor = p.actor ?? p.role;
|
|
1517
|
+
let linkedTaskId = p.taskId ?? null;
|
|
1518
|
+
if (linkedTaskId === null) {
|
|
1519
|
+
const match = ctx.db.prepare(
|
|
1520
|
+
`SELECT id FROM tasks
|
|
1521
|
+
WHERE role = ? AND endpoint = ? AND module IS ? AND assignee IS NOT NULL
|
|
1522
|
+
AND status IN ('pending', 'in_progress')
|
|
1523
|
+
AND (? IS NULL OR page IS NULL OR page = ?)
|
|
1524
|
+
ORDER BY id DESC LIMIT 1`
|
|
1525
|
+
).get(p.role, p.endpoint, module, p.page ?? null, p.page ?? null);
|
|
1526
|
+
linkedTaskId = match?.id ?? null;
|
|
1527
|
+
}
|
|
1528
|
+
const tx = ctx.db.transaction(() => {
|
|
1529
|
+
const result = ctx.db.prepare(
|
|
1530
|
+
`INSERT INTO artifacts (kind, module, endpoint, page, path, content_hash)
|
|
1531
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1532
|
+
).run(kind, module, p.endpoint, p.page ?? null, relPath, hash);
|
|
1533
|
+
const artifactId = result.lastInsertRowid;
|
|
1534
|
+
if (linkedTaskId !== null) {
|
|
1535
|
+
ctx.db.prepare("INSERT OR IGNORE INTO task_outputs (task_id, artifact_id) VALUES (?, ?)").run(linkedTaskId, artifactId);
|
|
1536
|
+
}
|
|
1537
|
+
logEvent(ctx.db, {
|
|
1538
|
+
entityType: "artifact",
|
|
1539
|
+
entityId: artifactId,
|
|
1540
|
+
event: "output_added",
|
|
1541
|
+
actor,
|
|
1542
|
+
payload: { path: relPath, kind, taskId: linkedTaskId },
|
|
1543
|
+
module,
|
|
1544
|
+
endpoint: p.endpoint,
|
|
1545
|
+
page: p.page ?? null
|
|
1546
|
+
});
|
|
1547
|
+
return artifactId;
|
|
1548
|
+
});
|
|
1549
|
+
return { artifactId: tx(), linkedTaskId, kind };
|
|
1550
|
+
}
|
|
1551
|
+
function submitArtifact(ctx, ref, actor) {
|
|
1552
|
+
const row = refreshArtifact(ctx, ref, actor);
|
|
1553
|
+
const tx = ctx.db.transaction(() => {
|
|
1554
|
+
ctx.db.prepare(
|
|
1555
|
+
"UPDATE artifacts SET submitted_at = CURRENT_TIMESTAMP, submitted_hash = content_hash, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
|
1556
|
+
).run(row.id);
|
|
1557
|
+
logEvent(ctx.db, {
|
|
1558
|
+
entityType: "artifact",
|
|
1559
|
+
entityId: row.id,
|
|
1560
|
+
event: "submitted",
|
|
1561
|
+
actor,
|
|
1562
|
+
module: row.module,
|
|
1563
|
+
endpoint: row.endpoint,
|
|
1564
|
+
page: row.page
|
|
1565
|
+
});
|
|
1566
|
+
});
|
|
1567
|
+
tx();
|
|
1568
|
+
return resolveArtifact(ctx, { id: row.id });
|
|
1569
|
+
}
|
|
1570
|
+
function approveArtifact(ctx, ref, actor, opts = {}) {
|
|
1571
|
+
const row = refreshArtifact(ctx, ref, actor);
|
|
1572
|
+
const via = opts.via ?? "review";
|
|
1573
|
+
const tx = ctx.db.transaction(() => {
|
|
1574
|
+
ctx.db.prepare(
|
|
1575
|
+
`UPDATE artifacts SET approved_hash = content_hash, reviewed_by = ?, reviewed_at = CURRENT_TIMESTAMP,
|
|
1576
|
+
updated_at = CURRENT_TIMESTAMP WHERE id = ?`
|
|
1577
|
+
).run(actor, row.id);
|
|
1578
|
+
logEvent(ctx.db, {
|
|
1579
|
+
entityType: "artifact",
|
|
1580
|
+
entityId: row.id,
|
|
1581
|
+
event: "approved",
|
|
1582
|
+
actor,
|
|
1583
|
+
payload: { via, hash: row.content_hash, trivial: opts.trivial ?? false },
|
|
1584
|
+
module: row.module,
|
|
1585
|
+
endpoint: row.endpoint,
|
|
1586
|
+
page: row.page
|
|
1587
|
+
});
|
|
1588
|
+
if (opts.trivial) {
|
|
1589
|
+
ctx.db.prepare(
|
|
1590
|
+
`UPDATE task_inputs SET input_hash = (SELECT content_hash FROM artifacts WHERE id = ?)
|
|
1591
|
+
WHERE artifact_id = ?`
|
|
1592
|
+
).run(row.id, row.id);
|
|
1593
|
+
const reviews = ctx.db.prepare(
|
|
1594
|
+
`SELECT t.id FROM tasks t JOIN task_inputs ti ON ti.task_id = t.id
|
|
1595
|
+
WHERE t.type = 'review' AND t.status = 'pending' AND ti.artifact_id = ?`
|
|
1596
|
+
).all(row.id);
|
|
1597
|
+
for (const r of reviews) {
|
|
1598
|
+
ctx.db.prepare("UPDATE tasks SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(r.id);
|
|
1599
|
+
logEvent(ctx.db, {
|
|
1600
|
+
entityType: "task",
|
|
1601
|
+
entityId: r.id,
|
|
1602
|
+
event: "review_auto_closed",
|
|
1603
|
+
actor,
|
|
1604
|
+
payload: { source: row.path, reason: "trivial \u5BA1\u6279" }
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
tx();
|
|
1610
|
+
dumpApprovals(ctx);
|
|
1611
|
+
archiveApprovedContent(ctx, row.id);
|
|
1612
|
+
return resolveArtifact(ctx, { id: row.id });
|
|
1613
|
+
}
|
|
1614
|
+
function archiveApprovedContent(ctx, artifactId) {
|
|
1615
|
+
try {
|
|
1616
|
+
const row = resolveArtifact(ctx, { id: artifactId });
|
|
1617
|
+
const abs = join6(ctx.root, row.path);
|
|
1618
|
+
if (!existsSync5(abs) || statSync3(abs).isDirectory()) return;
|
|
1619
|
+
const dir = join6(ctx.root, ctx.config.dataDir, "approved");
|
|
1620
|
+
mkdirSync3(dir, { recursive: true });
|
|
1621
|
+
writeFileSync2(join6(dir, String(artifactId)), readFileSync5(abs));
|
|
1622
|
+
} catch {
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
function approvedContent(ctx, artifactId) {
|
|
1626
|
+
const file = join6(ctx.root, ctx.config.dataDir, "approved", String(artifactId));
|
|
1627
|
+
if (!existsSync5(file)) return null;
|
|
1628
|
+
return readFileSync5(file, "utf-8");
|
|
1629
|
+
}
|
|
1630
|
+
function rejectArtifact(ctx, ref, actor, reason) {
|
|
1631
|
+
if (!reason || !reason.trim()) throw new Error("\u6253\u56DE\u5FC5\u987B\u9644\u539F\u56E0");
|
|
1632
|
+
const row = resolveArtifact(ctx, ref);
|
|
1633
|
+
const tx = ctx.db.transaction(() => {
|
|
1634
|
+
ctx.db.prepare(
|
|
1635
|
+
"UPDATE artifacts SET submitted_at = NULL, submitted_hash = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
|
1636
|
+
).run(row.id);
|
|
1637
|
+
logEvent(ctx.db, {
|
|
1638
|
+
entityType: "artifact",
|
|
1639
|
+
entityId: row.id,
|
|
1640
|
+
event: "rejected",
|
|
1641
|
+
actor,
|
|
1642
|
+
payload: { reason },
|
|
1643
|
+
module: row.module,
|
|
1644
|
+
endpoint: row.endpoint,
|
|
1645
|
+
page: row.page
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
tx();
|
|
1649
|
+
return resolveArtifact(ctx, { id: row.id });
|
|
1650
|
+
}
|
|
1651
|
+
function feedbackArtifact(ctx, ref, p) {
|
|
1652
|
+
if (p.verdict === -1 && (!p.comment || !p.comment.trim())) {
|
|
1653
|
+
throw new Error("\u8D1F\u53CD\u9988\u5FC5\u987B\u9644\u4E00\u53E5\u539F\u56E0,\u5426\u5219\u65E0\u6CD5\u6C89\u6DC0\u4E3A\u7ECF\u9A8C");
|
|
1654
|
+
}
|
|
1655
|
+
const row = refreshArtifact(ctx, ref, p.actor);
|
|
1656
|
+
const tx = ctx.db.transaction(() => {
|
|
1657
|
+
const result = ctx.db.prepare(
|
|
1658
|
+
`INSERT INTO artifact_feedback (artifact_id, task_id, verdict, comment, content_hash, actor)
|
|
1659
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1660
|
+
).run(row.id, p.taskId ?? null, p.verdict, p.comment ?? null, row.content_hash, p.actor);
|
|
1661
|
+
logEvent(ctx.db, {
|
|
1662
|
+
entityType: "artifact",
|
|
1663
|
+
entityId: row.id,
|
|
1664
|
+
event: "feedback",
|
|
1665
|
+
actor: p.actor,
|
|
1666
|
+
payload: { verdict: p.verdict, comment: p.comment ?? null },
|
|
1667
|
+
module: row.module,
|
|
1668
|
+
endpoint: row.endpoint,
|
|
1669
|
+
page: row.page
|
|
1670
|
+
});
|
|
1671
|
+
return result.lastInsertRowid;
|
|
1672
|
+
});
|
|
1673
|
+
const feedbackId = tx();
|
|
1674
|
+
let endorsed = false;
|
|
1675
|
+
if (row.kind === "prototype" && p.verdict === 1) {
|
|
1676
|
+
approveArtifact(ctx, { id: row.id }, p.actor, { via: "feedback" });
|
|
1677
|
+
endorsed = true;
|
|
1678
|
+
}
|
|
1679
|
+
return { feedbackId, endorsed };
|
|
1680
|
+
}
|
|
1681
|
+
function moveArtifact(ctx, { from, to, actor }) {
|
|
1682
|
+
const row = resolveArtifact(ctx, { path: from });
|
|
1683
|
+
const toRel = normalizeRelPath(ctx, to);
|
|
1684
|
+
if (!existsSync5(join6(ctx.root, toRel))) {
|
|
1685
|
+
throw new Error(`\u76EE\u6807\u6587\u4EF6\u4E0D\u5B58\u5728: ${toRel},\u8BF7\u5148\u79FB\u52A8\u78C1\u76D8\u6587\u4EF6\u518D\u6267\u884C move`);
|
|
1686
|
+
}
|
|
1687
|
+
const dup = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(toRel);
|
|
1688
|
+
if (dup && dup.id !== row.id) throw new Error(`\u76EE\u6807\u8DEF\u5F84\u5DF2\u88AB artifact #${dup.id} \u5360\u7528: ${toRel}`);
|
|
1689
|
+
const tx = ctx.db.transaction(() => {
|
|
1690
|
+
ctx.db.prepare("UPDATE artifacts SET path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(toRel, row.id);
|
|
1691
|
+
logEvent(ctx.db, {
|
|
1692
|
+
entityType: "artifact",
|
|
1693
|
+
entityId: row.id,
|
|
1694
|
+
event: "moved",
|
|
1695
|
+
actor,
|
|
1696
|
+
payload: { from: row.path, to: toRel },
|
|
1697
|
+
module: row.module,
|
|
1698
|
+
endpoint: row.endpoint,
|
|
1699
|
+
page: row.page
|
|
1700
|
+
});
|
|
1701
|
+
});
|
|
1702
|
+
tx();
|
|
1703
|
+
return refreshArtifact(ctx, { id: row.id }, actor);
|
|
1704
|
+
}
|
|
1705
|
+
function listArtifacts(ctx, f = {}) {
|
|
1706
|
+
let query = "SELECT * FROM artifacts WHERE 1=1";
|
|
1707
|
+
const params = [];
|
|
1708
|
+
if (f.module) {
|
|
1709
|
+
query += " AND module = ?";
|
|
1710
|
+
params.push(f.module);
|
|
1711
|
+
}
|
|
1712
|
+
if (f.endpoint) {
|
|
1713
|
+
query += " AND endpoint = ?";
|
|
1714
|
+
params.push(f.endpoint);
|
|
1715
|
+
}
|
|
1716
|
+
if (f.page) {
|
|
1717
|
+
query += " AND page = ?";
|
|
1718
|
+
params.push(f.page);
|
|
1719
|
+
}
|
|
1720
|
+
if (f.kind) {
|
|
1721
|
+
query += " AND kind = ?";
|
|
1722
|
+
params.push(f.kind);
|
|
1723
|
+
}
|
|
1724
|
+
query += " ORDER BY id DESC";
|
|
1725
|
+
const rows = ctx.db.prepare(query).all(...params);
|
|
1726
|
+
for (const row of rows) row.review_status = reviewStatus(row);
|
|
1727
|
+
return rows;
|
|
1728
|
+
}
|
|
1729
|
+
var init_artifact_commands = __esm({
|
|
1730
|
+
"core/commands/artifact.commands.ts"() {
|
|
1731
|
+
"use strict";
|
|
1732
|
+
init_derive();
|
|
1733
|
+
init_events();
|
|
1734
|
+
init_hash();
|
|
1735
|
+
init_kind();
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
|
|
1739
|
+
// core/commands/sync.command.ts
|
|
1740
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1741
|
+
import { join as join7 } from "node:path";
|
|
1742
|
+
function ownerRole(ctx, kind) {
|
|
1743
|
+
for (const [role, kinds] of Object.entries(ctx.config.roleProduces)) {
|
|
1744
|
+
if (kinds.includes(kind)) return role;
|
|
1745
|
+
}
|
|
1746
|
+
if (kind === "code") return "developer";
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
function hasOpenReview(ctx, artifactId) {
|
|
1750
|
+
const row = ctx.db.prepare(
|
|
1751
|
+
`SELECT COUNT(*) AS c FROM tasks t
|
|
1752
|
+
JOIN task_inputs ti ON ti.task_id = t.id
|
|
1753
|
+
WHERE t.type = 'review' AND t.status IN ('pending', 'in_progress') AND ti.artifact_id = ?`
|
|
1754
|
+
).get(artifactId);
|
|
1755
|
+
return row.c > 0;
|
|
1756
|
+
}
|
|
1757
|
+
function spawnReviews(ctx, source, reason) {
|
|
1758
|
+
if (hasOpenReview(ctx, source.id)) return 0;
|
|
1759
|
+
const registry = getKindRegistry(ctx.config);
|
|
1760
|
+
const downstream = ctx.db.prepare(`SELECT a.* FROM artifacts a JOIN artifact_edges e ON e.to_id = a.id WHERE e.from_id = ?`).all(source.id);
|
|
1761
|
+
const targets = /* @__PURE__ */ new Map();
|
|
1762
|
+
for (const child of downstream) {
|
|
1763
|
+
if (registry[child.kind]?.meta) continue;
|
|
1764
|
+
const role = ownerRole(ctx, child.kind);
|
|
1765
|
+
if (!role) continue;
|
|
1766
|
+
const key = `${role}|${child.endpoint ?? ""}|${child.module ?? source.module ?? ""}`;
|
|
1767
|
+
targets.set(key, { role, endpoint: child.endpoint, module: child.module ?? source.module });
|
|
1768
|
+
}
|
|
1769
|
+
let spawned = 0;
|
|
1770
|
+
const tx = ctx.db.transaction(() => {
|
|
1771
|
+
for (const t of targets.values()) {
|
|
1772
|
+
const result = ctx.db.prepare(
|
|
1773
|
+
`INSERT INTO tasks (module, role, endpoint, type, status, assignee, creator, content)
|
|
1774
|
+
VALUES (?, ?, ?, 'review', 'pending', ?, 'system', ?)`
|
|
1775
|
+
).run(t.module, t.role, t.endpoint, t.role, `[\u590D\u5BA1] \u4E0A\u6E38 ${source.path} ${reason},\u8BF7\u590D\u6838\u4F60\u57FA\u4E8E\u5B83\u7684\u4EA7\u51FA\u5E76\u5BF9\u9F50`);
|
|
1776
|
+
const taskId = result.lastInsertRowid;
|
|
1777
|
+
ctx.db.prepare("INSERT OR REPLACE INTO task_inputs (task_id, artifact_id, input_hash) VALUES (?, ?, ?)").run(taskId, source.id, source.content_hash);
|
|
1778
|
+
logEvent(ctx.db, {
|
|
1779
|
+
entityType: "task",
|
|
1780
|
+
entityId: taskId,
|
|
1781
|
+
event: "review_spawned",
|
|
1782
|
+
actor: "system",
|
|
1783
|
+
payload: { source: source.path, reason },
|
|
1784
|
+
module: t.module,
|
|
1785
|
+
endpoint: t.endpoint
|
|
1786
|
+
});
|
|
1787
|
+
spawned++;
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
tx();
|
|
1791
|
+
return spawned;
|
|
1792
|
+
}
|
|
1793
|
+
function syncArtifacts(ctx, actor = "sync") {
|
|
1794
|
+
const summary = { checked: 0, changed: 0, invalidated: 0, tombstoned: 0, reviewsSpawned: 0 };
|
|
1795
|
+
const artifacts = ctx.db.prepare("SELECT * FROM artifacts").all();
|
|
1796
|
+
for (const artifact of artifacts) {
|
|
1797
|
+
summary.checked++;
|
|
1798
|
+
try {
|
|
1799
|
+
const abs = join7(ctx.root, artifact.path);
|
|
1800
|
+
if (!existsSync6(abs)) {
|
|
1801
|
+
const already = ctx.db.prepare("SELECT COUNT(*) AS c FROM events WHERE entity_type='artifact' AND entity_id=? AND event='tombstoned'").get(artifact.id);
|
|
1802
|
+
if (already.c === 0) {
|
|
1803
|
+
logEvent(ctx.db, {
|
|
1804
|
+
entityType: "artifact",
|
|
1805
|
+
entityId: artifact.id,
|
|
1806
|
+
event: "tombstoned",
|
|
1807
|
+
actor,
|
|
1808
|
+
payload: { path: artifact.path },
|
|
1809
|
+
module: artifact.module,
|
|
1810
|
+
endpoint: artifact.endpoint,
|
|
1811
|
+
page: artifact.page
|
|
1812
|
+
});
|
|
1813
|
+
summary.tombstoned++;
|
|
1814
|
+
summary.reviewsSpawned += spawnReviews(ctx, artifact, "\u5DF2\u4ECE\u78C1\u76D8\u5220\u9664");
|
|
1815
|
+
}
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
const before = artifact.content_hash;
|
|
1819
|
+
const wasApproved = reviewStatus(artifact) === "approved";
|
|
1820
|
+
const after = refreshArtifact(ctx, { id: artifact.id }, actor);
|
|
1821
|
+
if (after.content_hash === before) continue;
|
|
1822
|
+
summary.changed++;
|
|
1823
|
+
if (wasApproved && reviewStatus(after) === "invalidated") {
|
|
1824
|
+
summary.invalidated++;
|
|
1825
|
+
summary.reviewsSpawned += spawnReviews(ctx, after, "\u5DF2\u53D8\u66F4(\u5BA1\u6279\u5931\u6548)");
|
|
1826
|
+
}
|
|
1827
|
+
} catch {
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return summary;
|
|
1831
|
+
}
|
|
1832
|
+
function detectOrphanCommit(ctx) {
|
|
1833
|
+
if (ctx.config.git.taskTrailer !== "on") return { orphan: false, hits: [] };
|
|
1834
|
+
const head = headCommitInfo(ctx.root, ctx.config.git.trailerKey);
|
|
1835
|
+
if (!head || head.trailer) return { orphan: false, hits: [] };
|
|
1836
|
+
const registry = getKindRegistry(ctx.config);
|
|
1837
|
+
const managed = ctx.db.prepare("SELECT * FROM artifacts").all().filter((a) => {
|
|
1838
|
+
const spec = registry[a.kind];
|
|
1839
|
+
return spec && !spec.meta && (spec.approval === "human" || a.kind === "code");
|
|
1840
|
+
});
|
|
1841
|
+
const hits = /* @__PURE__ */ new Set();
|
|
1842
|
+
for (const file of head.files) {
|
|
1843
|
+
for (const a of managed) {
|
|
1844
|
+
if (file === a.path || file.startsWith(a.path + "/")) {
|
|
1845
|
+
hits.add(file);
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
if (hits.size === 0) return { orphan: false, hits: [] };
|
|
1851
|
+
const already = ctx.db.prepare("SELECT COUNT(*) AS c FROM events WHERE event = 'orphan_commit' AND payload LIKE ?").get(`%${head.hash}%`);
|
|
1852
|
+
if (already.c === 0) {
|
|
1853
|
+
const tx = ctx.db.transaction(() => {
|
|
1854
|
+
logEvent(ctx.db, {
|
|
1855
|
+
entityType: "artifact",
|
|
1856
|
+
entityId: 0,
|
|
1857
|
+
event: "orphan_commit",
|
|
1858
|
+
actor: "post-commit",
|
|
1859
|
+
payload: { commit: head.hash, hits: [...hits].slice(0, 20) }
|
|
1860
|
+
});
|
|
1861
|
+
});
|
|
1862
|
+
tx();
|
|
1863
|
+
}
|
|
1864
|
+
return { orphan: true, hits: [...hits] };
|
|
1865
|
+
}
|
|
1866
|
+
function disputeArtifact(ctx, ref, actor, reason) {
|
|
1867
|
+
if (!reason?.trim()) throw new Error("\u5F02\u8BAE\u5FC5\u987B\u8BF4\u660E\u7406\u7531");
|
|
1868
|
+
const artifact = resolveArtifact(ctx, ref);
|
|
1869
|
+
const tx = ctx.db.transaction(() => {
|
|
1870
|
+
logEvent(ctx.db, {
|
|
1871
|
+
entityType: "artifact",
|
|
1872
|
+
entityId: artifact.id,
|
|
1873
|
+
event: "dispute",
|
|
1874
|
+
actor,
|
|
1875
|
+
payload: { reason },
|
|
1876
|
+
module: artifact.module,
|
|
1877
|
+
endpoint: artifact.endpoint,
|
|
1878
|
+
page: artifact.page
|
|
1879
|
+
});
|
|
1880
|
+
});
|
|
1881
|
+
tx();
|
|
1882
|
+
}
|
|
1883
|
+
var init_sync_command = __esm({
|
|
1884
|
+
"core/commands/sync.command.ts"() {
|
|
1885
|
+
"use strict";
|
|
1886
|
+
init_derive();
|
|
1887
|
+
init_events();
|
|
1888
|
+
init_git();
|
|
1889
|
+
init_kind();
|
|
1890
|
+
init_artifact_commands();
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
// core/commands/task.commands.ts
|
|
1895
|
+
function createTask(ctx, p) {
|
|
1896
|
+
const type = p.type ?? "build";
|
|
1897
|
+
if (!VALID_TYPES.includes(type)) throw new Error(`\u65E0\u6548\u7684\u4EFB\u52A1\u7C7B\u578B: ${type},\u53EF\u9009\u503C: ${VALID_TYPES.join(", ")}`);
|
|
1898
|
+
if (!VALID_ROLES.includes(p.role)) throw new Error(`\u65E0\u6548\u7684\u89D2\u8272: ${p.role},\u53EF\u9009\u503C: ${VALID_ROLES.join(", ")}`);
|
|
1899
|
+
const module = normalizeModule(p.module, ctx.config);
|
|
1900
|
+
const tx = ctx.db.transaction(() => {
|
|
1901
|
+
const result = ctx.db.prepare(
|
|
1902
|
+
`INSERT INTO tasks (module, role, endpoint, page, type, assignee, creator, content, external_ref)
|
|
1903
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1904
|
+
).run(module, p.role, p.endpoint ?? null, p.page ?? null, type, p.assignee ?? null, p.creator, p.content ?? null, p.externalRef ?? null);
|
|
1905
|
+
const id = result.lastInsertRowid;
|
|
1906
|
+
logEvent(ctx.db, {
|
|
1907
|
+
entityType: "task",
|
|
1908
|
+
entityId: id,
|
|
1909
|
+
event: "created",
|
|
1910
|
+
actor: p.creator,
|
|
1911
|
+
payload: { role: p.role, type },
|
|
1912
|
+
module,
|
|
1913
|
+
endpoint: p.endpoint ?? null,
|
|
1914
|
+
page: p.page ?? null
|
|
1915
|
+
});
|
|
1916
|
+
return id;
|
|
1917
|
+
});
|
|
1918
|
+
return tx();
|
|
1919
|
+
}
|
|
1920
|
+
function getTaskRow(ctx, id) {
|
|
1921
|
+
const task = ctx.db.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
1922
|
+
if (!task) throw new Error(`\u4EFB\u52A1 #${id} \u4E0D\u5B58\u5728`);
|
|
1923
|
+
return task;
|
|
1924
|
+
}
|
|
1925
|
+
function claimTask(ctx, { id, assignee }) {
|
|
1926
|
+
const task = getTaskRow(ctx, id);
|
|
1927
|
+
const { warnings, inputs } = validateClaim(ctx, task);
|
|
1928
|
+
const claimCommit = gitHead(ctx.root);
|
|
1929
|
+
const tx = ctx.db.transaction(() => {
|
|
1930
|
+
const result = ctx.db.prepare(
|
|
1931
|
+
`UPDATE tasks SET assignee = ?, status = 'in_progress', claim_commit = ?, updated_at = CURRENT_TIMESTAMP
|
|
1932
|
+
WHERE id = ? AND status = 'pending' AND (assignee IS NULL OR assignee = ?)`
|
|
1933
|
+
).run(assignee, claimCommit, id, assignee);
|
|
1934
|
+
if (result.changes === 0) {
|
|
1935
|
+
throw new Error(`\u4EFB\u52A1 #${id} \u65E0\u6CD5\u9886\u53D6:\u5DF2\u88AB\u4ED6\u4EBA\u9886\u53D6\u6216\u72B6\u6001\u4E0D\u662F pending(\u5F53\u524D ${task.status}${task.assignee ? `,\u6267\u884C\u4EBA ${task.assignee}` : ""})`);
|
|
1936
|
+
}
|
|
1937
|
+
const snapshot = ctx.db.prepare(
|
|
1938
|
+
"INSERT OR REPLACE INTO task_inputs (task_id, artifact_id, input_hash) VALUES (?, ?, ?)"
|
|
1939
|
+
);
|
|
1940
|
+
for (const a of inputs) snapshot.run(id, a.id, a.content_hash);
|
|
1941
|
+
logEvent(ctx.db, {
|
|
1942
|
+
entityType: "task",
|
|
1943
|
+
entityId: id,
|
|
1944
|
+
event: "claimed",
|
|
1945
|
+
actor: assignee,
|
|
1946
|
+
payload: { warnings, inputCount: inputs.length },
|
|
1947
|
+
module: task.module,
|
|
1948
|
+
endpoint: task.endpoint,
|
|
1949
|
+
page: task.page
|
|
1950
|
+
});
|
|
1951
|
+
});
|
|
1952
|
+
tx();
|
|
1953
|
+
return { id, warnings };
|
|
1954
|
+
}
|
|
1955
|
+
function updateTask(ctx, { id, status, operator, force = false }) {
|
|
1956
|
+
const task = getTaskRow(ctx, id);
|
|
1957
|
+
if (!VALID_STATUS.includes(status)) {
|
|
1958
|
+
throw new Error(`\u65E0\u6548\u7684\u72B6\u6001: ${status},\u53EF\u9009\u503C: ${VALID_STATUS.join(", ")}`);
|
|
1959
|
+
}
|
|
1960
|
+
if (task.assignee !== null && task.assignee !== operator) {
|
|
1961
|
+
throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u66F4\u65B0\u4EFB\u52A1\u72B6\u6001`);
|
|
1962
|
+
}
|
|
1963
|
+
const { warnings } = validateComplete(ctx, task, status, { force });
|
|
1964
|
+
const tx = ctx.db.transaction(() => {
|
|
1965
|
+
ctx.db.prepare("UPDATE tasks SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, id);
|
|
1966
|
+
logEvent(ctx.db, {
|
|
1967
|
+
entityType: "task",
|
|
1968
|
+
entityId: id,
|
|
1969
|
+
event: status === "completed" ? "completed" : "status_changed",
|
|
1970
|
+
actor: operator,
|
|
1971
|
+
payload: { status, warnings, ...force ? { force: true } : {} },
|
|
1972
|
+
module: task.module,
|
|
1973
|
+
endpoint: task.endpoint,
|
|
1974
|
+
page: task.page
|
|
1975
|
+
});
|
|
1976
|
+
});
|
|
1977
|
+
tx();
|
|
1978
|
+
if (task.type === "hotfix" && status === "completed") {
|
|
1979
|
+
warnings.push(...checkContractTouch(ctx, task, operator));
|
|
1980
|
+
}
|
|
1981
|
+
if (task.type === "rework" && status === "completed") {
|
|
1982
|
+
const qaId = spawnFollowupQa(ctx, task);
|
|
1983
|
+
warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${qaId}`);
|
|
1984
|
+
}
|
|
1985
|
+
if (status === "completed" && task.external_ref?.startsWith("gh#")) {
|
|
1986
|
+
const closed = closeLinkedIssue(ctx, task.external_ref, task.id);
|
|
1987
|
+
warnings.push(closed ? `[issue] \u5DF2\u5173\u95ED ${task.external_ref}` : `[issue] ${task.external_ref} \u5173\u95ED\u5931\u8D25(gh \u4E0D\u53EF\u7528),\u8BF7\u624B\u52A8\u5904\u7406`);
|
|
1988
|
+
}
|
|
1989
|
+
return { id, warnings };
|
|
1990
|
+
}
|
|
1991
|
+
function spawnFollowupQa(ctx, reworkTask) {
|
|
1992
|
+
const tx = ctx.db.transaction(() => {
|
|
1993
|
+
const result = ctx.db.prepare(
|
|
1994
|
+
`INSERT INTO tasks (module, role, endpoint, page, type, status, assignee, creator, content)
|
|
1995
|
+
VALUES (?, 'qa', ?, ?, 'qa', 'pending', 'qa', 'system', ?)`
|
|
1996
|
+
).run(reworkTask.module, reworkTask.endpoint, reworkTask.page, `[\u590D\u9A8C] rework #${reworkTask.id} \u5DF2\u5B8C\u6210,\u8BF7\u91CD\u65B0\u9A8C\u6536`);
|
|
1997
|
+
const qaId = result.lastInsertRowid;
|
|
1998
|
+
logEvent(ctx.db, {
|
|
1999
|
+
entityType: "task",
|
|
2000
|
+
entityId: qaId,
|
|
2001
|
+
event: "qa_respawned",
|
|
2002
|
+
actor: "system",
|
|
2003
|
+
payload: { rework: reworkTask.id },
|
|
2004
|
+
module: reworkTask.module,
|
|
2005
|
+
endpoint: reworkTask.endpoint,
|
|
2006
|
+
page: reworkTask.page
|
|
2007
|
+
});
|
|
2008
|
+
return qaId;
|
|
2009
|
+
});
|
|
2010
|
+
return tx();
|
|
2011
|
+
}
|
|
2012
|
+
function checkContractTouch(ctx, task, operator) {
|
|
2013
|
+
const warnings = [];
|
|
2014
|
+
const touched = ctx.config.git.taskTrailer === "on" && task.claim_commit ? touchedByTaskTrailer(ctx.root, task.claim_commit, ctx.config.git.trailerKey, task.id) : touchedSince(ctx.root, task.claim_commit);
|
|
2015
|
+
if (touched.length === 0) return warnings;
|
|
2016
|
+
const kinds = contractKinds(ctx.config);
|
|
2017
|
+
const contracts = ctx.db.prepare(`SELECT * FROM artifacts WHERE kind IN (${kinds.map(() => "?").join(",")})`).all(...kinds);
|
|
2018
|
+
for (const contract of contracts) {
|
|
2019
|
+
const hit = touched.some((t) => t === contract.path || t.startsWith(contract.path + "/"));
|
|
2020
|
+
if (!hit) continue;
|
|
2021
|
+
const open = ctx.db.prepare(
|
|
2022
|
+
`SELECT COUNT(*) AS c FROM tasks t JOIN task_inputs ti ON ti.task_id = t.id
|
|
2023
|
+
WHERE t.type = 'review' AND t.status IN ('pending','in_progress') AND ti.artifact_id = ?`
|
|
2024
|
+
).get(contract.id);
|
|
2025
|
+
if (open.c > 0) continue;
|
|
2026
|
+
const role = ownerRole(ctx, contract.kind) ?? "architect";
|
|
2027
|
+
const tx = ctx.db.transaction(() => {
|
|
2028
|
+
const result = ctx.db.prepare(
|
|
2029
|
+
`INSERT INTO tasks (module, role, endpoint, type, status, assignee, creator, content)
|
|
2030
|
+
VALUES (?, ?, ?, 'review', 'pending', ?, 'system', ?)`
|
|
2031
|
+
).run(
|
|
2032
|
+
contract.module,
|
|
2033
|
+
role,
|
|
2034
|
+
contract.endpoint,
|
|
2035
|
+
role,
|
|
2036
|
+
`[\u5951\u7EA6\u89E6\u78B0] hotfix #${task.id} \u89E6\u78B0\u4E86\u5951\u7EA6 ${contract.path},\u8BF7\u8865\u6587\u6863\u5E76\u91CD\u65B0\u9001\u5BA1`
|
|
2037
|
+
);
|
|
2038
|
+
const reviewId = result.lastInsertRowid;
|
|
2039
|
+
ctx.db.prepare("INSERT OR REPLACE INTO task_inputs (task_id, artifact_id, input_hash) VALUES (?, ?, ?)").run(reviewId, contract.id, contract.content_hash);
|
|
2040
|
+
logEvent(ctx.db, {
|
|
2041
|
+
entityType: "task",
|
|
2042
|
+
entityId: reviewId,
|
|
2043
|
+
event: "contract_touched",
|
|
2044
|
+
actor: operator,
|
|
2045
|
+
payload: { hotfix: task.id, contract: contract.path },
|
|
2046
|
+
module: contract.module,
|
|
2047
|
+
endpoint: contract.endpoint
|
|
2048
|
+
});
|
|
2049
|
+
});
|
|
2050
|
+
tx();
|
|
2051
|
+
warnings.push(`[\u5951\u7EA6\u89E6\u78B0] ${contract.path} \u88AB\u672C hotfix \u4FEE\u6539,\u5DF2\u81EA\u52A8\u6D3E\u8865\u6587\u6863 review \u4EFB\u52A1`);
|
|
2052
|
+
}
|
|
2053
|
+
return warnings;
|
|
2054
|
+
}
|
|
2055
|
+
function removeTask(ctx, { id, operator, force = false }) {
|
|
2056
|
+
const task = getTaskRow(ctx, id);
|
|
2057
|
+
if (!force && task.creator !== operator) {
|
|
2058
|
+
throw new Error(`\u53EA\u6709\u521B\u5EFA\u4EBA(${task.creator})\u624D\u80FD\u5220\u9664\u4EFB\u52A1`);
|
|
2059
|
+
}
|
|
2060
|
+
const tx = ctx.db.transaction(() => {
|
|
2061
|
+
logEvent(ctx.db, {
|
|
2062
|
+
entityType: "task",
|
|
2063
|
+
entityId: id,
|
|
2064
|
+
event: "removed",
|
|
2065
|
+
actor: operator,
|
|
2066
|
+
payload: { force },
|
|
2067
|
+
module: task.module,
|
|
2068
|
+
endpoint: task.endpoint,
|
|
2069
|
+
page: task.page
|
|
2070
|
+
});
|
|
2071
|
+
ctx.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
2072
|
+
});
|
|
2073
|
+
tx();
|
|
2074
|
+
}
|
|
2075
|
+
function addTaskInput(ctx, { id, path, operator }) {
|
|
2076
|
+
const task = getTaskRow(ctx, id);
|
|
2077
|
+
const artifact = ctx.db.prepare("SELECT * FROM artifacts WHERE path = ?").get(normalizeRelPath(ctx, path));
|
|
2078
|
+
if (!artifact) throw new Error(`\u4EA7\u7269\u672A\u767B\u8BB0: ${path},\u65E0\u6CD5\u7533\u62A5\u4F9D\u8D56`);
|
|
2079
|
+
const tx = ctx.db.transaction(() => {
|
|
2080
|
+
ctx.db.prepare("INSERT OR REPLACE INTO task_inputs (task_id, artifact_id, input_hash) VALUES (?, ?, ?)").run(id, artifact.id, artifact.content_hash);
|
|
2081
|
+
logEvent(ctx.db, {
|
|
2082
|
+
entityType: "task",
|
|
2083
|
+
entityId: id,
|
|
2084
|
+
event: "input_added",
|
|
2085
|
+
actor: operator,
|
|
2086
|
+
payload: { path: artifact.path, artifactId: artifact.id },
|
|
2087
|
+
module: task.module,
|
|
2088
|
+
endpoint: task.endpoint,
|
|
2089
|
+
page: task.page
|
|
2090
|
+
});
|
|
2091
|
+
});
|
|
2092
|
+
tx();
|
|
2093
|
+
}
|
|
2094
|
+
function recordNote(ctx, { id, content, operator }) {
|
|
2095
|
+
const task = getTaskRow(ctx, id);
|
|
2096
|
+
const tx = ctx.db.transaction(() => {
|
|
2097
|
+
logEvent(ctx.db, {
|
|
2098
|
+
entityType: "task",
|
|
2099
|
+
entityId: id,
|
|
2100
|
+
event: "note",
|
|
2101
|
+
actor: operator,
|
|
2102
|
+
payload: { content },
|
|
2103
|
+
module: task.module,
|
|
2104
|
+
endpoint: task.endpoint,
|
|
2105
|
+
page: task.page
|
|
2106
|
+
});
|
|
2107
|
+
});
|
|
2108
|
+
tx();
|
|
2109
|
+
}
|
|
2110
|
+
function listTasks(ctx, f = {}) {
|
|
2111
|
+
let query = "SELECT * FROM tasks WHERE 1=1";
|
|
2112
|
+
const params = [];
|
|
2113
|
+
if (f.status) {
|
|
2114
|
+
query += " AND status = ?";
|
|
2115
|
+
params.push(f.status);
|
|
2116
|
+
}
|
|
2117
|
+
if (f.assignee) {
|
|
2118
|
+
query += " AND assignee = ?";
|
|
2119
|
+
params.push(f.assignee);
|
|
2120
|
+
}
|
|
2121
|
+
if (f.module) {
|
|
2122
|
+
query += " AND module LIKE ?";
|
|
2123
|
+
params.push(`%${f.module}%`);
|
|
2124
|
+
}
|
|
2125
|
+
if (f.role) {
|
|
2126
|
+
query += " AND role = ?";
|
|
2127
|
+
params.push(f.role);
|
|
2128
|
+
}
|
|
2129
|
+
if (f.endpoint) {
|
|
2130
|
+
query += " AND endpoint = ?";
|
|
2131
|
+
params.push(f.endpoint);
|
|
2132
|
+
}
|
|
2133
|
+
if (f.type) {
|
|
2134
|
+
query += " AND type = ?";
|
|
2135
|
+
params.push(f.type);
|
|
2136
|
+
}
|
|
2137
|
+
query += " ORDER BY id DESC";
|
|
2138
|
+
const rows = ctx.db.prepare(query).all(...params);
|
|
2139
|
+
if (f.withStale) {
|
|
2140
|
+
for (const row of rows) row.stale = taskStaleness(ctx.db, row.id).stale;
|
|
2141
|
+
}
|
|
2142
|
+
return rows;
|
|
2143
|
+
}
|
|
2144
|
+
function getTaskDetail(ctx, id) {
|
|
2145
|
+
const task = getTaskRow(ctx, id);
|
|
2146
|
+
const events = listEvents(ctx.db, { entityType: "task", entityId: id });
|
|
2147
|
+
const outputs = ctx.db.prepare(
|
|
2148
|
+
`SELECT a.* FROM artifacts a JOIN task_outputs t ON t.artifact_id = a.id WHERE t.task_id = ? ORDER BY a.id ASC`
|
|
2149
|
+
).all(id);
|
|
2150
|
+
if (outputs.length === 0 && task.module) {
|
|
2151
|
+
const fallback = ctx.db.prepare(`SELECT * FROM artifacts WHERE module = ? AND endpoint = ? ORDER BY id ASC`).all(task.module, task.endpoint);
|
|
2152
|
+
outputs.push(...fallback);
|
|
2153
|
+
}
|
|
2154
|
+
return { task, events, outputs, staleness: taskStaleness(ctx.db, id) };
|
|
2155
|
+
}
|
|
2156
|
+
var VALID_STATUS, VALID_TYPES, VALID_ROLES;
|
|
2157
|
+
var init_task_commands = __esm({
|
|
2158
|
+
"core/commands/task.commands.ts"() {
|
|
2159
|
+
"use strict";
|
|
2160
|
+
init_derive();
|
|
2161
|
+
init_events();
|
|
2162
|
+
init_gates();
|
|
2163
|
+
init_git();
|
|
2164
|
+
init_gh();
|
|
2165
|
+
init_kind();
|
|
2166
|
+
init_artifact_commands();
|
|
2167
|
+
init_sync_command();
|
|
2168
|
+
VALID_STATUS = ["pending", "in_progress", "completed", "cancelled"];
|
|
2169
|
+
VALID_TYPES = ["build", "review", "qa", "hotfix", "baseline", "legacy", "rework"];
|
|
2170
|
+
VALID_ROLES = ["product-manager", "architect", "designer", "developer", "qa"];
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
// core/commands/graph.command.ts
|
|
2175
|
+
function graphModule(ctx, module) {
|
|
2176
|
+
const artifacts = ctx.db.prepare("SELECT * FROM artifacts WHERE module = ? OR (module IS NULL AND kind IN ('baseline','project','roles','glossary')) ORDER BY id").all(module);
|
|
2177
|
+
if (artifacts.length === 0) return `%% \u6A21\u5757 ${module} \u6CA1\u6709\u767B\u8BB0\u4EFB\u4F55\u4EA7\u7269`;
|
|
2178
|
+
const ids = artifacts.map((a) => a.id);
|
|
2179
|
+
const edges = ctx.db.prepare(
|
|
2180
|
+
`SELECT from_id, to_id FROM artifact_edges
|
|
2181
|
+
WHERE from_id IN (${ids.map(() => "?").join(",")}) AND to_id IN (${ids.map(() => "?").join(",")})`
|
|
2182
|
+
).all(...ids, ...ids);
|
|
2183
|
+
const lines = ["flowchart TD"];
|
|
2184
|
+
const label = (a) => {
|
|
2185
|
+
const name = a.path.split("/").pop() ?? a.path;
|
|
2186
|
+
const suffix = a.endpoint ? ` (${a.endpoint}${a.page ? `/${a.page}` : ""})` : "";
|
|
2187
|
+
return `${name}${suffix}`.replace(/["[\]]/g, "");
|
|
2188
|
+
};
|
|
2189
|
+
KIND_TIERS.forEach((kinds, i) => {
|
|
2190
|
+
const tierArtifacts = artifacts.filter((a) => kinds.includes(a.kind));
|
|
2191
|
+
if (tierArtifacts.length === 0) return;
|
|
2192
|
+
lines.push(` subgraph T${i}["${TIER_LABELS[i]}"]`);
|
|
2193
|
+
for (const a of tierArtifacts) {
|
|
2194
|
+
lines.push(` a${a.id}["${label(a)}"]:::${reviewStatus(a)}`);
|
|
2195
|
+
}
|
|
2196
|
+
lines.push(" end");
|
|
2197
|
+
});
|
|
2198
|
+
const inTiers = new Set(artifacts.filter((a) => KIND_TIERS.flat().includes(a.kind)).map((a) => a.id));
|
|
2199
|
+
const others = artifacts.filter((a) => !inTiers.has(a.id));
|
|
2200
|
+
for (const a of others) {
|
|
2201
|
+
lines.push(` a${a.id}["${label(a)}"]:::${reviewStatus(a)}`);
|
|
2202
|
+
}
|
|
2203
|
+
for (const e of edges) {
|
|
2204
|
+
lines.push(` a${e.from_id} --> a${e.to_id}`);
|
|
2205
|
+
}
|
|
2206
|
+
lines.push(" classDef approved fill:#e1f5ee,stroke:#0f6e56");
|
|
2207
|
+
lines.push(" classDef draft fill:#f1efe8,stroke:#888780");
|
|
2208
|
+
lines.push(" classDef pending fill:#faeeda,stroke:#ba7517");
|
|
2209
|
+
lines.push(" classDef invalidated fill:#fcebeb,stroke:#a32d2d");
|
|
2210
|
+
return lines.join("\n");
|
|
2211
|
+
}
|
|
2212
|
+
var TIER_LABELS;
|
|
2213
|
+
var init_graph_command = __esm({
|
|
2214
|
+
"core/commands/graph.command.ts"() {
|
|
2215
|
+
"use strict";
|
|
2216
|
+
init_derive();
|
|
2217
|
+
init_kind();
|
|
2218
|
+
TIER_LABELS = ["\u5951\u7EA6\u57FA\u7EBF", "\u4E1A\u52A1\u6D41\u7A0B", "\u6A21\u5757 PRD", "\u9875\u9762 PRD", "\u67B6\u6784/\u8BBE\u8BA1\u7CFB\u7EDF", "\u8BBE\u8BA1\u63D0\u793A\u8BCD", "\u539F\u578B/\u9A8C\u6536", "\u4EE3\u7801"];
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
// core/commands/migrate-legacy.command.ts
|
|
2223
|
+
import Database2 from "better-sqlite3";
|
|
2224
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2225
|
+
import { join as join8 } from "node:path";
|
|
2226
|
+
function migrateLegacy(ctx, legacyDbPath) {
|
|
2227
|
+
const relPath = legacyDbPath ?? ctx.config.legacyDb;
|
|
2228
|
+
const absPath = join8(ctx.root, relPath);
|
|
2229
|
+
if (!existsSync7(absPath)) throw new Error(`\u65E7\u5E93\u4E0D\u5B58\u5728: ${relPath}`);
|
|
2230
|
+
const existing = ctx.db.prepare("SELECT COUNT(*) AS c FROM tasks WHERE type = 'legacy'").get();
|
|
2231
|
+
if (existing.c > 0) throw new Error(`\u5DF2\u8FC1\u79FB\u8FC7(\u5B58\u5728 ${existing.c} \u6761 legacy \u4EFB\u52A1),\u4E0D\u53EF\u91CD\u590D\u8FC1\u79FB`);
|
|
2232
|
+
const legacy = new Database2(absPath, { readonly: true, fileMustExist: true });
|
|
2233
|
+
const oldTasks = legacy.prepare("SELECT * FROM tasks ORDER BY id").all();
|
|
2234
|
+
const oldOutputs = legacy.prepare("SELECT * FROM task_outputs ORDER BY id").all();
|
|
2235
|
+
const oldRecords = legacy.prepare("SELECT * FROM task_records ORDER BY id").all();
|
|
2236
|
+
legacy.close();
|
|
2237
|
+
const summary = { tasks: 0, artifacts: 0, linkedOutputs: 0, missingFiles: [], notes: 0 };
|
|
2238
|
+
const tx = ctx.db.transaction(() => {
|
|
2239
|
+
const insertTask = ctx.db.prepare(
|
|
2240
|
+
`INSERT INTO tasks (id, module, role, endpoint, page, type, status, assignee, creator, content, created_at, updated_at)
|
|
2241
|
+
VALUES (?, ?, ?, ?, ?, 'legacy', ?, ?, ?, ?, ?, ?)`
|
|
2242
|
+
);
|
|
2243
|
+
for (const t of oldTasks) {
|
|
2244
|
+
insertTask.run(
|
|
2245
|
+
t.id,
|
|
2246
|
+
normalizeModule(t.module, ctx.config),
|
|
2247
|
+
t.role,
|
|
2248
|
+
t.endpoint,
|
|
2249
|
+
t.page,
|
|
2250
|
+
t.status,
|
|
2251
|
+
t.assignee,
|
|
2252
|
+
t.creator,
|
|
2253
|
+
t.content,
|
|
2254
|
+
t.created_at,
|
|
2255
|
+
t.updated_at
|
|
2256
|
+
);
|
|
2257
|
+
summary.tasks++;
|
|
2258
|
+
}
|
|
2259
|
+
const insertArtifact = ctx.db.prepare(
|
|
2260
|
+
`INSERT INTO artifacts (kind, module, endpoint, page, path, content_hash, created_at)
|
|
2261
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
2262
|
+
);
|
|
2263
|
+
const findTask = ctx.db.prepare(
|
|
2264
|
+
`SELECT id FROM tasks
|
|
2265
|
+
WHERE type = 'legacy' AND role = ? AND endpoint = ? AND module IS ?
|
|
2266
|
+
AND (? IS NULL OR page IS ?)
|
|
2267
|
+
ORDER BY id DESC LIMIT 1`
|
|
2268
|
+
);
|
|
2269
|
+
const linkOutput = ctx.db.prepare("INSERT OR IGNORE INTO task_outputs (task_id, artifact_id) VALUES (?, ?)");
|
|
2270
|
+
for (const o of oldOutputs) {
|
|
2271
|
+
const relFile = o.file_path.replace(/\\/g, "/");
|
|
2272
|
+
const module = normalizeModule(o.module, ctx.config);
|
|
2273
|
+
const hash = hashPath(join8(ctx.root, relFile));
|
|
2274
|
+
if (hash === null) summary.missingFiles.push(relFile);
|
|
2275
|
+
const dup = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(relFile);
|
|
2276
|
+
if (dup) continue;
|
|
2277
|
+
const result = insertArtifact.run(
|
|
2278
|
+
inferKind(relFile, ctx.config),
|
|
2279
|
+
module,
|
|
2280
|
+
o.endpoint,
|
|
2281
|
+
o.page,
|
|
2282
|
+
relFile,
|
|
2283
|
+
hash ?? "",
|
|
2284
|
+
o.created_at
|
|
2285
|
+
);
|
|
2286
|
+
const artifactId = result.lastInsertRowid;
|
|
2287
|
+
summary.artifacts++;
|
|
2288
|
+
const match = findTask.get(o.role, o.endpoint, module, o.page, o.page);
|
|
2289
|
+
if (match) {
|
|
2290
|
+
linkOutput.run(match.id, artifactId);
|
|
2291
|
+
summary.linkedOutputs++;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const taskIds = new Set(oldTasks.map((t) => t.id));
|
|
2295
|
+
for (const r of oldRecords) {
|
|
2296
|
+
if (!taskIds.has(r.task_id)) continue;
|
|
2297
|
+
logEvent(ctx.db, {
|
|
2298
|
+
entityType: "task",
|
|
2299
|
+
entityId: r.task_id,
|
|
2300
|
+
event: "note",
|
|
2301
|
+
actor: r.operator ?? "unknown",
|
|
2302
|
+
payload: { content: r.content, migrated: true },
|
|
2303
|
+
createdAt: r.created_at
|
|
2304
|
+
});
|
|
2305
|
+
summary.notes++;
|
|
2306
|
+
}
|
|
2307
|
+
logEvent(ctx.db, {
|
|
2308
|
+
entityType: "task",
|
|
2309
|
+
entityId: 0,
|
|
2310
|
+
event: "migrated",
|
|
2311
|
+
actor: "system",
|
|
2312
|
+
payload: { ...summary, source: relPath }
|
|
2313
|
+
});
|
|
2314
|
+
});
|
|
2315
|
+
tx();
|
|
2316
|
+
return summary;
|
|
2317
|
+
}
|
|
2318
|
+
var init_migrate_legacy_command = __esm({
|
|
2319
|
+
"core/commands/migrate-legacy.command.ts"() {
|
|
2320
|
+
"use strict";
|
|
2321
|
+
init_events();
|
|
2322
|
+
init_hash();
|
|
2323
|
+
init_kind();
|
|
2324
|
+
}
|
|
2325
|
+
});
|
|
2326
|
+
|
|
2327
|
+
// core/commands/meta.command.ts
|
|
2328
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
2329
|
+
import { join as join9 } from "node:path";
|
|
2330
|
+
function metaSources(config) {
|
|
2331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2332
|
+
const sources = [];
|
|
2333
|
+
const add = (s) => {
|
|
2334
|
+
if (!seen.has(s.dir)) {
|
|
2335
|
+
seen.add(s.dir);
|
|
2336
|
+
sources.push(s);
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
for (const a of resolvePlatforms(config.platforms)) {
|
|
2340
|
+
add({ dir: a.agentsDir, filter: (n) => n.endsWith(".md") || n.endsWith(".toml") });
|
|
2341
|
+
add({ dir: a.skillsDir, filter: (n) => n === "SKILL.md", recursive: true });
|
|
2342
|
+
if (a.hooksScanDir) add({ dir: a.hooksScanDir, recursive: true });
|
|
2343
|
+
}
|
|
2344
|
+
add({ dir: "docs/workbench", filter: (n) => n === "PLAN.md" });
|
|
2345
|
+
return sources;
|
|
2346
|
+
}
|
|
2347
|
+
function collectFiles(root, dir, filter, recursive) {
|
|
2348
|
+
const abs = join9(root, dir);
|
|
2349
|
+
if (!existsSync8(abs)) return [];
|
|
2350
|
+
const results = [];
|
|
2351
|
+
const walk = (d, rel) => {
|
|
2352
|
+
for (const name of readdirSync3(d).sort()) {
|
|
2353
|
+
const full = join9(d, name);
|
|
2354
|
+
const relPath = `${rel}/${name}`;
|
|
2355
|
+
if (statSync4(full).isDirectory()) {
|
|
2356
|
+
if (recursive) walk(full, relPath);
|
|
2357
|
+
} else if (!filter || filter(name)) {
|
|
2358
|
+
results.push(relPath);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
walk(abs, dir);
|
|
2363
|
+
return results;
|
|
2364
|
+
}
|
|
2365
|
+
function registerMetaArtifacts(ctx, actor = "system") {
|
|
2366
|
+
const result = { registered: [], skipped: [] };
|
|
2367
|
+
const files = [];
|
|
2368
|
+
for (const src of metaSources(ctx.config)) {
|
|
2369
|
+
files.push(...collectFiles(ctx.root, src.dir, src.filter, src.recursive));
|
|
2370
|
+
}
|
|
2371
|
+
const insert = ctx.db.prepare(
|
|
2372
|
+
`INSERT INTO artifacts (kind, module, endpoint, page, path, content_hash) VALUES (?, NULL, NULL, NULL, ?, ?)`
|
|
2373
|
+
);
|
|
2374
|
+
const exists = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?");
|
|
2375
|
+
const tx = ctx.db.transaction(() => {
|
|
2376
|
+
for (const file of files) {
|
|
2377
|
+
const relPath = normalizeRelPath(ctx, file);
|
|
2378
|
+
if (exists.get(relPath)) {
|
|
2379
|
+
result.skipped.push(relPath);
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
const kind = inferKind(relPath, ctx.config);
|
|
2383
|
+
const hash = hashPath(join9(ctx.root, relPath));
|
|
2384
|
+
if (hash === null) continue;
|
|
2385
|
+
const inserted = insert.run(kind, relPath, hash);
|
|
2386
|
+
logEvent(ctx.db, {
|
|
2387
|
+
entityType: "artifact",
|
|
2388
|
+
entityId: inserted.lastInsertRowid,
|
|
2389
|
+
event: "meta_registered",
|
|
2390
|
+
actor,
|
|
2391
|
+
payload: { path: relPath, kind }
|
|
2392
|
+
});
|
|
2393
|
+
result.registered.push({ path: relPath, kind });
|
|
2394
|
+
}
|
|
2395
|
+
});
|
|
2396
|
+
tx();
|
|
2397
|
+
return result;
|
|
2398
|
+
}
|
|
2399
|
+
var init_meta_command = __esm({
|
|
2400
|
+
"core/commands/meta.command.ts"() {
|
|
2401
|
+
"use strict";
|
|
2402
|
+
init_events();
|
|
2403
|
+
init_hash();
|
|
2404
|
+
init_kind();
|
|
2405
|
+
init_platforms();
|
|
2406
|
+
init_artifact_commands();
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
// core/commands/scan.command.ts
|
|
2411
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
|
|
2412
|
+
import { join as join10 } from "node:path";
|
|
2413
|
+
function parseCoords(ctx, kind, relPath) {
|
|
2414
|
+
const none = { module: null, endpoint: null, page: null };
|
|
2415
|
+
const base = (p) => p.split("/").pop().replace(/\.(md|html)$/i, "");
|
|
2416
|
+
const seg = relPath.split("/");
|
|
2417
|
+
const mod = (raw) => normalizeModule(raw, ctx.config);
|
|
2418
|
+
switch (kind) {
|
|
2419
|
+
case "flow":
|
|
2420
|
+
case "module-prd":
|
|
2421
|
+
return { module: mod(base(relPath)), endpoint: "common", page: null };
|
|
2422
|
+
case "db-doc":
|
|
2423
|
+
return { module: mod(base(relPath)), endpoint: "common", page: null };
|
|
2424
|
+
case "api-doc":
|
|
2425
|
+
return { module: mod(base(relPath)), endpoint: "service", page: null };
|
|
2426
|
+
case "design-system":
|
|
2427
|
+
return { module: null, endpoint: base(relPath), page: null };
|
|
2428
|
+
case "page-prd":
|
|
2429
|
+
case "design-prompt":
|
|
2430
|
+
case "prototype":
|
|
2431
|
+
case "acceptance": {
|
|
2432
|
+
if (seg.length < 3) return none;
|
|
2433
|
+
const endpoint = seg[seg.length - 3];
|
|
2434
|
+
const module = mod(seg[seg.length - 2]);
|
|
2435
|
+
return { module, endpoint, page: `${module}/${base(relPath)}` };
|
|
2436
|
+
}
|
|
2437
|
+
default:
|
|
2438
|
+
return none;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
function isMetaPath(ctx, relPath) {
|
|
2442
|
+
const registry = getKindRegistry(ctx.config);
|
|
2443
|
+
for (const spec of Object.values(registry)) {
|
|
2444
|
+
if (!spec.meta) continue;
|
|
2445
|
+
for (const pattern of spec.pathPatterns ?? []) {
|
|
2446
|
+
if (pattern.endsWith("/") ? relPath.startsWith(pattern) : relPath === pattern) return true;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
return false;
|
|
2450
|
+
}
|
|
2451
|
+
function walkFiles2(absDir, rel, out) {
|
|
2452
|
+
for (const name of readdirSync4(absDir).sort()) {
|
|
2453
|
+
if (name.startsWith(".") || name === "node_modules") continue;
|
|
2454
|
+
const full = join10(absDir, name);
|
|
2455
|
+
const relPath = `${rel}/${name}`;
|
|
2456
|
+
if (statSync5(full).isDirectory()) {
|
|
2457
|
+
walkFiles2(full, relPath, out);
|
|
2458
|
+
} else if (/\.(md|html)$/i.test(name)) {
|
|
2459
|
+
out.push(relPath);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function expandCodeRoots(ctx) {
|
|
2464
|
+
const results = [];
|
|
2465
|
+
for (const [endpoint, patterns] of Object.entries(ctx.config.codeRoots)) {
|
|
2466
|
+
for (const pattern of patterns) {
|
|
2467
|
+
const segments = pattern.replace(/\\/g, "/").split("/");
|
|
2468
|
+
let candidates = [{ dir: "", module: null }];
|
|
2469
|
+
for (const segment of segments) {
|
|
2470
|
+
const next = [];
|
|
2471
|
+
const ph = /^\{(\w+)\}(.*)$/.exec(segment);
|
|
2472
|
+
for (const c of candidates) {
|
|
2473
|
+
if (ph) {
|
|
2474
|
+
const [, placeholder, suffix] = ph;
|
|
2475
|
+
const abs = join10(ctx.root, c.dir);
|
|
2476
|
+
if (!existsSync9(abs)) continue;
|
|
2477
|
+
for (const name of readdirSync4(abs).sort()) {
|
|
2478
|
+
const isDir = statSync5(join10(abs, name)).isDirectory();
|
|
2479
|
+
if (suffix === "" ? !isDir : isDir || !name.endsWith(suffix)) continue;
|
|
2480
|
+
const captured = suffix === "" ? name : name.slice(0, -suffix.length);
|
|
2481
|
+
next.push({ dir: c.dir ? `${c.dir}/${name}` : name, module: placeholder === "module" ? captured : c.module });
|
|
2482
|
+
}
|
|
2483
|
+
} else {
|
|
2484
|
+
next.push({ dir: c.dir ? `${c.dir}/${segment}` : segment, module: c.module });
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
candidates = next;
|
|
2488
|
+
}
|
|
2489
|
+
for (const c of candidates) {
|
|
2490
|
+
if (c.module && existsSync9(join10(ctx.root, c.dir))) {
|
|
2491
|
+
results.push({ dir: c.dir, endpoint, module: normalizeModule(c.module, ctx.config) });
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return results;
|
|
2497
|
+
}
|
|
2498
|
+
function deriveEdges(ctx) {
|
|
2499
|
+
const registry = getKindRegistry(ctx.config);
|
|
2500
|
+
const artifacts = ctx.db.prepare("SELECT * FROM artifacts").all();
|
|
2501
|
+
const nonMeta = artifacts.filter((a) => !registry[a.kind]?.meta);
|
|
2502
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2503
|
+
for (const a of nonMeta) {
|
|
2504
|
+
const list = byKind.get(a.kind) ?? [];
|
|
2505
|
+
list.push(a);
|
|
2506
|
+
byKind.set(a.kind, list);
|
|
2507
|
+
}
|
|
2508
|
+
const insert = ctx.db.prepare("INSERT OR IGNORE INTO artifact_edges (from_id, to_id) VALUES (?, ?)");
|
|
2509
|
+
let created = 0;
|
|
2510
|
+
const tx = ctx.db.transaction(() => {
|
|
2511
|
+
for (const child of nonMeta) {
|
|
2512
|
+
const parents = registry[child.kind]?.parents ?? [];
|
|
2513
|
+
for (const parentKind of parents) {
|
|
2514
|
+
const level = registry[parentKind]?.level ?? "module";
|
|
2515
|
+
const candidates = (byKind.get(parentKind) ?? []).filter((p) => {
|
|
2516
|
+
if ((level === "module" || level === "page") && child.module && p.module !== child.module) return false;
|
|
2517
|
+
if ((level === "endpoint" || level === "page") && child.endpoint && p.endpoint !== child.endpoint) return false;
|
|
2518
|
+
if (level === "page" && child.page && p.page !== child.page) return false;
|
|
2519
|
+
return true;
|
|
2520
|
+
});
|
|
2521
|
+
for (const parent of candidates) {
|
|
2522
|
+
created += insert.run(parent.id, child.id).changes;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
tx();
|
|
2528
|
+
return created;
|
|
2529
|
+
}
|
|
2530
|
+
function scanArtifacts(ctx, actor = "system") {
|
|
2531
|
+
const summary = { registered: 0, refreshed: 0, edges: 0, skipped: [] };
|
|
2532
|
+
const files = [];
|
|
2533
|
+
for (const dir of Object.values(ctx.config.docs)) {
|
|
2534
|
+
const abs = join10(ctx.root, dir);
|
|
2535
|
+
if (existsSync9(abs)) walkFiles2(abs, dir, files);
|
|
2536
|
+
}
|
|
2537
|
+
for (const p of ["ARCHITECTURE.md", "TECH.md"]) {
|
|
2538
|
+
if (existsSync9(join10(ctx.root, p))) files.push(p);
|
|
2539
|
+
}
|
|
2540
|
+
const exists = ctx.db.prepare("SELECT id, content_hash FROM artifacts WHERE path = ?");
|
|
2541
|
+
const insert = ctx.db.prepare(
|
|
2542
|
+
"INSERT INTO artifacts (kind, module, endpoint, page, path, content_hash) VALUES (?, ?, ?, ?, ?, ?)"
|
|
2543
|
+
);
|
|
2544
|
+
const registerOne = (relPath, kind, coords) => {
|
|
2545
|
+
const row = exists.get(relPath);
|
|
2546
|
+
if (row) {
|
|
2547
|
+
const before = row.content_hash;
|
|
2548
|
+
const after = refreshArtifact(ctx, { id: row.id }, actor);
|
|
2549
|
+
if (after.content_hash !== before) summary.refreshed++;
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
const hash = hashPath(join10(ctx.root, relPath));
|
|
2553
|
+
if (hash === null) return;
|
|
2554
|
+
const tx = ctx.db.transaction(() => {
|
|
2555
|
+
const result = insert.run(kind, coords.module, coords.endpoint, coords.page, relPath, hash);
|
|
2556
|
+
logEvent(ctx.db, {
|
|
2557
|
+
entityType: "artifact",
|
|
2558
|
+
entityId: result.lastInsertRowid,
|
|
2559
|
+
event: "scan_registered",
|
|
2560
|
+
actor,
|
|
2561
|
+
payload: { path: relPath, kind },
|
|
2562
|
+
module: coords.module,
|
|
2563
|
+
endpoint: coords.endpoint,
|
|
2564
|
+
page: coords.page
|
|
2565
|
+
});
|
|
2566
|
+
});
|
|
2567
|
+
tx();
|
|
2568
|
+
summary.registered++;
|
|
2569
|
+
};
|
|
2570
|
+
for (const relPath of files) {
|
|
2571
|
+
if (isMetaPath(ctx, relPath)) {
|
|
2572
|
+
summary.skipped.push(relPath);
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
const kind = inferKind(relPath, ctx.config);
|
|
2576
|
+
registerOne(relPath, kind, parseCoords(ctx, kind, relPath));
|
|
2577
|
+
}
|
|
2578
|
+
for (const code of expandCodeRoots(ctx)) {
|
|
2579
|
+
registerOne(code.dir, "code", { module: code.module, endpoint: code.endpoint, page: null });
|
|
2580
|
+
}
|
|
2581
|
+
summary.edges = deriveEdges(ctx);
|
|
2582
|
+
return summary;
|
|
2583
|
+
}
|
|
2584
|
+
var init_scan_command = __esm({
|
|
2585
|
+
"core/commands/scan.command.ts"() {
|
|
2586
|
+
"use strict";
|
|
2587
|
+
init_events();
|
|
2588
|
+
init_hash();
|
|
2589
|
+
init_kind();
|
|
2590
|
+
init_artifact_commands();
|
|
2591
|
+
}
|
|
2592
|
+
});
|
|
2593
|
+
|
|
2594
|
+
// core/commands/qa.command.ts
|
|
2595
|
+
function recordQaResult(ctx, p) {
|
|
2596
|
+
const task = getTaskRow(ctx, p.id);
|
|
2597
|
+
if (task.role !== "qa") throw new Error(`\u4EFB\u52A1 #${p.id} \u4E0D\u662F qa \u4EFB\u52A1`);
|
|
2598
|
+
if (task.assignee !== p.operator) throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u8BB0\u5F55\u9A8C\u6536\u7ED3\u679C`);
|
|
2599
|
+
if (p.result === "fail" && !p.reason?.trim()) throw new Error(`\u9A8C\u6536\u4E0D\u901A\u8FC7\u5FC5\u987B\u9644\u539F\u56E0,\u5B83\u5C06\u6210\u4E3A rework \u4EFB\u52A1\u7684\u5185\u5BB9`);
|
|
2600
|
+
const eventName = p.result === "pass" ? "qa_passed" : "qa_failed";
|
|
2601
|
+
const tx = ctx.db.transaction(() => {
|
|
2602
|
+
logEvent(ctx.db, {
|
|
2603
|
+
entityType: "task",
|
|
2604
|
+
entityId: task.id,
|
|
2605
|
+
event: eventName,
|
|
2606
|
+
actor: p.operator,
|
|
2607
|
+
payload: { reason: p.reason ?? null },
|
|
2608
|
+
module: task.module,
|
|
2609
|
+
endpoint: task.endpoint,
|
|
2610
|
+
page: task.page
|
|
2611
|
+
});
|
|
2612
|
+
});
|
|
2613
|
+
tx();
|
|
2614
|
+
updateTask(ctx, { id: task.id, status: "completed", operator: p.operator, force: true });
|
|
2615
|
+
if (p.result === "pass") {
|
|
2616
|
+
const codes = ctx.db.prepare("SELECT * FROM artifacts WHERE kind = 'code' AND module IS ? AND (endpoint IS ? OR ? IS NULL)").all(task.module, task.endpoint, task.endpoint);
|
|
2617
|
+
for (const code of codes) {
|
|
2618
|
+
try {
|
|
2619
|
+
feedbackArtifact(ctx, { id: code.id }, { verdict: 1, comment: "QA \u9A8C\u6536\u901A\u8FC7(\u81EA\u52A8)", actor: "qa-auto", taskId: task.id });
|
|
2620
|
+
} catch {
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return { reworkTaskId: null };
|
|
2624
|
+
}
|
|
2625
|
+
const tx2 = ctx.db.transaction(() => {
|
|
2626
|
+
const result = ctx.db.prepare(
|
|
2627
|
+
`INSERT INTO tasks (module, role, endpoint, page, type, status, assignee, creator, content)
|
|
2628
|
+
VALUES (?, 'developer', ?, ?, 'rework', 'pending', 'developer', 'system', ?)`
|
|
2629
|
+
).run(task.module, task.endpoint, task.page, `[\u8FD4\u5DE5] QA #${task.id} \u9A8C\u6536\u4E0D\u901A\u8FC7:${p.reason}`);
|
|
2630
|
+
const reworkId = result.lastInsertRowid;
|
|
2631
|
+
logEvent(ctx.db, {
|
|
2632
|
+
entityType: "task",
|
|
2633
|
+
entityId: reworkId,
|
|
2634
|
+
event: "rework_spawned",
|
|
2635
|
+
actor: "system",
|
|
2636
|
+
payload: { qaTask: task.id, reason: p.reason },
|
|
2637
|
+
module: task.module,
|
|
2638
|
+
endpoint: task.endpoint,
|
|
2639
|
+
page: task.page
|
|
2640
|
+
});
|
|
2641
|
+
return reworkId;
|
|
2642
|
+
});
|
|
2643
|
+
return { reworkTaskId: tx2() };
|
|
2644
|
+
}
|
|
2645
|
+
var init_qa_command = __esm({
|
|
2646
|
+
"core/commands/qa.command.ts"() {
|
|
2647
|
+
"use strict";
|
|
2648
|
+
init_events();
|
|
2649
|
+
init_artifact_commands();
|
|
2650
|
+
init_task_commands();
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
// core/commands/retro.command.ts
|
|
2655
|
+
var retro_command_exports = {};
|
|
2656
|
+
__export(retro_command_exports, {
|
|
2657
|
+
AUTO_ACTOR_WEIGHT: () => AUTO_ACTOR_WEIGHT,
|
|
2658
|
+
RED_FLAG_THRESHOLD: () => RED_FLAG_THRESHOLD,
|
|
2659
|
+
SKILL_CANDIDATE_THRESHOLD: () => SKILL_CANDIDATE_THRESHOLD,
|
|
2660
|
+
approvalStats: () => approvalStats,
|
|
2661
|
+
distillFeedback: () => distillFeedback,
|
|
2662
|
+
exportEventLog: () => exportEventLog,
|
|
2663
|
+
runRetrospective: () => runRetrospective
|
|
2664
|
+
});
|
|
2665
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2666
|
+
import { join as join11 } from "node:path";
|
|
2667
|
+
function parseDbTime(s) {
|
|
2668
|
+
return Date.parse(s.includes("T") ? s : s.replace(" ", "T") + "Z");
|
|
2669
|
+
}
|
|
2670
|
+
function distillFeedback(ctx, opts = {}) {
|
|
2671
|
+
const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
2672
|
+
const halfLifeMs = ctx.config.feedbackHalfLifeDays * 24 * 60 * 60 * 1e3;
|
|
2673
|
+
let query = `
|
|
2674
|
+
SELECT f.artifact_id, f.verdict, f.comment, f.actor, f.created_at,
|
|
2675
|
+
a.kind, a.module, a.endpoint, a.path
|
|
2676
|
+
FROM artifact_feedback f JOIN artifacts a ON a.id = f.artifact_id`;
|
|
2677
|
+
const params = [];
|
|
2678
|
+
if (opts.module) {
|
|
2679
|
+
query += " WHERE a.module = ?";
|
|
2680
|
+
params.push(opts.module);
|
|
2681
|
+
}
|
|
2682
|
+
query += " ORDER BY f.id";
|
|
2683
|
+
const rows = ctx.db.prepare(query).all(...params);
|
|
2684
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2685
|
+
for (const row of rows) {
|
|
2686
|
+
const endpoint = row.endpoint ?? "common";
|
|
2687
|
+
const key = `${endpoint}/${row.kind}`;
|
|
2688
|
+
let group = groups.get(key);
|
|
2689
|
+
if (!group) {
|
|
2690
|
+
group = { endpoint, kind: row.kind, posScore: 0, negScore: 0, bucket: "observation", evidence: [] };
|
|
2691
|
+
groups.set(key, group);
|
|
2692
|
+
}
|
|
2693
|
+
const ageMs = Math.max(0, now - parseDbTime(row.created_at));
|
|
2694
|
+
const actorWeight = row.actor.endsWith("-auto") ? AUTO_ACTOR_WEIGHT : 1;
|
|
2695
|
+
const weight = actorWeight * Math.pow(0.5, ageMs / halfLifeMs);
|
|
2696
|
+
if (row.verdict === 1) group.posScore += weight;
|
|
2697
|
+
else group.negScore += weight;
|
|
2698
|
+
group.evidence.push({
|
|
2699
|
+
artifactId: row.artifact_id,
|
|
2700
|
+
path: row.path,
|
|
2701
|
+
module: row.module,
|
|
2702
|
+
verdict: row.verdict,
|
|
2703
|
+
weight: Math.round(weight * 100) / 100,
|
|
2704
|
+
comment: row.comment,
|
|
2705
|
+
actor: row.actor,
|
|
2706
|
+
createdAt: row.created_at
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
const result = [...groups.values()];
|
|
2710
|
+
for (const g of result) {
|
|
2711
|
+
g.posScore = Math.round(g.posScore * 100) / 100;
|
|
2712
|
+
g.negScore = Math.round(g.negScore * 100) / 100;
|
|
2713
|
+
const pos = g.posScore >= SKILL_CANDIDATE_THRESHOLD;
|
|
2714
|
+
const neg = g.negScore >= RED_FLAG_THRESHOLD;
|
|
2715
|
+
if (pos && neg) {
|
|
2716
|
+
g.bucket = "observation";
|
|
2717
|
+
g.reason = "mixed";
|
|
2718
|
+
} else if (pos) {
|
|
2719
|
+
g.bucket = "skill-candidate";
|
|
2720
|
+
} else if (neg) {
|
|
2721
|
+
g.bucket = "red-flag";
|
|
2722
|
+
} else {
|
|
2723
|
+
g.bucket = "observation";
|
|
2724
|
+
g.reason = "insufficient";
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
const order = { "skill-candidate": 0, "red-flag": 1, observation: 2 };
|
|
2728
|
+
return result.sort((a, b) => order[a.bucket] - order[b.bucket] || `${a.endpoint}/${a.kind}`.localeCompare(`${b.endpoint}/${b.kind}`));
|
|
2729
|
+
}
|
|
2730
|
+
function approvalStats(ctx) {
|
|
2731
|
+
const rows = ctx.db.prepare(
|
|
2732
|
+
`SELECT e.entity_id, e.event, e.created_at, a.kind
|
|
2733
|
+
FROM events e JOIN artifacts a ON a.id = e.entity_id
|
|
2734
|
+
WHERE e.entity_type = 'artifact' AND e.event IN ('submitted', 'approved', 'rejected')
|
|
2735
|
+
ORDER BY e.entity_id, e.id`
|
|
2736
|
+
).all();
|
|
2737
|
+
const perKind = /* @__PURE__ */ new Map();
|
|
2738
|
+
const kindOf = (kind) => {
|
|
2739
|
+
let k = perKind.get(kind);
|
|
2740
|
+
if (!k) {
|
|
2741
|
+
k = { approved: 0, rejected: 0, durations: [] };
|
|
2742
|
+
perKind.set(kind, k);
|
|
2743
|
+
}
|
|
2744
|
+
return k;
|
|
2745
|
+
};
|
|
2746
|
+
let currentEntity = -1;
|
|
2747
|
+
let lastSubmittedAt = null;
|
|
2748
|
+
for (const row of rows) {
|
|
2749
|
+
if (row.entity_id !== currentEntity) {
|
|
2750
|
+
currentEntity = row.entity_id;
|
|
2751
|
+
lastSubmittedAt = null;
|
|
2752
|
+
}
|
|
2753
|
+
const k = kindOf(row.kind);
|
|
2754
|
+
if (row.event === "submitted") {
|
|
2755
|
+
lastSubmittedAt = parseDbTime(row.created_at);
|
|
2756
|
+
} else if (row.event === "approved") {
|
|
2757
|
+
k.approved++;
|
|
2758
|
+
if (lastSubmittedAt !== null) {
|
|
2759
|
+
k.durations.push(parseDbTime(row.created_at) - lastSubmittedAt);
|
|
2760
|
+
lastSubmittedAt = null;
|
|
2761
|
+
}
|
|
2762
|
+
} else {
|
|
2763
|
+
k.rejected++;
|
|
2764
|
+
lastSubmittedAt = null;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const hours = (ds) => ds.length === 0 ? null : Math.round(ds.reduce((s, d) => s + d, 0) / ds.length / 36e5 * 100) / 100;
|
|
2768
|
+
const byKind = [...perKind.entries()].map(([kind, k]) => ({ kind, approved: k.approved, rejected: k.rejected, avgApprovalHours: hours(k.durations) })).sort((a, b) => a.kind.localeCompare(b.kind));
|
|
2769
|
+
const approved = byKind.reduce((s, k) => s + k.approved, 0);
|
|
2770
|
+
const rejected = byKind.reduce((s, k) => s + k.rejected, 0);
|
|
2771
|
+
const allDurations = [...perKind.values()].flatMap((k) => k.durations);
|
|
2772
|
+
return {
|
|
2773
|
+
approved,
|
|
2774
|
+
rejected,
|
|
2775
|
+
rejectionRate: approved + rejected === 0 ? null : Math.round(rejected / (approved + rejected) * 100) / 100,
|
|
2776
|
+
avgApprovalHours: hours(allDurations),
|
|
2777
|
+
byKind
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function runRetrospective(ctx, opts = {}) {
|
|
2781
|
+
const groups = distillFeedback(ctx, opts);
|
|
2782
|
+
const candidates = groups.filter((g) => g.bucket === "skill-candidate").length;
|
|
2783
|
+
const redFlags = groups.filter((g) => g.bucket === "red-flag").length;
|
|
2784
|
+
const cli = ctx.config.cli;
|
|
2785
|
+
const guidance = [];
|
|
2786
|
+
if (candidates > 0) {
|
|
2787
|
+
guidance.push(
|
|
2788
|
+
`\u53D1\u73B0 ${candidates} \u4E2A skill \u5019\u9009\u7EC4:\u4F9D\u636E\u5176 evidence(\u8DEF\u5F84+comment)\u8D77\u8349 .claude/skills/<\u540D\u79F0>/SKILL.md,\u7136\u540E \`${cli} register-meta\` \u6CE8\u518C + \`${cli} submit --actor=<\u89D2\u8272> -- .claude/skills/<\u540D\u79F0>/SKILL.md\` \u9001\u4EBA\u5BA1;approved \u540E\u624D\u4F5C\u4E3A\u7ECF\u9A8C\u751F\u6548`
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
if (redFlags > 0) {
|
|
2792
|
+
guidance.push(
|
|
2793
|
+
`\u53D1\u73B0 ${redFlags} \u4E2A Red Flags \u7EC4:\u8D1F\u4F8B comment \u5373\u7D20\u6750,\u5199\u5165\u5BF9\u5E94 skill \u8349\u7A3F\u7684 Red Flags \u7AE0\u8282;\u80FD\u673A\u5668\u67E5\u7684\u7EA6\u5B9A\u5E94\u964D\u7EA7\u4E3A protocolLints \u5361\u70B9(workbench.config.json)`
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
if (guidance.length === 0) {
|
|
2797
|
+
guidance.push("\u53CD\u9988\u6837\u672C\u4E0D\u8DB3,\u7EE7\u7EED\u79EF\u7D2F(QA pass \u81EA\u52A8\u5582 +1,\u4EBA\u5DE5 \u{1F44D}\u{1F44E} \u662F\u66F4\u5F3A\u4FE1\u53F7)");
|
|
2798
|
+
}
|
|
2799
|
+
return {
|
|
2800
|
+
halfLifeDays: ctx.config.feedbackHalfLifeDays,
|
|
2801
|
+
module: opts.module ?? null,
|
|
2802
|
+
groups,
|
|
2803
|
+
candidates,
|
|
2804
|
+
redFlags,
|
|
2805
|
+
approval: approvalStats(ctx),
|
|
2806
|
+
guidance
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
function exportEventLog(ctx) {
|
|
2810
|
+
ctx.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
2811
|
+
const dir = join11(ctx.root, ctx.config.dataDir);
|
|
2812
|
+
mkdirSync4(dir, { recursive: true });
|
|
2813
|
+
const dump = (table, file) => {
|
|
2814
|
+
const rows = ctx.db.prepare(`SELECT * FROM ${table} ORDER BY id`).all();
|
|
2815
|
+
writeFileSync3(join11(dir, file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length > 0 ? "\n" : ""));
|
|
2816
|
+
return rows.length;
|
|
2817
|
+
};
|
|
2818
|
+
return {
|
|
2819
|
+
events: dump("events", "events.jsonl"),
|
|
2820
|
+
feedbacks: dump("artifact_feedback", "feedback.jsonl"),
|
|
2821
|
+
files: [`${ctx.config.dataDir}/events.jsonl`, `${ctx.config.dataDir}/feedback.jsonl`]
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
var SKILL_CANDIDATE_THRESHOLD, RED_FLAG_THRESHOLD, AUTO_ACTOR_WEIGHT;
|
|
2825
|
+
var init_retro_command = __esm({
|
|
2826
|
+
"core/commands/retro.command.ts"() {
|
|
2827
|
+
"use strict";
|
|
2828
|
+
SKILL_CANDIDATE_THRESHOLD = 3;
|
|
2829
|
+
RED_FLAG_THRESHOLD = 2;
|
|
2830
|
+
AUTO_ACTOR_WEIGHT = 0.5;
|
|
2831
|
+
}
|
|
2832
|
+
});
|
|
2833
|
+
|
|
2834
|
+
// core/commands/plan.command.ts
|
|
2835
|
+
function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
2836
|
+
const module = normalizeModule(moduleRaw, ctx.config);
|
|
2837
|
+
const summary = { created: [], skipped: 0, cancelled: 0, warnings: [] };
|
|
2838
|
+
const artifacts = ctx.db.prepare("SELECT * FROM artifacts WHERE module IS ? OR module IS NULL").all(module);
|
|
2839
|
+
const byKind = (kind) => artifacts.filter((a) => a.kind === kind && a.module === module);
|
|
2840
|
+
const projectLevel = (kind) => artifacts.filter((a) => a.kind === kind && a.module === null);
|
|
2841
|
+
const modulePrd = byKind("module-prd");
|
|
2842
|
+
if (modulePrd.length === 0) {
|
|
2843
|
+
throw new Error(`[\u524D\u7F6E\u6761\u4EF6] \u6A21\u5757 ${module} \u6CA1\u6709\u767B\u8BB0 module-prd,PM \u5148\u4EA7\u51FA\u5E76\u767B\u8BB0\u540E\u624D\u80FD\u6D3E\u53D1`);
|
|
2844
|
+
}
|
|
2845
|
+
const trustChecks = [
|
|
2846
|
+
{ desc: "\u6280\u672F\u57FA\u7EBF(baseline)", rows: projectLevel("baseline") },
|
|
2847
|
+
{ desc: "\u9879\u76EE\u5168\u666F(project)", rows: projectLevel("project") },
|
|
2848
|
+
{ desc: `flow(${module})`, rows: byKind("flow") },
|
|
2849
|
+
{ desc: `\u6A21\u5757 PRD(${module})`, rows: modulePrd }
|
|
2850
|
+
];
|
|
2851
|
+
for (const check of trustChecks) {
|
|
2852
|
+
const ok = check.rows.some((r) => reviewStatus(r) === "approved");
|
|
2853
|
+
if (ok) continue;
|
|
2854
|
+
const msg = `[\u4FE1\u4EFB\u8B66\u544A] ${check.desc} \u672A\u8FBE approved`;
|
|
2855
|
+
if (ctx.config.gates.approvalMode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
|
|
2856
|
+
summary.warnings.push(msg);
|
|
2857
|
+
}
|
|
2858
|
+
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page);
|
|
2859
|
+
const endpoints = [...new Set(pagePrds.map((a) => a.endpoint))];
|
|
2860
|
+
const desired = [
|
|
2861
|
+
{ role: "architect", endpoint: "common", page: null, type: "build", assignee: "architect", content: `\u8BBE\u8BA1 ${module} \u6A21\u5757\u6570\u636E\u5E93` },
|
|
2862
|
+
{ role: "architect", endpoint: "service", page: null, type: "build", assignee: "architect", content: `\u8BBE\u8BA1 ${module} \u6A21\u5757 API \u6587\u6863` },
|
|
2863
|
+
{ role: "developer", endpoint: "service", page: null, type: "build", assignee: "developer", content: `\u5B9E\u73B0 ${module} \u6A21\u5757 service \u7AEF` }
|
|
2864
|
+
];
|
|
2865
|
+
for (const endpoint of endpoints) {
|
|
2866
|
+
const hasDesignSystem = artifacts.some((a) => a.kind === "design-system" && a.endpoint === endpoint);
|
|
2867
|
+
if (!hasDesignSystem) {
|
|
2868
|
+
desired.push({
|
|
2869
|
+
role: "designer",
|
|
2870
|
+
endpoint,
|
|
2871
|
+
page: null,
|
|
2872
|
+
type: "build",
|
|
2873
|
+
assignee: "designer",
|
|
2874
|
+
content: `\u5EFA\u7ACB ${endpoint} \u7AEF\u8BBE\u8BA1\u7CFB\u7EDF(\u8BE5\u7AEF\u9996\u4E2A\u9875\u9762\u8BBE\u8BA1\u524D\u7F6E)`
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
for (const prd of pagePrds) {
|
|
2879
|
+
desired.push(
|
|
2880
|
+
{ role: "designer", endpoint: prd.endpoint, page: prd.page, type: "build", assignee: "designer", content: `\u8BBE\u8BA1 ${prd.endpoint}/${prd.page} \u9875\u9762(\u63D0\u793A\u8BCD+\u539F\u578B)` },
|
|
2881
|
+
{ role: "developer", endpoint: prd.endpoint, page: prd.page, type: "build", assignee: "developer", content: `\u5B9E\u73B0 ${prd.endpoint}/${prd.page} \u9875\u9762` },
|
|
2882
|
+
{ role: "qa", endpoint: prd.endpoint, page: prd.page, type: "qa", assignee: "qa", content: `\u9A8C\u6536 ${prd.endpoint}/${prd.page} \u9875\u9762` }
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
const activeRoles = new Set(ctx.config.pipeline);
|
|
2886
|
+
const filtered = desired.filter((d) => activeRoles.has(d.role));
|
|
2887
|
+
desired.length = 0;
|
|
2888
|
+
desired.push(...filtered);
|
|
2889
|
+
const exists = ctx.db.prepare(
|
|
2890
|
+
`SELECT COUNT(*) AS c FROM tasks
|
|
2891
|
+
WHERE module IS ? AND role = ? AND endpoint IS ? AND page IS ? AND type = ? AND status != 'cancelled'`
|
|
2892
|
+
);
|
|
2893
|
+
for (const d of desired) {
|
|
2894
|
+
const row = exists.get(module, d.role, d.endpoint, d.page, d.type);
|
|
2895
|
+
if (row.c > 0) {
|
|
2896
|
+
summary.skipped++;
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
const id = createTask(ctx, { module, role: d.role, endpoint: d.endpoint, page: d.page, type: d.type, assignee: d.assignee, creator, content: d.content });
|
|
2900
|
+
summary.created.push({ id, role: d.role, endpoint: d.endpoint, page: d.page, type: d.type });
|
|
2901
|
+
}
|
|
2902
|
+
const validPages = new Set(pagePrds.map((p) => `${p.endpoint}|${p.page}`));
|
|
2903
|
+
const pending = ctx.db.prepare(
|
|
2904
|
+
`SELECT id, role, endpoint, page FROM tasks
|
|
2905
|
+
WHERE module IS ? AND status = 'pending' AND page IS NOT NULL AND type IN ('build', 'qa')`
|
|
2906
|
+
).all(module);
|
|
2907
|
+
for (const t of pending) {
|
|
2908
|
+
if (validPages.has(`${t.endpoint}|${t.page}`)) continue;
|
|
2909
|
+
const tx = ctx.db.transaction(() => {
|
|
2910
|
+
ctx.db.prepare("UPDATE tasks SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(t.id);
|
|
2911
|
+
logEvent(ctx.db, {
|
|
2912
|
+
entityType: "task",
|
|
2913
|
+
entityId: t.id,
|
|
2914
|
+
event: "plan_cancelled",
|
|
2915
|
+
actor: creator,
|
|
2916
|
+
payload: { reason: "\u9875\u9762 PRD \u5DF2\u5220\u9664" },
|
|
2917
|
+
module,
|
|
2918
|
+
endpoint: t.endpoint,
|
|
2919
|
+
page: t.page
|
|
2920
|
+
});
|
|
2921
|
+
});
|
|
2922
|
+
tx();
|
|
2923
|
+
summary.cancelled++;
|
|
2924
|
+
}
|
|
2925
|
+
return summary;
|
|
2926
|
+
}
|
|
2927
|
+
var init_plan_command = __esm({
|
|
2928
|
+
"core/commands/plan.command.ts"() {
|
|
2929
|
+
"use strict";
|
|
2930
|
+
init_derive();
|
|
2931
|
+
init_events();
|
|
2932
|
+
init_kind();
|
|
2933
|
+
init_task_commands();
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
// core/commands/install-hooks.command.ts
|
|
2938
|
+
import { chmodSync, existsSync as existsSync10, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2939
|
+
import { join as join12 } from "node:path";
|
|
2940
|
+
function installGitHooks(ctx) {
|
|
2941
|
+
const hooksDir = join12(ctx.root, ".git", "hooks");
|
|
2942
|
+
if (!existsSync10(join12(ctx.root, ".git"))) throw new Error("\u4E0D\u662F git \u4ED3\u5E93");
|
|
2943
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
2944
|
+
const installed = [];
|
|
2945
|
+
const postCommit = `#!/bin/sh
|
|
2946
|
+
# opcflow:\u63D0\u4EA4\u540E\u5BF9\u8D26(fail-open,\u540E\u53F0\u6267\u884C\u4E0D\u963B\u585E git)
|
|
2947
|
+
${WORKBENCH_BIN} postcommit >/dev/null 2>&1 &
|
|
2948
|
+
exit 0
|
|
2949
|
+
`;
|
|
2950
|
+
writeFileSync4(join12(hooksDir, "post-commit"), postCommit);
|
|
2951
|
+
try {
|
|
2952
|
+
chmodSync(join12(hooksDir, "post-commit"), 493);
|
|
2953
|
+
} catch {
|
|
2954
|
+
}
|
|
2955
|
+
installed.push("post-commit(sync \u5BF9\u8D26)");
|
|
2956
|
+
if (ctx.config.git.taskTrailer === "on") {
|
|
2957
|
+
const prepare = `#!/bin/sh
|
|
2958
|
+
# workbench:\u6CE8\u5165\u4EFB\u52A1\u5F52\u56E0 trailer(\u73AF\u5883\u53D8\u91CF WORKBENCH_TASK_ID \u5B58\u5728\u65F6)
|
|
2959
|
+
if [ -n "$WORKBENCH_TASK_ID" ] && [ "$2" != "merge" ]; then
|
|
2960
|
+
if ! grep -q "^${ctx.config.git.trailerKey}: #" "$1"; then
|
|
2961
|
+
printf '\\n${ctx.config.git.trailerKey}: #%s\\n' "$WORKBENCH_TASK_ID" >> "$1"
|
|
2962
|
+
fi
|
|
2963
|
+
fi
|
|
2964
|
+
exit 0
|
|
2965
|
+
`;
|
|
2966
|
+
writeFileSync4(join12(hooksDir, "prepare-commit-msg"), prepare);
|
|
2967
|
+
try {
|
|
2968
|
+
chmodSync(join12(hooksDir, "prepare-commit-msg"), 493);
|
|
2969
|
+
} catch {
|
|
2970
|
+
}
|
|
2971
|
+
installed.push(`prepare-commit-msg(${ctx.config.git.trailerKey} trailer \u6CE8\u5165)`);
|
|
2972
|
+
}
|
|
2973
|
+
return installed;
|
|
2974
|
+
}
|
|
2975
|
+
var init_install_hooks_command = __esm({
|
|
2976
|
+
"core/commands/install-hooks.command.ts"() {
|
|
2977
|
+
"use strict";
|
|
2978
|
+
init_config();
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
// core/commands/gen-agents.command.ts
|
|
2983
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2984
|
+
import { dirname as dirname3, join as join13 } from "node:path";
|
|
2985
|
+
function expandPath(ctx, kind) {
|
|
2986
|
+
const spec = getKindRegistry(ctx.config)[kind];
|
|
2987
|
+
const raw = spec?.pathPatterns?.[0] ?? "";
|
|
2988
|
+
return raw.replace("{prd}", ctx.config.docs.prd).replace("{architecture}", ctx.config.docs.architecture).replace("{design}", ctx.config.docs.design).replace("{acceptance}", ctx.config.docs.acceptance);
|
|
2989
|
+
}
|
|
2990
|
+
function parseTemplate(rawInput) {
|
|
2991
|
+
const raw = rawInput.replace(/\r\n/g, "\n");
|
|
2992
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
2993
|
+
if (!m) throw new Error("\u6A21\u677F\u7F3A\u5C11 frontmatter");
|
|
2994
|
+
const meta = {};
|
|
2995
|
+
for (const line of m[1].split("\n")) {
|
|
2996
|
+
const idx = line.indexOf(":");
|
|
2997
|
+
if (idx === -1) continue;
|
|
2998
|
+
meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
2999
|
+
}
|
|
3000
|
+
return {
|
|
3001
|
+
name: meta.name ?? "",
|
|
3002
|
+
description: meta.description ?? "",
|
|
3003
|
+
tools: (meta.tools ?? "").split(",").map((t) => t.trim()).filter(Boolean),
|
|
3004
|
+
body: m[2].replace(/^\n+/, "")
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
function genAgents(ctx, templatesDir) {
|
|
3008
|
+
const lang = ctx.config.language;
|
|
3009
|
+
const blocks = SHARED[lang] ?? SHARED.zh;
|
|
3010
|
+
const dir = templatesDir ?? join13(WORKBENCH_DIR, "templates/agents", lang);
|
|
3011
|
+
if (!existsSync11(dir)) throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${dir}`);
|
|
3012
|
+
const cli = ctx.config.cli;
|
|
3013
|
+
const baseTokens = {
|
|
3014
|
+
CLI: cli,
|
|
3015
|
+
TRUST_PROTOCOL: blocks.trust.replaceAll("{{CLI}}", cli),
|
|
3016
|
+
CLI_GUIDE: blocks.guide.replaceAll("{{CLI}}", cli),
|
|
3017
|
+
PATH_PROJECT: expandPath(ctx, "project"),
|
|
3018
|
+
PATH_ROLES: expandPath(ctx, "roles"),
|
|
3019
|
+
PATH_GLOSSARY: expandPath(ctx, "glossary"),
|
|
3020
|
+
PATH_FLOWS: expandPath(ctx, "flow"),
|
|
3021
|
+
PATH_MODULES: expandPath(ctx, "module-prd"),
|
|
3022
|
+
PATH_PAGES: expandPath(ctx, "page-prd"),
|
|
3023
|
+
PATH_DB_DOCS: expandPath(ctx, "db-doc"),
|
|
3024
|
+
PATH_API_DOCS: expandPath(ctx, "api-doc"),
|
|
3025
|
+
PATH_DESIGN_SYSTEMS: expandPath(ctx, "design-system"),
|
|
3026
|
+
PATH_DESIGN_PROMPTS: expandPath(ctx, "design-prompt"),
|
|
3027
|
+
PATH_PROTOTYPES: expandPath(ctx, "prototype"),
|
|
3028
|
+
PATH_ACCEPTANCE: expandPath(ctx, "acceptance"),
|
|
3029
|
+
ENDPOINTS: ctx.config.endpoints.join(" / "),
|
|
3030
|
+
PIPELINE: ctx.config.pipeline.join(" \u2192 "),
|
|
3031
|
+
CODE_ROOTS: ctx.config.endpoints.map((e) => `| ${e} | ${(ctx.config.codeRoots[e] ?? []).join(blocks.sep) || blocks.todo} |`).join("\n")
|
|
3032
|
+
};
|
|
3033
|
+
const renderTokens = (text, memory) => {
|
|
3034
|
+
let content = text;
|
|
3035
|
+
const tokens = { ...baseTokens, MEMORY: memory };
|
|
3036
|
+
for (const [token, value] of Object.entries(tokens)) content = content.replaceAll(`{{${token}}}`, value);
|
|
3037
|
+
const leftover = content.match(/\{\{[A-Z_]+\}\}/);
|
|
3038
|
+
if (leftover) throw new Error(`\u6A21\u677F\u5B58\u5728\u672A\u89E3\u6790 token: ${leftover[0]}`);
|
|
3039
|
+
return content;
|
|
3040
|
+
};
|
|
3041
|
+
const adapters = resolvePlatforms(ctx.config.platforms);
|
|
3042
|
+
const pipeline = new Set(ctx.config.pipeline);
|
|
3043
|
+
const written = [];
|
|
3044
|
+
for (const file of readdirSync5(dir)) {
|
|
3045
|
+
if (!file.endsWith(".md")) continue;
|
|
3046
|
+
const role = file.replace(/\.md$/, "");
|
|
3047
|
+
if (!pipeline.has(role)) continue;
|
|
3048
|
+
const tpl = parseTemplate(readFileSync6(join13(dir, file), "utf-8"));
|
|
3049
|
+
for (const adapter of adapters) {
|
|
3050
|
+
const memory = adapter.memoryBlock(role, lang);
|
|
3051
|
+
const spec = {
|
|
3052
|
+
name: tpl.name,
|
|
3053
|
+
description: renderTokens(tpl.description, memory),
|
|
3054
|
+
tools: tpl.tools,
|
|
3055
|
+
model: resolveModel(ctx.config.model, adapter),
|
|
3056
|
+
body: renderTokens(tpl.body, memory)
|
|
3057
|
+
};
|
|
3058
|
+
const rel = `${adapter.agentsDir}/${adapter.agentFile(role)}`;
|
|
3059
|
+
const abs = join13(ctx.root, rel);
|
|
3060
|
+
mkdirSync6(dirname3(abs), { recursive: true });
|
|
3061
|
+
writeFileSync5(abs, adapter.renderAgent(spec));
|
|
3062
|
+
written.push(rel);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return { written };
|
|
3066
|
+
}
|
|
3067
|
+
var TRUST_PROTOCOL_ZH, CLI_GUIDE_ZH, TRUST_PROTOCOL_EN, CLI_GUIDE_EN, SHARED;
|
|
3068
|
+
var init_gen_agents_command = __esm({
|
|
3069
|
+
"core/commands/gen-agents.command.ts"() {
|
|
3070
|
+
"use strict";
|
|
3071
|
+
init_config();
|
|
3072
|
+
init_kind();
|
|
3073
|
+
init_platforms();
|
|
3074
|
+
TRUST_PROTOCOL_ZH = `## \u4FE1\u4EFB\u534F\u8BAE(\u6700\u9AD8\u4F18\u5148\u7EA7)
|
|
3075
|
+
|
|
3076
|
+
| \u4E0A\u6E38\u4EA7\u7269\u72B6\u6001 | \u4F60\u7684\u884C\u4E3A |
|
|
3077
|
+
| --- | --- |
|
|
3078
|
+
| approved | \u89C6\u4E3A\u771F\u76F8\u76F4\u63A5\u4F7F\u7528;**\u7981\u6B62**\u91CD\u65B0\u63A8\u5BFC\u3001\u7981\u6B62\u5411\u7528\u6237\u91CD\u590D\u786E\u8BA4\u5DF2\u62CD\u677F\u5185\u5BB9 |
|
|
3079
|
+
| draft / pending(\u4ECE\u672A\u83B7\u6279) | \u53EF\u7528,\u4F46\u4EA7\u51FA\u4E2D\u6807\u6CE8"\u57FA\u4E8E\u672A\u5BA1\u6587\u6863";\u9047\u7591\u70B9\u505C\u4E0B\u6765\u95EE |
|
|
3080
|
+
| pending \u4E14\u66FE\u83B7\u6279(\u590D\u5BA1\u4E2D) | **\u7981\u7528**,\u7B49\u590D\u5BA1\u901A\u8FC7 |
|
|
3081
|
+
| invalidated | **\u7981\u7528**,\u505C\u6B62\u5E76\u8981\u6C42\u4E0A\u6E38\u590D\u5BA1 |
|
|
3082
|
+
|
|
3083
|
+
\u72B6\u6001\u67E5\u8BE2:\`{{CLI}} artifacts --module=<\u6A21\u5757>\`
|
|
3084
|
+
\u5BF9 approved \u5185\u5BB9\u6709\u5B9E\u8D28\u5F02\u8BAE\u65F6**\u7981\u6B62\u64C5\u81EA\u504F\u79BB**,\u7559\u75D5\u540E\u505C\u6B62\u7B49\u7528\u6237\u88C1\u51B3:
|
|
3085
|
+
\`{{CLI}} dispute --actor=<\u89D2\u8272> --reason="..." -- <\u6587\u4EF6\u8DEF\u5F84>\``;
|
|
3086
|
+
CLI_GUIDE_ZH = `## \u4EFB\u52A1\u64CD\u4F5C
|
|
3087
|
+
|
|
3088
|
+
MCP \u5DF2\u6CE8\u518C\u65F6\u4F18\u5148\u7528 \`wb_*\` typed tools(\u4E0E CLI \u540C\u6E90\u540C\u4E8B\u52A1);CLI \u7B49\u4EF7:
|
|
3089
|
+
|
|
3090
|
+
\`\`\`bash
|
|
3091
|
+
{{CLI}} list --role=<\u89D2\u8272> --status=pending # \u67E5\u770B\u5F85\u529E
|
|
3092
|
+
{{CLI}} claim <id> --assignee=<\u89D2\u8272> # \u9886\u53D6(gate \u81EA\u52A8\u6821\u9A8C,\u4F9D\u8D56\u81EA\u52A8\u5FEB\u7167)
|
|
3093
|
+
{{CLI}} input <id> --operator=<\u89D2\u8272> -- <\u8DEF\u5F84> # \u8865\u5145\u7533\u62A5 gate \u4E4B\u5916\u8BFB\u8FC7\u7684\u4EA7\u7269
|
|
3094
|
+
{{CLI}} output --module=<\u6A21\u5757> --role=<\u89D2\u8272> --endpoint=<\u7AEF> [--page=<\u6A21\u5757>/<\u9875\u9762>] -- <\u8DEF\u5F84>
|
|
3095
|
+
{{CLI}} submit --actor=<\u89D2\u8272> -- <\u8DEF\u5F84> # \u5951\u7EA6\u7C7B\u6587\u6863\u5199\u5B8C\u5373\u9001\u5BA1
|
|
3096
|
+
{{CLI}} update <id> --status=completed --operator=<\u89D2\u8272>
|
|
3097
|
+
{{CLI}} record <id> --operator=<\u89D2\u8272> "\u5907\u6CE8"
|
|
3098
|
+
\`\`\`
|
|
3099
|
+
|
|
3100
|
+
- \u9886\u53D6\u65F6 gate \u62A5\u9519\u90FD\u662F**\u53EF\u884C\u52A8\u7684**:\u6309\u63D0\u793A\u7B49\u4E0A\u6E38\u4EA7\u51FA/\u5BA1\u6279,\u7981\u6B62\u7ED5\u8FC7
|
|
3101
|
+
- \u4EFB\u52A1 stale(\u4E0A\u6E38\u53D8\u66F4)\u65F6 complete \u4F1A\u88AB\u62E6\u622A:\u5148\u5BF9\u9F50\u53D8\u66F4;\u786E\u8BA4\u65E0\u5F71\u54CD\u624D \`--force=true\`(\u7559\u75D5)
|
|
3102
|
+
- \u4EA7\u51FA\u5FC5\u987B**\u5148\u5199\u6587\u4EF6\u518D\u767B\u8BB0**;\u767B\u8BB0\u4F1A\u81EA\u52A8\u5173\u8054\u4F60\u9886\u53D6\u7684\u4EFB\u52A1
|
|
3103
|
+
- **git \u5F52\u56E0**:\u9886\u53D6\u4EFB\u52A1\u540E\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF \`WORKBENCH_TASK_ID=<\u4EFB\u52A1id>\`,
|
|
3104
|
+
\u63D0\u4EA4\u65F6 hook \u81EA\u52A8\u6CE8\u5165 \`Task: #id\` trailer(\u591A agent \u540C\u5206\u652F\u7684\u5F52\u56E0\u4F9D\u636E,\u4E0D\u8BBE\u5373\u4E3A\u5B64\u513F\u63D0\u4EA4)`;
|
|
3105
|
+
TRUST_PROTOCOL_EN = `## Trust Protocol (highest priority)
|
|
3106
|
+
|
|
3107
|
+
| Upstream state | Your behavior |
|
|
3108
|
+
| --- | --- |
|
|
3109
|
+
| approved | Treat as truth, use directly; **do not** re-derive or re-confirm settled content |
|
|
3110
|
+
| draft / pending (never approved) | Usable, but flag outputs as "based on unreviewed docs"; stop and ask on doubt |
|
|
3111
|
+
| pending & previously approved (under re-review) | **Forbidden**, wait for re-review |
|
|
3112
|
+
| invalidated | **Forbidden**, stop and require upstream re-review |
|
|
3113
|
+
|
|
3114
|
+
Query state: \`{{CLI}} artifacts --module=<module>\`
|
|
3115
|
+
On a substantive objection to approved content, **do not silently deviate** \u2014 leave a trace and stop for a ruling:
|
|
3116
|
+
\`{{CLI}} dispute --actor=<role> --reason="..." -- <file path>\``;
|
|
3117
|
+
CLI_GUIDE_EN = `## Task operations
|
|
3118
|
+
|
|
3119
|
+
When MCP is registered, prefer the \`wb_*\` typed tools (same source & transaction as the CLI); CLI equivalents:
|
|
3120
|
+
|
|
3121
|
+
\`\`\`bash
|
|
3122
|
+
{{CLI}} list --role=<role> --status=pending # your queue
|
|
3123
|
+
{{CLI}} claim <id> --assignee=<role> # claim (gate auto-checks, deps auto-snapshotted)
|
|
3124
|
+
{{CLI}} input <id> --operator=<role> -- <path> # declare a dep read outside the gate
|
|
3125
|
+
{{CLI}} output --module=<module> --role=<role> --endpoint=<endpoint> [--page=<module>/<page>] -- <path>
|
|
3126
|
+
{{CLI}} submit --actor=<role> -- <path> # submit a contract doc once written
|
|
3127
|
+
{{CLI}} update <id> --status=completed --operator=<role>
|
|
3128
|
+
{{CLI}} record <id> --operator=<role> "note"
|
|
3129
|
+
\`\`\`
|
|
3130
|
+
|
|
3131
|
+
- Gate errors on claim are **actionable**: follow them to wait for upstream output/approval, never bypass
|
|
3132
|
+
- A stale task (upstream changed) blocks complete: align with the change first; only \`--force=true\` (traced) once you confirm no impact
|
|
3133
|
+
- Outputs must be **written to a file, then registered**; registration auto-links your claimed task
|
|
3134
|
+
- **git attribution**: after claiming, set env var \`WORKBENCH_TASK_ID=<task id>\`; the commit hook injects a \`Task: #id\` trailer (attribution for multi-agent shared branches; unset = orphan commit)`;
|
|
3135
|
+
SHARED = {
|
|
3136
|
+
zh: { trust: TRUST_PROTOCOL_ZH, guide: CLI_GUIDE_ZH, todo: "(\u5F85\u914D\u7F6E:workbench.config.json \u7684 codeRoots)", sep: "\u3001" },
|
|
3137
|
+
en: { trust: TRUST_PROTOCOL_EN, guide: CLI_GUIDE_EN, todo: "(configure codeRoots in workbench.config.json)", sep: ", " }
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
});
|
|
3141
|
+
|
|
3142
|
+
// core/commands/audit.command.ts
|
|
3143
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
3144
|
+
import { join as join14 } from "node:path";
|
|
3145
|
+
function auditModule(ctx, moduleRaw) {
|
|
3146
|
+
const module = normalizeModule(moduleRaw, ctx.config);
|
|
3147
|
+
const kinds = contractKinds(ctx.config);
|
|
3148
|
+
const artifacts = ctx.db.prepare(`SELECT * FROM artifacts WHERE module = ? ORDER BY kind, path`).all(module);
|
|
3149
|
+
const contracts = artifacts.filter((a) => kinds.includes(a.kind)).map((a) => ({
|
|
3150
|
+
kind: a.kind,
|
|
3151
|
+
path: a.path,
|
|
3152
|
+
status: reviewStatus(a),
|
|
3153
|
+
everApproved: everApproved(a),
|
|
3154
|
+
onDisk: existsSync12(join14(ctx.root, a.path))
|
|
3155
|
+
}));
|
|
3156
|
+
const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk: existsSync12(join14(ctx.root, a.path)) }));
|
|
3157
|
+
return {
|
|
3158
|
+
module,
|
|
3159
|
+
cleared: moduleCleared(ctx.db, module),
|
|
3160
|
+
contracts,
|
|
3161
|
+
codeDirs,
|
|
3162
|
+
suggestedSubmits: contracts.filter((c) => c.status === "draft" && c.onDisk).map((c) => c.path)
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
var init_audit_command = __esm({
|
|
3166
|
+
"core/commands/audit.command.ts"() {
|
|
3167
|
+
"use strict";
|
|
3168
|
+
init_derive();
|
|
3169
|
+
init_kind();
|
|
3170
|
+
}
|
|
3171
|
+
});
|
|
3172
|
+
|
|
3173
|
+
// core/commands/intake.command.ts
|
|
3174
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3175
|
+
function intakeIssues(ctx) {
|
|
3176
|
+
const summary = { fetched: 0, created: [], skipped: 0 };
|
|
3177
|
+
let issues = [];
|
|
3178
|
+
try {
|
|
3179
|
+
const out = execFileSync3("gh", ["issue", "list", "--state", "open", "--json", "number,title,url,labels", "--limit", "50"], {
|
|
3180
|
+
cwd: ctx.root,
|
|
3181
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3182
|
+
}).toString();
|
|
3183
|
+
issues = JSON.parse(out);
|
|
3184
|
+
} catch {
|
|
3185
|
+
throw new Error("gh CLI \u4E0D\u53EF\u7528\u6216\u975E GitHub \u4ED3\u5E93(intake \u4F9D\u8D56 gh issue list)");
|
|
3186
|
+
}
|
|
3187
|
+
summary.fetched = issues.length;
|
|
3188
|
+
const exists = ctx.db.prepare("SELECT COUNT(*) AS c FROM tasks WHERE external_ref = ?");
|
|
3189
|
+
for (const issue of issues) {
|
|
3190
|
+
const ref = `gh#${issue.number}`;
|
|
3191
|
+
if (exists.get(ref).c > 0) {
|
|
3192
|
+
summary.skipped++;
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
const isBug = issue.labels.some((l) => l.name.toLowerCase() === "bug");
|
|
3196
|
+
const id = createTask(ctx, {
|
|
3197
|
+
role: isBug ? "developer" : "product-manager",
|
|
3198
|
+
endpoint: isBug ? null : null,
|
|
3199
|
+
type: isBug ? "hotfix" : "build",
|
|
3200
|
+
assignee: isBug ? "developer" : "product-manager",
|
|
3201
|
+
creator: "intake",
|
|
3202
|
+
content: `[gh#${issue.number}] ${issue.title}
|
|
3203
|
+
${issue.url}`,
|
|
3204
|
+
externalRef: ref
|
|
3205
|
+
});
|
|
3206
|
+
summary.created.push({ issue: issue.number, taskId: id, lane: isBug ? "hotfix" : "pm" });
|
|
3207
|
+
}
|
|
3208
|
+
return summary;
|
|
3209
|
+
}
|
|
3210
|
+
var init_intake_command = __esm({
|
|
3211
|
+
"core/commands/intake.command.ts"() {
|
|
3212
|
+
"use strict";
|
|
3213
|
+
init_task_commands();
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
|
|
3217
|
+
// core/commands/init.command.ts
|
|
3218
|
+
import {
|
|
3219
|
+
copyFileSync,
|
|
3220
|
+
existsSync as existsSync13,
|
|
3221
|
+
mkdirSync as mkdirSync7,
|
|
3222
|
+
readFileSync as readFileSync7,
|
|
3223
|
+
readdirSync as readdirSync6,
|
|
3224
|
+
writeFileSync as writeFileSync6
|
|
3225
|
+
} from "node:fs";
|
|
3226
|
+
import { dirname as dirname4, join as join15 } from "node:path";
|
|
3227
|
+
function deployPreset(root) {
|
|
3228
|
+
const deployed = [];
|
|
3229
|
+
if (!existsSync13(PRESET_DIR)) return deployed;
|
|
3230
|
+
const walk = (rel) => {
|
|
3231
|
+
for (const ent of readdirSync6(join15(PRESET_DIR, rel), {
|
|
3232
|
+
withFileTypes: true
|
|
3233
|
+
})) {
|
|
3234
|
+
const childRel = rel ? join15(rel, ent.name) : ent.name;
|
|
3235
|
+
if (ent.isDirectory()) {
|
|
3236
|
+
walk(childRel);
|
|
3237
|
+
} else if (ent.isFile()) {
|
|
3238
|
+
const dest = join15(root, childRel);
|
|
3239
|
+
if (existsSync13(dest)) continue;
|
|
3240
|
+
mkdirSync7(dirname4(dest), { recursive: true });
|
|
3241
|
+
copyFileSync(join15(PRESET_DIR, childRel), dest);
|
|
3242
|
+
deployed.push(childRel);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
walk("");
|
|
3247
|
+
return deployed;
|
|
3248
|
+
}
|
|
3249
|
+
function ensureRootTsx(root) {
|
|
3250
|
+
const pkgPath = join15(root, "package.json");
|
|
3251
|
+
if (!existsSync13(pkgPath)) return false;
|
|
3252
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
3253
|
+
if (pkg.devDependencies?.tsx) return false;
|
|
3254
|
+
pkg.devDependencies = { ...pkg.devDependencies ?? {}, tsx: TSX_VERSION };
|
|
3255
|
+
writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
3256
|
+
return true;
|
|
3257
|
+
}
|
|
3258
|
+
function initProject(root, opts) {
|
|
3259
|
+
const configPath = join15(root, CONFIG_FILENAME);
|
|
3260
|
+
if (existsSync13(configPath)) {
|
|
3261
|
+
throw new Error(
|
|
3262
|
+
`${CONFIG_FILENAME} \u5DF2\u5B58\u5728,init \u53EA\u7528\u4E8E\u7A7A\u9879\u76EE\u5F15\u5BFC(\u6539\u914D\u7F6E\u8BF7\u76F4\u63A5\u7F16\u8F91\u8BE5\u6587\u4EF6)`
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
if (opts.endpoints.length === 0)
|
|
3266
|
+
throw new Error("\u81F3\u5C11\u58F0\u660E\u4E00\u4E2A\u7AEF(--endpoints=service,...)");
|
|
3267
|
+
const hasFrontend = opts.endpoints.some((e) => e !== "service");
|
|
3268
|
+
const pipeline = opts.pipeline ?? (hasFrontend ? ["product-manager", "architect", "designer", "developer", "qa"] : ["product-manager", "architect", "developer", "qa"]);
|
|
3269
|
+
const config = {
|
|
3270
|
+
endpoints: opts.endpoints,
|
|
3271
|
+
pipeline,
|
|
3272
|
+
docs: {
|
|
3273
|
+
prd: "docs/prd",
|
|
3274
|
+
architecture: "docs/architecture",
|
|
3275
|
+
design: "docs/design",
|
|
3276
|
+
acceptance: "docs/acceptance"
|
|
3277
|
+
},
|
|
3278
|
+
codeRoots: opts.codeRoots ?? {},
|
|
3279
|
+
cli: WORKBENCH_BIN,
|
|
3280
|
+
machineChecks: { enabled: false },
|
|
3281
|
+
protocolLints: [],
|
|
3282
|
+
moduleMapping: {},
|
|
3283
|
+
feedbackHalfLifeDays: 15,
|
|
3284
|
+
gates: { approvalMode: "warn", writeGate: "observe" },
|
|
3285
|
+
git: { taskTrailer: "off", trailerKey: "Task" },
|
|
3286
|
+
platforms: opts.platforms && opts.platforms.length ? opts.platforms : ["claude"],
|
|
3287
|
+
language: opts.language ?? "zh",
|
|
3288
|
+
...opts.model ? { model: opts.model } : {}
|
|
3289
|
+
};
|
|
3290
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3291
|
+
const scaffolded = [];
|
|
3292
|
+
if (opts.scaffold !== false) {
|
|
3293
|
+
for (const dir of DOC_DIRS) {
|
|
3294
|
+
const abs = join15(root, dir);
|
|
3295
|
+
if (!existsSync13(abs)) {
|
|
3296
|
+
mkdirSync7(abs, { recursive: true });
|
|
3297
|
+
writeFileSync6(join15(abs, ".gitkeep"), "");
|
|
3298
|
+
scaffolded.push(dir);
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
const preset = opts.preset !== false ? deployPreset(root) : [];
|
|
3303
|
+
const rootTsxAdded = ensureRootTsx(root);
|
|
3304
|
+
const ctx = openWorkbenchAt(root);
|
|
3305
|
+
const { written } = genAgents(ctx);
|
|
3306
|
+
const meta = registerMetaArtifacts(ctx);
|
|
3307
|
+
const adapters = resolvePlatforms(config.platforms);
|
|
3308
|
+
const server = {
|
|
3309
|
+
name: "opcflow",
|
|
3310
|
+
command: "npx",
|
|
3311
|
+
args: ["-y", PKG_NAME, "mcp"]
|
|
3312
|
+
};
|
|
3313
|
+
const mcpPaths = [];
|
|
3314
|
+
if (opts.mcp !== false) for (const a of adapters) mcpPaths.push(a.writeMcp(root, server));
|
|
3315
|
+
const hookPaths = [];
|
|
3316
|
+
if (opts.writeHooks !== false) {
|
|
3317
|
+
const preBase = `${WORKBENCH_BIN} hook pre`;
|
|
3318
|
+
const postBase = `${WORKBENCH_BIN} hook post`;
|
|
3319
|
+
for (const a of adapters)
|
|
3320
|
+
hookPaths.push(
|
|
3321
|
+
...a.writeHooks(root, {
|
|
3322
|
+
preCommand: `${preBase} --platform=${a.id}`,
|
|
3323
|
+
postCommand: `${postBase} --platform=${a.id}`
|
|
3324
|
+
})
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
const notes = adapters.flatMap((a) => a.notes);
|
|
3328
|
+
let hooks = [];
|
|
3329
|
+
if (opts.gitHooks !== false && existsSync13(join15(root, ".git"))) {
|
|
3330
|
+
try {
|
|
3331
|
+
hooks = installGitHooks(ctx);
|
|
3332
|
+
} catch {
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
return {
|
|
3336
|
+
ctx,
|
|
3337
|
+
configPath,
|
|
3338
|
+
agents: written,
|
|
3339
|
+
metaRegistered: meta.registered.length,
|
|
3340
|
+
hooks,
|
|
3341
|
+
scaffolded,
|
|
3342
|
+
mcpPaths,
|
|
3343
|
+
hookPaths,
|
|
3344
|
+
platforms: config.platforms,
|
|
3345
|
+
notes,
|
|
3346
|
+
preset,
|
|
3347
|
+
rootTsxAdded
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
var DOC_DIRS, PRESET_DIR, TSX_VERSION;
|
|
3351
|
+
var init_init_command = __esm({
|
|
3352
|
+
"core/commands/init.command.ts"() {
|
|
3353
|
+
"use strict";
|
|
3354
|
+
init_config();
|
|
3355
|
+
init_db();
|
|
3356
|
+
init_platforms();
|
|
3357
|
+
init_gen_agents_command();
|
|
3358
|
+
init_install_hooks_command();
|
|
3359
|
+
init_meta_command();
|
|
3360
|
+
DOC_DIRS = [
|
|
3361
|
+
"docs/prd/flows",
|
|
3362
|
+
"docs/prd/modules",
|
|
3363
|
+
"docs/prd/pages",
|
|
3364
|
+
"docs/architecture/database",
|
|
3365
|
+
"docs/architecture/api",
|
|
3366
|
+
"docs/design/systems",
|
|
3367
|
+
"docs/design/prompts",
|
|
3368
|
+
"docs/design/prototypes",
|
|
3369
|
+
"docs/acceptance"
|
|
3370
|
+
];
|
|
3371
|
+
PRESET_DIR = join15(WORKBENCH_DIR, "preset");
|
|
3372
|
+
TSX_VERSION = "^4";
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
|
|
3376
|
+
// core/index.ts
|
|
3377
|
+
var core_exports = {};
|
|
3378
|
+
__export(core_exports, {
|
|
3379
|
+
AUTO_ACTOR_WEIGHT: () => AUTO_ACTOR_WEIGHT,
|
|
3380
|
+
CONFIG_FILENAME: () => CONFIG_FILENAME,
|
|
3381
|
+
CURRENT_SCHEMA_VERSION: () => CURRENT_SCHEMA_VERSION,
|
|
3382
|
+
DEFAULT_KIND_REGISTRY: () => DEFAULT_KIND_REGISTRY,
|
|
3383
|
+
KIND_TIERS: () => KIND_TIERS,
|
|
3384
|
+
META_KINDS: () => META_KINDS,
|
|
3385
|
+
MIGRATIONS: () => MIGRATIONS,
|
|
3386
|
+
PKG_NAME: () => PKG_NAME,
|
|
3387
|
+
PLATFORM_IDS: () => PLATFORM_IDS,
|
|
3388
|
+
PM_KINDS: () => PM_KINDS,
|
|
3389
|
+
RED_FLAG_THRESHOLD: () => RED_FLAG_THRESHOLD,
|
|
3390
|
+
SCHEMA_V1: () => SCHEMA_V1,
|
|
3391
|
+
SKILL_CANDIDATE_THRESHOLD: () => SKILL_CANDIDATE_THRESHOLD,
|
|
3392
|
+
WORKBENCH_BIN: () => WORKBENCH_BIN,
|
|
3393
|
+
WORKBENCH_DIR: () => WORKBENCH_DIR,
|
|
3394
|
+
addTaskInput: () => addTaskInput,
|
|
3395
|
+
approvalStats: () => approvalStats,
|
|
3396
|
+
approveArtifact: () => approveArtifact,
|
|
3397
|
+
approvedContent: () => approvedContent,
|
|
3398
|
+
auditModule: () => auditModule,
|
|
3399
|
+
buildTree: () => buildTree,
|
|
3400
|
+
claimTask: () => claimTask,
|
|
3401
|
+
closeLinkedIssue: () => closeLinkedIssue,
|
|
3402
|
+
contractKinds: () => contractKinds,
|
|
3403
|
+
createTask: () => createTask,
|
|
3404
|
+
deriveEdges: () => deriveEdges,
|
|
3405
|
+
detectOrphanCommit: () => detectOrphanCommit,
|
|
3406
|
+
disputeArtifact: () => disputeArtifact,
|
|
3407
|
+
distillFeedback: () => distillFeedback,
|
|
3408
|
+
ensureSchema: () => ensureSchema,
|
|
3409
|
+
everApproved: () => everApproved,
|
|
3410
|
+
exportEventLog: () => exportEventLog,
|
|
3411
|
+
feedbackArtifact: () => feedbackArtifact,
|
|
3412
|
+
findProjectRoot: () => findProjectRoot,
|
|
3413
|
+
genAgents: () => genAgents,
|
|
3414
|
+
getAdapter: () => getAdapter,
|
|
3415
|
+
getKindRegistry: () => getKindRegistry,
|
|
3416
|
+
getTaskDetail: () => getTaskDetail,
|
|
3417
|
+
getTaskRow: () => getTaskRow,
|
|
3418
|
+
gitHead: () => gitHead,
|
|
3419
|
+
graphModule: () => graphModule,
|
|
3420
|
+
hashDirectory: () => hashDirectory,
|
|
3421
|
+
hashFile: () => hashFile,
|
|
3422
|
+
hashPath: () => hashPath,
|
|
3423
|
+
headCommitInfo: () => headCommitInfo,
|
|
3424
|
+
inferKind: () => inferKind,
|
|
3425
|
+
initProject: () => initProject,
|
|
3426
|
+
installGitHooks: () => installGitHooks,
|
|
3427
|
+
intakeIssues: () => intakeIssues,
|
|
3428
|
+
kindSpec: () => kindSpec,
|
|
3429
|
+
listArtifacts: () => listArtifacts,
|
|
3430
|
+
listEvents: () => listEvents,
|
|
3431
|
+
listTasks: () => listTasks,
|
|
3432
|
+
loadConfig: () => loadConfig,
|
|
3433
|
+
logEvent: () => logEvent,
|
|
3434
|
+
migrateLegacy: () => migrateLegacy,
|
|
3435
|
+
moduleCleared: () => moduleCleared,
|
|
3436
|
+
moveArtifact: () => moveArtifact,
|
|
3437
|
+
nodeDetail: () => nodeDetail,
|
|
3438
|
+
normalizeModule: () => normalizeModule,
|
|
3439
|
+
normalizeRelPath: () => normalizeRelPath,
|
|
3440
|
+
openWorkbench: () => openWorkbench,
|
|
3441
|
+
openWorkbenchAt: () => openWorkbenchAt,
|
|
3442
|
+
ownerRole: () => ownerRole,
|
|
3443
|
+
planModule: () => planModule,
|
|
3444
|
+
prototypeEndorsed: () => prototypeEndorsed,
|
|
3445
|
+
recordNote: () => recordNote,
|
|
3446
|
+
recordQaResult: () => recordQaResult,
|
|
3447
|
+
refreshArtifact: () => refreshArtifact,
|
|
3448
|
+
registerMetaArtifacts: () => registerMetaArtifacts,
|
|
3449
|
+
registerOutput: () => registerOutput,
|
|
3450
|
+
rejectArtifact: () => rejectArtifact,
|
|
3451
|
+
removeTask: () => removeTask,
|
|
3452
|
+
resolveArtifact: () => resolveArtifact,
|
|
3453
|
+
resolveModel: () => resolveModel,
|
|
3454
|
+
resolvePlatforms: () => resolvePlatforms,
|
|
3455
|
+
reviewStatus: () => reviewStatus,
|
|
3456
|
+
runProtocolLints: () => runProtocolLints,
|
|
3457
|
+
runRetrospective: () => runRetrospective,
|
|
3458
|
+
scanArtifacts: () => scanArtifacts,
|
|
3459
|
+
spawnFollowupQa: () => spawnFollowupQa,
|
|
3460
|
+
spawnReviews: () => spawnReviews,
|
|
3461
|
+
submitArtifact: () => submitArtifact,
|
|
3462
|
+
syncArtifacts: () => syncArtifacts,
|
|
3463
|
+
taskStaleness: () => taskStaleness,
|
|
3464
|
+
touchedByTaskTrailer: () => touchedByTaskTrailer,
|
|
3465
|
+
touchedSince: () => touchedSince,
|
|
3466
|
+
updateTask: () => updateTask,
|
|
3467
|
+
validateClaim: () => validateClaim,
|
|
3468
|
+
validateComplete: () => validateComplete
|
|
3469
|
+
});
|
|
3470
|
+
var init_core = __esm({
|
|
3471
|
+
"core/index.ts"() {
|
|
3472
|
+
"use strict";
|
|
3473
|
+
init_types();
|
|
3474
|
+
init_config();
|
|
3475
|
+
init_db();
|
|
3476
|
+
init_hash();
|
|
3477
|
+
init_events();
|
|
3478
|
+
init_kind();
|
|
3479
|
+
init_platforms();
|
|
3480
|
+
init_derive();
|
|
3481
|
+
init_lints();
|
|
3482
|
+
init_tree();
|
|
3483
|
+
init_gates();
|
|
3484
|
+
init_task_commands();
|
|
3485
|
+
init_artifact_commands();
|
|
3486
|
+
init_graph_command();
|
|
3487
|
+
init_migrate_legacy_command();
|
|
3488
|
+
init_meta_command();
|
|
3489
|
+
init_scan_command();
|
|
3490
|
+
init_sync_command();
|
|
3491
|
+
init_qa_command();
|
|
3492
|
+
init_retro_command();
|
|
3493
|
+
init_plan_command();
|
|
3494
|
+
init_install_hooks_command();
|
|
3495
|
+
init_gen_agents_command();
|
|
3496
|
+
init_audit_command();
|
|
3497
|
+
init_intake_command();
|
|
3498
|
+
init_init_command();
|
|
3499
|
+
init_gh();
|
|
3500
|
+
init_git();
|
|
3501
|
+
}
|
|
3502
|
+
});
|
|
3503
|
+
|
|
3504
|
+
// server/mcp.ts
|
|
3505
|
+
var mcp_exports = {};
|
|
3506
|
+
__export(mcp_exports, {
|
|
3507
|
+
buildMcpServer: () => buildMcpServer
|
|
3508
|
+
});
|
|
3509
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3510
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3511
|
+
import { z } from "zod";
|
|
3512
|
+
function buildMcpServer(ctx) {
|
|
3513
|
+
const server = new McpServer({ name: "opcflow", version: "0.1.0" });
|
|
3514
|
+
server.registerTool(
|
|
3515
|
+
"wb_list_tasks",
|
|
3516
|
+
{
|
|
3517
|
+
description: "\u67E5\u770B\u4EFB\u52A1\u5217\u8868(gate \u62A5\u9519\u53EF\u884C\u52A8;\u9886\u53D6\u7528 wb_claim)",
|
|
3518
|
+
inputSchema: { role: z.string().optional(), status: z.string().optional(), module: z.string().optional(), type: z.string().optional() }
|
|
3519
|
+
},
|
|
3520
|
+
async (args) => json(listTasks(ctx, { ...args, withStale: true }))
|
|
3521
|
+
);
|
|
3522
|
+
server.registerTool(
|
|
3523
|
+
"wb_claim",
|
|
3524
|
+
{ description: "\u9886\u53D6\u4EFB\u52A1(gate \u81EA\u52A8\u6821\u9A8C,\u4E0A\u6E38\u4F9D\u8D56\u81EA\u52A8\u5FEB\u7167)", inputSchema: { id: z.number(), assignee: z.string() } },
|
|
3525
|
+
async (args) => json(claimTask(ctx, args))
|
|
3526
|
+
);
|
|
3527
|
+
server.registerTool(
|
|
3528
|
+
"wb_update_status",
|
|
3529
|
+
{
|
|
3530
|
+
description: "\u66F4\u65B0\u4EFB\u52A1\u72B6\u6001;completed \u4F1A\u8DD1 stale \u62E6\u622A/\u4EA7\u51FA\u4E49\u52A1/\u534F\u8BAE lint/\u673A\u5668\u68C0\u67E5",
|
|
3531
|
+
inputSchema: { id: z.number(), status: z.string(), operator: z.string(), force: z.boolean().optional() }
|
|
3532
|
+
},
|
|
3533
|
+
async (args) => json(updateTask(ctx, args))
|
|
3534
|
+
);
|
|
3535
|
+
server.registerTool(
|
|
3536
|
+
"wb_output",
|
|
3537
|
+
{
|
|
3538
|
+
description: "\u767B\u8BB0\u4EA7\u51FA\u6587\u4EF6(\u5FC5\u987B\u5148\u5199\u6587\u4EF6;\u81EA\u52A8\u63A8\u65AD kind \u5E76\u5173\u8054\u5DF2\u9886\u53D6\u4EFB\u52A1)",
|
|
3539
|
+
inputSchema: {
|
|
3540
|
+
role: z.string(),
|
|
3541
|
+
endpoint: z.string().optional(),
|
|
3542
|
+
module: z.string().optional(),
|
|
3543
|
+
page: z.string().optional(),
|
|
3544
|
+
filePath: z.string()
|
|
3545
|
+
}
|
|
3546
|
+
},
|
|
3547
|
+
async (args) => json(registerOutput(ctx, { ...args, endpoint: args.endpoint ?? "common" }))
|
|
3548
|
+
);
|
|
3549
|
+
server.registerTool(
|
|
3550
|
+
"wb_submit",
|
|
3551
|
+
{ description: "\u5951\u7EA6\u6587\u6863\u9001\u5BA1(\u8FDB\u5165\u7528\u6237\u5F85\u5BA1\u961F\u5217)", inputSchema: { path: z.string(), actor: z.string() } },
|
|
3552
|
+
async (args) => json(submitArtifact(ctx, { path: args.path }, args.actor))
|
|
3553
|
+
);
|
|
3554
|
+
server.registerTool(
|
|
3555
|
+
"wb_feedback",
|
|
3556
|
+
{
|
|
3557
|
+
description: "\u5BF9\u539F\u578B/\u4EE3\u7801\u4EA7\u7269\u8BB0\u5F55 \u{1F44D}(+1)/\u{1F44E}(-1);\u{1F44E} \u5FC5\u9644 comment;\u539F\u578B \u{1F44D}=\u653E\u884C",
|
|
3558
|
+
inputSchema: { path: z.string(), verdict: z.union([z.literal(1), z.literal(-1)]), comment: z.string().optional(), actor: z.string() }
|
|
3559
|
+
},
|
|
3560
|
+
async (args) => json(feedbackArtifact(ctx, { path: args.path }, args))
|
|
3561
|
+
);
|
|
3562
|
+
server.registerTool(
|
|
3563
|
+
"wb_dispute",
|
|
3564
|
+
{
|
|
3565
|
+
description: "\u5BF9 approved \u5185\u5BB9\u6709\u5B9E\u8D28\u5F02\u8BAE\u65F6\u7559\u75D5\u5E76\u505C\u6B62(\u7B49\u7528\u6237\u88C1\u51B3,\u7981\u6B62\u64C5\u81EA\u504F\u79BB)",
|
|
3566
|
+
inputSchema: { path: z.string(), actor: z.string(), reason: z.string() }
|
|
3567
|
+
},
|
|
3568
|
+
async (args) => {
|
|
3569
|
+
disputeArtifact(ctx, { path: args.path }, args.actor, args.reason);
|
|
3570
|
+
return json({ ok: true });
|
|
3571
|
+
}
|
|
3572
|
+
);
|
|
3573
|
+
server.registerTool(
|
|
3574
|
+
"wb_input",
|
|
3575
|
+
{
|
|
3576
|
+
description: "\u8865\u5145\u7533\u62A5\u4EFB\u52A1\u4F9D\u8D56(gate \u4E4B\u5916\u8BFB\u8FC7\u7684\u767B\u8BB0\u4EA7\u7269,\u8FDB\u5165 stale \u76D1\u63A7)",
|
|
3577
|
+
inputSchema: { id: z.number(), path: z.string(), operator: z.string() }
|
|
3578
|
+
},
|
|
3579
|
+
async (args) => {
|
|
3580
|
+
addTaskInput(ctx, args);
|
|
3581
|
+
return json({ ok: true });
|
|
3582
|
+
}
|
|
3583
|
+
);
|
|
3584
|
+
server.registerTool(
|
|
3585
|
+
"wb_record",
|
|
3586
|
+
{ description: "\u4EFB\u52A1\u5907\u6CE8\u7559\u75D5", inputSchema: { id: z.number(), content: z.string(), operator: z.string() } },
|
|
3587
|
+
async (args) => {
|
|
3588
|
+
recordNote(ctx, args);
|
|
3589
|
+
return json({ ok: true });
|
|
3590
|
+
}
|
|
3591
|
+
);
|
|
3592
|
+
server.registerTool(
|
|
3593
|
+
"wb_plan",
|
|
3594
|
+
{ description: "\u6309\u5DF2\u767B\u8BB0 page-prd \u6D3E\u53D1\u6A21\u5757\u6574\u7EC4\u4EFB\u52A1(\u5E42\u7B49,\u5220\u9875\u81EA\u52A8 cancel)", inputSchema: { module: z.string(), creator: z.string().optional() } },
|
|
3595
|
+
async (args) => json(planModule(ctx, args.module, args.creator))
|
|
3596
|
+
);
|
|
3597
|
+
server.registerTool(
|
|
3598
|
+
"wb_sync",
|
|
3599
|
+
{ description: "\u5168\u91CF\u5BF9\u8D26:\u5931\u6548\u68C0\u6D4B+review \u6D3E\u53D1+tombstone", inputSchema: {} },
|
|
3600
|
+
async () => json(syncArtifacts(ctx, "mcp"))
|
|
3601
|
+
);
|
|
3602
|
+
server.registerTool(
|
|
3603
|
+
"wb_audit",
|
|
3604
|
+
{ description: "\u6A21\u5757\u5BF9\u8D26\u62A5\u544A(\u6E05\u7B97\u72B6\u6001/\u5951\u7EA6\u4FE1\u4EFB\u72B6\u6001/\u9001\u5BA1\u5EFA\u8BAE)", inputSchema: { module: z.string() } },
|
|
3605
|
+
async (args) => json(auditModule(ctx, args.module))
|
|
3606
|
+
);
|
|
3607
|
+
server.registerTool(
|
|
3608
|
+
"wb_qa",
|
|
3609
|
+
{
|
|
3610
|
+
description: "\u8BB0\u5F55 QA \u9A8C\u6536\u7ED3\u679C;fail \u5FC5\u9644 reason(\u81EA\u52A8\u6D3E rework,\u5B8C\u6210\u540E\u81EA\u52A8\u590D\u9A8C)",
|
|
3611
|
+
inputSchema: { id: z.number(), result: z.enum(["pass", "fail"]), reason: z.string().optional(), operator: z.string() }
|
|
3612
|
+
},
|
|
3613
|
+
async (args) => json(recordQaResult(ctx, args))
|
|
3614
|
+
);
|
|
3615
|
+
server.registerTool(
|
|
3616
|
+
"wb_retro",
|
|
3617
|
+
{
|
|
3618
|
+
description: "retrospective \u8BC1\u636E\u5305:\u53CD\u9988\u534A\u8870\u671F\u52A0\u6743\u63D0\u70BC(skill \u5019\u9009/Red Flags/\u89C2\u5BDF)+ \u5BA1\u6279\u541E\u5410\u62A5\u8868\u3002\u4F9D\u636E evidence \u8D77\u8349 .claude/skills/<\u540D\u79F0>/SKILL.md \u540E register-meta + submit \u9001\u4EBA\u5BA1,approved \u624D\u751F\u6548",
|
|
3619
|
+
inputSchema: { module: z.string().optional() }
|
|
3620
|
+
},
|
|
3621
|
+
async (args) => json(runRetrospective(ctx, args))
|
|
3622
|
+
);
|
|
3623
|
+
server.registerTool(
|
|
3624
|
+
"wb_artifacts",
|
|
3625
|
+
{
|
|
3626
|
+
description: "\u67E5\u770B\u4EA7\u7269\u4FE1\u4EFB\u72B6\u6001(approved=\u771F\u76F8\u76F4\u63A5\u7528;invalidated/\u590D\u5BA1\u4E2D=\u7981\u7528)",
|
|
3627
|
+
inputSchema: { module: z.string().optional(), kind: z.string().optional(), endpoint: z.string().optional() }
|
|
3628
|
+
},
|
|
3629
|
+
async (args) => json(listArtifacts(ctx, args))
|
|
3630
|
+
);
|
|
3631
|
+
return server;
|
|
3632
|
+
}
|
|
3633
|
+
var json;
|
|
3634
|
+
var init_mcp = __esm({
|
|
3635
|
+
"server/mcp.ts"() {
|
|
3636
|
+
"use strict";
|
|
3637
|
+
init_core();
|
|
3638
|
+
json = (data) => ({ content: [{ type: "text", text: JSON.stringify(data, null, 2) }] });
|
|
3639
|
+
if (process.argv[1]?.replace(/\\/g, "/").endsWith("server/mcp.ts")) {
|
|
3640
|
+
Promise.resolve().then(() => (init_db(), db_exports)).then(async ({ openWorkbench: openWorkbench2 }) => {
|
|
3641
|
+
const ctx = openWorkbench2(process.env.WORKBENCH_PROJECT);
|
|
3642
|
+
const server = buildMcpServer(ctx);
|
|
3643
|
+
await server.connect(new StdioServerTransport());
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
});
|
|
3648
|
+
|
|
3649
|
+
// server/app.ts
|
|
3650
|
+
var app_exports = {};
|
|
3651
|
+
__export(app_exports, {
|
|
3652
|
+
createServer: () => createServer
|
|
3653
|
+
});
|
|
3654
|
+
import fastifyCors from "@fastify/cors";
|
|
3655
|
+
import fastifyStatic from "@fastify/static";
|
|
3656
|
+
import Fastify from "fastify";
|
|
3657
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
|
|
3658
|
+
import { isAbsolute as isAbsolute2, join as join16, normalize, relative as relative2 } from "node:path";
|
|
3659
|
+
function getArtifact(ctx, id) {
|
|
3660
|
+
const row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(id);
|
|
3661
|
+
if (!row) throw Object.assign(new Error(`artifact #${id} \u4E0D\u5B58\u5728`), { statusCode: 404 });
|
|
3662
|
+
return row;
|
|
3663
|
+
}
|
|
3664
|
+
function guardedAbsPath(ctx, artifact, rel) {
|
|
3665
|
+
const base = normalize(join16(ctx.root, artifact.path));
|
|
3666
|
+
if (!rel) return base;
|
|
3667
|
+
const target = normalize(join16(base, rel));
|
|
3668
|
+
const r = relative2(base, target);
|
|
3669
|
+
if (r === "" || r === "." || r.startsWith("..") || isAbsolute2(r)) {
|
|
3670
|
+
throw Object.assign(new Error("\u8DEF\u5F84\u8D8A\u754C"), { statusCode: 403 });
|
|
3671
|
+
}
|
|
3672
|
+
return target;
|
|
3673
|
+
}
|
|
3674
|
+
async function createServer(ctx) {
|
|
3675
|
+
const app = Fastify({ logger: false });
|
|
3676
|
+
await app.register(fastifyCors, { origin: true });
|
|
3677
|
+
const webDist = join16(WORKBENCH_DIR, "web/dist");
|
|
3678
|
+
if (existsSync14(webDist)) {
|
|
3679
|
+
await app.register(fastifyStatic, { root: webDist });
|
|
3680
|
+
}
|
|
3681
|
+
app.get("/api/meta", async () => ({
|
|
3682
|
+
root: ctx.root,
|
|
3683
|
+
language: ctx.config.language,
|
|
3684
|
+
pipeline: ctx.config.pipeline,
|
|
3685
|
+
endpoints: ctx.config.endpoints,
|
|
3686
|
+
approvalMode: ctx.config.gates.approvalMode
|
|
3687
|
+
}));
|
|
3688
|
+
app.get("/api/tree", async (req) => {
|
|
3689
|
+
return buildTree(ctx, { includeMeta: req.query.includeMeta === "1" });
|
|
3690
|
+
});
|
|
3691
|
+
app.get(
|
|
3692
|
+
"/api/node",
|
|
3693
|
+
async (req) => {
|
|
3694
|
+
const { module, endpoint, page, includeMeta } = req.query;
|
|
3695
|
+
const isBucket = module === "__project__" || module === "__meta__";
|
|
3696
|
+
return nodeDetail(ctx, {
|
|
3697
|
+
module: isBucket ? null : module,
|
|
3698
|
+
endpoint: endpoint || null,
|
|
3699
|
+
page: page || null,
|
|
3700
|
+
includeMeta: includeMeta === "1",
|
|
3701
|
+
metaOnly: module === "__meta__"
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
);
|
|
3705
|
+
app.get("/api/artifact/:id", async (req) => {
|
|
3706
|
+
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3707
|
+
const abs = guardedAbsPath(ctx, artifact);
|
|
3708
|
+
const feedback = ctx.db.prepare("SELECT * FROM artifact_feedback WHERE artifact_id = ? ORDER BY id DESC").all(artifact.id);
|
|
3709
|
+
const events = listEvents(ctx.db, { entityType: "artifact", entityId: artifact.id, limit: 50 });
|
|
3710
|
+
let content = null;
|
|
3711
|
+
let isDirectory = false;
|
|
3712
|
+
let missing = false;
|
|
3713
|
+
if (!existsSync14(abs)) {
|
|
3714
|
+
missing = true;
|
|
3715
|
+
} else if (statSync6(abs).isDirectory()) {
|
|
3716
|
+
isDirectory = true;
|
|
3717
|
+
} else if (statSync6(abs).size <= MAX_INLINE_SIZE) {
|
|
3718
|
+
content = readFileSync8(abs, "utf-8");
|
|
3719
|
+
}
|
|
3720
|
+
return { artifact, content, isDirectory, missing, feedback, events };
|
|
3721
|
+
});
|
|
3722
|
+
app.get("/api/artifact/:id/raw", async (req, reply) => {
|
|
3723
|
+
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3724
|
+
const abs = guardedAbsPath(ctx, artifact);
|
|
3725
|
+
if (!existsSync14(abs) || statSync6(abs).isDirectory()) {
|
|
3726
|
+
return reply.code(404).send("not found");
|
|
3727
|
+
}
|
|
3728
|
+
reply.header("content-type", artifact.path.endsWith(".html") ? "text/html; charset=utf-8" : "text/plain; charset=utf-8");
|
|
3729
|
+
return reply.send(readFileSync8(abs));
|
|
3730
|
+
});
|
|
3731
|
+
app.get("/api/artifact/:id/files", async (req) => {
|
|
3732
|
+
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3733
|
+
const base = guardedAbsPath(ctx, artifact);
|
|
3734
|
+
const files = [];
|
|
3735
|
+
const walk = (dir, rel) => {
|
|
3736
|
+
for (const name of readdirSync7(dir).sort()) {
|
|
3737
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
3738
|
+
const full = join16(dir, name);
|
|
3739
|
+
const relPath = rel ? `${rel}/${name}` : name;
|
|
3740
|
+
const st = statSync6(full);
|
|
3741
|
+
if (st.isDirectory()) walk(full, relPath);
|
|
3742
|
+
else files.push({ rel: relPath, size: st.size });
|
|
3743
|
+
}
|
|
3744
|
+
};
|
|
3745
|
+
if (existsSync14(base) && statSync6(base).isDirectory()) walk(base, "");
|
|
3746
|
+
return { files };
|
|
3747
|
+
});
|
|
3748
|
+
app.get("/api/artifact/:id/file", async (req) => {
|
|
3749
|
+
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3750
|
+
const abs = guardedAbsPath(ctx, artifact, req.query.rel);
|
|
3751
|
+
if (!existsSync14(abs) || statSync6(abs).isDirectory()) {
|
|
3752
|
+
throw Object.assign(new Error("\u6587\u4EF6\u4E0D\u5B58\u5728"), { statusCode: 404 });
|
|
3753
|
+
}
|
|
3754
|
+
if (statSync6(abs).size > MAX_INLINE_SIZE) {
|
|
3755
|
+
throw Object.assign(new Error("\u6587\u4EF6\u8FC7\u5927"), { statusCode: 413 });
|
|
3756
|
+
}
|
|
3757
|
+
return { rel: req.query.rel, content: readFileSync8(abs, "utf-8") };
|
|
3758
|
+
});
|
|
3759
|
+
app.get("/api/events", async (req) => {
|
|
3760
|
+
return listEvents(ctx.db, {
|
|
3761
|
+
afterId: req.query.afterId ? parseInt(req.query.afterId) : void 0,
|
|
3762
|
+
module: req.query.module,
|
|
3763
|
+
limit: req.query.limit ? parseInt(req.query.limit) : 100
|
|
3764
|
+
});
|
|
3765
|
+
});
|
|
3766
|
+
app.get("/api/sse", (req, reply) => {
|
|
3767
|
+
reply.raw.writeHead(200, {
|
|
3768
|
+
"content-type": "text/event-stream",
|
|
3769
|
+
"cache-control": "no-cache",
|
|
3770
|
+
connection: "keep-alive",
|
|
3771
|
+
"access-control-allow-origin": "*"
|
|
3772
|
+
});
|
|
3773
|
+
let cursor2 = ctx.db.prepare("SELECT MAX(id) AS id FROM events").get().id ?? 0;
|
|
3774
|
+
reply.raw.write(`event: hello
|
|
3775
|
+
data: {"cursor":${cursor2}}
|
|
3776
|
+
|
|
3777
|
+
`);
|
|
3778
|
+
const timer = setInterval(() => {
|
|
3779
|
+
try {
|
|
3780
|
+
const rows = listEvents(ctx.db, { afterId: cursor2, limit: 200 });
|
|
3781
|
+
if (rows.length > 0) {
|
|
3782
|
+
cursor2 = rows[rows.length - 1].id;
|
|
3783
|
+
reply.raw.write(`data: ${JSON.stringify(rows)}
|
|
3784
|
+
|
|
3785
|
+
`);
|
|
3786
|
+
}
|
|
3787
|
+
} catch {
|
|
3788
|
+
}
|
|
3789
|
+
}, 500);
|
|
3790
|
+
req.raw.on("close", () => clearInterval(timer));
|
|
3791
|
+
});
|
|
3792
|
+
app.get("/api/review-queue", async () => {
|
|
3793
|
+
return listArtifacts(ctx, {}).filter((r) => r.review_status === "pending" || r.review_status === "invalidated").map((r) => ({
|
|
3794
|
+
...r,
|
|
3795
|
+
ever_approved: everApproved(r),
|
|
3796
|
+
endorsed: r.kind === "prototype" ? prototypeEndorsed(ctx.db, r) : false
|
|
3797
|
+
}));
|
|
3798
|
+
});
|
|
3799
|
+
app.get("/api/skill-candidates", async () => {
|
|
3800
|
+
const report = runRetrospective(ctx);
|
|
3801
|
+
return {
|
|
3802
|
+
groups: report.groups.filter((g) => g.bucket === "skill-candidate" || g.bucket === "red-flag"),
|
|
3803
|
+
candidates: report.candidates,
|
|
3804
|
+
redFlags: report.redFlags,
|
|
3805
|
+
halfLifeDays: report.halfLifeDays,
|
|
3806
|
+
guidance: report.guidance
|
|
3807
|
+
};
|
|
3808
|
+
});
|
|
3809
|
+
app.get("/api/artifact/:id/diff", async (req) => {
|
|
3810
|
+
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3811
|
+
const abs = guardedAbsPath(ctx, artifact);
|
|
3812
|
+
const current = existsSync14(abs) && !statSync6(abs).isDirectory() ? readFileSync8(abs, "utf-8") : null;
|
|
3813
|
+
return { approved: approvedContent(ctx, artifact.id), current };
|
|
3814
|
+
});
|
|
3815
|
+
app.post(
|
|
3816
|
+
"/api/artifact/:id/approve",
|
|
3817
|
+
async (req) => approveArtifact(ctx, { id: parseInt(req.params.id) }, req.body.actor, { trivial: req.body.trivial })
|
|
3818
|
+
);
|
|
3819
|
+
app.post(
|
|
3820
|
+
"/api/artifact/:id/reject",
|
|
3821
|
+
async (req) => rejectArtifact(ctx, { id: parseInt(req.params.id) }, req.body.actor, req.body.reason)
|
|
3822
|
+
);
|
|
3823
|
+
app.post(
|
|
3824
|
+
"/api/artifact/:id/submit",
|
|
3825
|
+
async (req) => submitArtifact(ctx, { id: parseInt(req.params.id) }, req.body.actor)
|
|
3826
|
+
);
|
|
3827
|
+
app.post(
|
|
3828
|
+
"/api/artifact/:id/feedback",
|
|
3829
|
+
async (req) => feedbackArtifact(ctx, { id: parseInt(req.params.id) }, {
|
|
3830
|
+
verdict: req.body.verdict,
|
|
3831
|
+
comment: req.body.comment,
|
|
3832
|
+
actor: req.body.actor
|
|
3833
|
+
})
|
|
3834
|
+
);
|
|
3835
|
+
app.post("/api/sync", async () => syncArtifacts(ctx, "opcflow"));
|
|
3836
|
+
app.post(
|
|
3837
|
+
"/api/task/:id/claim",
|
|
3838
|
+
async (req) => claimTask(ctx, { id: parseInt(req.params.id), assignee: req.body.assignee })
|
|
3839
|
+
);
|
|
3840
|
+
app.post(
|
|
3841
|
+
"/api/task/:id/status",
|
|
3842
|
+
async (req) => updateTask(ctx, {
|
|
3843
|
+
id: parseInt(req.params.id),
|
|
3844
|
+
status: req.body.status,
|
|
3845
|
+
operator: req.body.operator,
|
|
3846
|
+
force: req.body.force
|
|
3847
|
+
})
|
|
3848
|
+
);
|
|
3849
|
+
app.setErrorHandler((err, _req, reply) => {
|
|
3850
|
+
const e = err;
|
|
3851
|
+
reply.code(e.statusCode ?? 500).send({ error: e.message ?? String(err) });
|
|
3852
|
+
});
|
|
3853
|
+
return app;
|
|
3854
|
+
}
|
|
3855
|
+
var MAX_INLINE_SIZE;
|
|
3856
|
+
var init_app = __esm({
|
|
3857
|
+
"server/app.ts"() {
|
|
3858
|
+
"use strict";
|
|
3859
|
+
init_artifact_commands();
|
|
3860
|
+
init_retro_command();
|
|
3861
|
+
init_sync_command();
|
|
3862
|
+
init_config();
|
|
3863
|
+
init_task_commands();
|
|
3864
|
+
init_derive();
|
|
3865
|
+
init_events();
|
|
3866
|
+
init_tree();
|
|
3867
|
+
MAX_INLINE_SIZE = 1024 * 1024;
|
|
3868
|
+
}
|
|
3869
|
+
});
|
|
3870
|
+
|
|
3871
|
+
// scripts/hook-input.ts
|
|
3872
|
+
function hookPlatform() {
|
|
3873
|
+
const arg = process.argv.find((a) => a.startsWith("--platform="));
|
|
3874
|
+
return arg ? arg.slice("--platform=".length) : "claude";
|
|
3875
|
+
}
|
|
3876
|
+
function extractFilePath(input) {
|
|
3877
|
+
return input?.tool_input?.file_path ?? // claude PreToolUse/PostToolUse
|
|
3878
|
+
input?.tool_input?.filePath ?? input?.file_path ?? // cursor afterFileEdit / 通用
|
|
3879
|
+
input?.filePath ?? input?.args?.file_path ?? // opencode tool 参数
|
|
3880
|
+
input?.args?.filePath ?? input?.arguments?.file_path ?? // codex 兜底
|
|
3881
|
+
input?.arguments?.filePath ?? input?.input?.file_path ?? void 0;
|
|
3882
|
+
}
|
|
3883
|
+
function hookProjectDir() {
|
|
3884
|
+
return process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? process.env.CURSOR_PROJECT_DIR ?? process.env.OPENCODE_PROJECT_DIR ?? process.env.WORKBENCH_PROJECT ?? void 0;
|
|
3885
|
+
}
|
|
3886
|
+
async function readStdinJson() {
|
|
3887
|
+
const chunks = [];
|
|
3888
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
3889
|
+
try {
|
|
3890
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
3891
|
+
} catch {
|
|
3892
|
+
return {};
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
var init_hook_input = __esm({
|
|
3896
|
+
"scripts/hook-input.ts"() {
|
|
3897
|
+
"use strict";
|
|
3898
|
+
}
|
|
3899
|
+
});
|
|
3900
|
+
|
|
3901
|
+
// scripts/hook-pretooluse.ts
|
|
3902
|
+
var hook_pretooluse_exports = {};
|
|
3903
|
+
__export(hook_pretooluse_exports, {
|
|
3904
|
+
writeGateHook: () => writeGateHook
|
|
3905
|
+
});
|
|
3906
|
+
async function writeGateHook(platform) {
|
|
3907
|
+
const input = await readStdinJson();
|
|
3908
|
+
const filePath = extractFilePath(input);
|
|
3909
|
+
if (!filePath) return;
|
|
3910
|
+
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
3911
|
+
const { normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
3912
|
+
const { reviewStatus: reviewStatus2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
|
|
3913
|
+
const { contractKinds: contractKinds2 } = await Promise.resolve().then(() => (init_kind(), kind_exports));
|
|
3914
|
+
const { logEvent: logEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
3915
|
+
const ctx = openWorkbench2(hookProjectDir());
|
|
3916
|
+
const mode = ctx.config.gates.writeGate;
|
|
3917
|
+
if (mode === "off") return;
|
|
3918
|
+
const rel = normalizeRelPath2(ctx, filePath);
|
|
3919
|
+
const artifact = ctx.db.prepare("SELECT * FROM artifacts WHERE path = ?").get(rel);
|
|
3920
|
+
if (!artifact) return;
|
|
3921
|
+
if (!contractKinds2(ctx.config).includes(artifact.kind)) return;
|
|
3922
|
+
if (reviewStatus2(artifact) !== "approved") return;
|
|
3923
|
+
const taskEnv = Number(process.env.WORKBENCH_TASK_ID);
|
|
3924
|
+
if (Number.isInteger(taskEnv) && taskEnv > 0) {
|
|
3925
|
+
const task = ctx.db.prepare("SELECT COUNT(*) AS c FROM tasks WHERE id = ? AND assignee IS NOT NULL AND status IN ('pending','in_progress')").get(taskEnv);
|
|
3926
|
+
if (task.c > 0) return;
|
|
3927
|
+
}
|
|
3928
|
+
logEvent2(ctx.db, {
|
|
3929
|
+
entityType: "artifact",
|
|
3930
|
+
entityId: artifact.id,
|
|
3931
|
+
event: "would_block",
|
|
3932
|
+
actor: "write-gate",
|
|
3933
|
+
payload: { path: rel, kind: artifact.kind, platform, taskEnv: process.env.WORKBENCH_TASK_ID ?? null, mode },
|
|
3934
|
+
module: artifact.module,
|
|
3935
|
+
endpoint: artifact.endpoint,
|
|
3936
|
+
page: artifact.page
|
|
3937
|
+
});
|
|
3938
|
+
if (mode === "enforce") {
|
|
3939
|
+
const msg = `\u8BE5\u6587\u4EF6\u662F\u5DF2\u5BA1\u6279\u5951\u7EA6(${artifact.kind}): ${rel}\u3002
|
|
3940
|
+
\u4FEE\u6539\u524D\u8BF7\u5148\u9886\u53D6\u5BF9\u5E94\u4EFB\u52A1:${ctx.config.cli} list --module=${artifact.module ?? ""} --status=pending
|
|
3941
|
+
\u9886\u53D6\u540E\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF WORKBENCH_TASK_ID=<\u4EFB\u52A1id> \u518D\u4FEE\u6539;\u82E5\u5BF9\u5185\u5BB9\u672C\u8EAB\u6709\u5F02\u8BAE,\u7528 dispute \u547D\u4EE4\u7559\u75D5\u5E76\u505C\u6B62\u3002`;
|
|
3942
|
+
if (platform === "cursor") {
|
|
3943
|
+
process.stdout.write(JSON.stringify({ permission: "deny", userMessage: msg, agentMessage: msg }));
|
|
3944
|
+
process.exit(0);
|
|
3945
|
+
}
|
|
3946
|
+
console.error(msg);
|
|
3947
|
+
process.exit(2);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
var init_hook_pretooluse = __esm({
|
|
3951
|
+
"scripts/hook-pretooluse.ts"() {
|
|
3952
|
+
"use strict";
|
|
3953
|
+
init_hook_input();
|
|
3954
|
+
if (process.argv[1]?.replace(/\\/g, "/").endsWith("hook-pretooluse.ts")) {
|
|
3955
|
+
writeGateHook(hookPlatform()).catch(() => {
|
|
3956
|
+
}).finally(() => process.exit(0));
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
});
|
|
3960
|
+
|
|
3961
|
+
// scripts/hook-refresh.ts
|
|
3962
|
+
var hook_refresh_exports = {};
|
|
3963
|
+
__export(hook_refresh_exports, {
|
|
3964
|
+
refreshHook: () => refreshHook
|
|
3965
|
+
});
|
|
3966
|
+
async function refreshHook(_platform) {
|
|
3967
|
+
const input = await readStdinJson();
|
|
3968
|
+
const filePath = extractFilePath(input);
|
|
3969
|
+
if (!filePath) return;
|
|
3970
|
+
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
3971
|
+
const { refreshArtifact: refreshArtifact2, normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
3972
|
+
const ctx = openWorkbench2(hookProjectDir());
|
|
3973
|
+
const rel = normalizeRelPath2(ctx, filePath);
|
|
3974
|
+
const exact = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(rel);
|
|
3975
|
+
if (exact) {
|
|
3976
|
+
refreshArtifact2(ctx, { id: exact.id }, "hook");
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
const dirs = ctx.db.prepare("SELECT id, path FROM artifacts WHERE kind = 'code'").all();
|
|
3980
|
+
for (const d of dirs) {
|
|
3981
|
+
if (rel.startsWith(d.path + "/")) {
|
|
3982
|
+
refreshArtifact2(ctx, { id: d.id }, "hook");
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
var init_hook_refresh = __esm({
|
|
3988
|
+
"scripts/hook-refresh.ts"() {
|
|
3989
|
+
"use strict";
|
|
3990
|
+
init_hook_input();
|
|
3991
|
+
if (process.argv[1]?.replace(/\\/g, "/").endsWith("hook-refresh.ts")) {
|
|
3992
|
+
refreshHook(hookPlatform()).catch(() => {
|
|
3993
|
+
}).finally(() => process.exit(0));
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
});
|
|
3997
|
+
|
|
3998
|
+
// cli.ts
|
|
3999
|
+
init_db();
|
|
4000
|
+
|
|
4001
|
+
// cli-runner.ts
|
|
4002
|
+
init_core();
|
|
4003
|
+
import chalk from "chalk";
|
|
4004
|
+
var STATUS_TEXT = { pending: "\u23F3 \u5F85\u9886\u53D6", in_progress: "\u{1F504} \u8FDB\u884C\u4E2D", completed: "\u2705 \u5DF2\u5B8C\u6210", cancelled: "\u274C \u5DF2\u53D6\u6D88" };
|
|
4005
|
+
var STATUS_COLOR = { pending: chalk.gray, in_progress: chalk.blue, completed: chalk.green, cancelled: chalk.red };
|
|
4006
|
+
var REVIEW_TEXT = { draft: "\u{1F4DD} \u8349\u7A3F", pending: "\u23F3 \u5F85\u5BA1", approved: "\u2705 \u5DF2\u5BA1\u6279", invalidated: "\u26A0\uFE0F \u5DF2\u5931\u6548" };
|
|
4007
|
+
function parseArgs(argv) {
|
|
4008
|
+
if (argv.length === 0) return { a: {} };
|
|
4009
|
+
const [command, ...rest] = argv;
|
|
4010
|
+
const a = {};
|
|
4011
|
+
for (const arg of rest) {
|
|
4012
|
+
if (arg === "--") continue;
|
|
4013
|
+
const match = arg.match(/^--(\w+)=([\s\S]+)$/);
|
|
4014
|
+
if (match) a[match[1]] = match[2];
|
|
4015
|
+
else if (arg.match(/^\d+$/)) a.id = parseInt(arg);
|
|
4016
|
+
else if (a._ === void 0) a._ = arg;
|
|
4017
|
+
else fail(`\u591A\u4F59\u7684\u4F4D\u7F6E\u53C2\u6570: ${arg}(\u5DF2\u6709 ${a._});\u542B\u7A7A\u683C\u7684\u5185\u5BB9\u8BF7\u52A0\u5F15\u53F7`);
|
|
4018
|
+
}
|
|
4019
|
+
return { command, a };
|
|
4020
|
+
}
|
|
4021
|
+
function printWarnings(warnings) {
|
|
4022
|
+
for (const w of warnings) console.log(chalk.yellow(` \u26A0 ${w}`));
|
|
4023
|
+
}
|
|
4024
|
+
function printTasks(rows) {
|
|
4025
|
+
if (rows.length === 0) return console.log(chalk.yellow("\u6CA1\u6709\u627E\u5230\u4EFB\u52A1"));
|
|
4026
|
+
console.log(chalk.bold("\n ID | \u6A21\u5757 | \u89D2\u8272 | \u7AEF | \u9875\u9762 | \u7C7B\u578B | \u72B6\u6001 | \u6267\u884C\u4EBA | \u521B\u5EFA\u65F6\u95F4"));
|
|
4027
|
+
console.log(chalk.gray("------|---------|-------------|---------|----------------------|--------|-----------|--------------|-------------------"));
|
|
4028
|
+
for (const row of rows) {
|
|
4029
|
+
const colorFn = STATUS_COLOR[row.status] || chalk.white;
|
|
4030
|
+
const pageDisplay = row.page && row.page.length > 20 ? row.page.substring(0, 17) + "..." : row.page || "";
|
|
4031
|
+
const staleMark = row.stale ? chalk.yellow(" \u26A0stale") : "";
|
|
4032
|
+
console.log(
|
|
4033
|
+
colorFn(` ${String(row.id).padEnd(5)} | ${(row.module || "-").padEnd(8)} | ${row.role.padEnd(12)} | ${(row.endpoint || "-").padEnd(8)} | ${pageDisplay.padEnd(22)} | ${row.type.padEnd(7)} | ${(STATUS_TEXT[row.status] || row.status).padEnd(10)} | ${(row.assignee || "-").padEnd(13)} | ${row.created_at}`) + staleMark
|
|
4034
|
+
);
|
|
4035
|
+
}
|
|
4036
|
+
console.log();
|
|
4037
|
+
}
|
|
4038
|
+
function printArtifacts(rows) {
|
|
4039
|
+
if (rows.length === 0) return console.log(chalk.yellow("\u6CA1\u6709\u627E\u5230\u4EA7\u7269"));
|
|
4040
|
+
console.log(chalk.bold("\n ID | kind | \u6A21\u5757 | \u7AEF | \u5BA1\u6279\u72B6\u6001 | \u8DEF\u5F84"));
|
|
4041
|
+
console.log(chalk.gray("------|---------------|---------|---------|-----------|-------------------"));
|
|
4042
|
+
for (const row of rows) {
|
|
4043
|
+
const p = row.path.length > 60 ? "..." + row.path.substring(row.path.length - 57) : row.path;
|
|
4044
|
+
console.log(` ${String(row.id).padEnd(5)} | ${row.kind.padEnd(14)} | ${(row.module || "-").padEnd(8)} | ${(row.endpoint || "-").padEnd(8)} | ${(REVIEW_TEXT[row.review_status] || row.review_status).padEnd(10)} | ${p}`);
|
|
4045
|
+
}
|
|
4046
|
+
console.log();
|
|
4047
|
+
}
|
|
4048
|
+
function printTaskDetail(ctx, id) {
|
|
4049
|
+
const { task, events, outputs, staleness } = getTaskDetail(ctx, id);
|
|
4050
|
+
console.log(chalk.bold(`
|
|
4051
|
+
\u2550\u2550\u2550 \u4EFB\u52A1 #${task.id} \u2550\u2550\u2550`));
|
|
4052
|
+
console.log(` \u6A21\u5757: ${task.module || "-"} \u89D2\u8272: ${task.role} \u7AEF: ${task.endpoint || "-"} \u9875\u9762: ${task.page || "-"}`);
|
|
4053
|
+
console.log(` \u7C7B\u578B: ${task.type} \u72B6\u6001: ${STATUS_COLOR[task.status]?.(STATUS_TEXT[task.status]) || task.status}${staleness.stale ? chalk.yellow(" \u26A0 STALE(\u4E0A\u6E38\u5DF2\u53D8\u66F4)") : ""}`);
|
|
4054
|
+
console.log(` \u6267\u884C\u4EBA: ${task.assignee || "-"} \u521B\u5EFA\u4EBA: ${task.creator}`);
|
|
4055
|
+
if (task.content) console.log(chalk.bold("\n \u{1F4C4} \u5185\u5BB9: ") + task.content);
|
|
4056
|
+
if (staleness.stale) {
|
|
4057
|
+
console.log(chalk.yellow("\n \u26A0 \u53D8\u66F4\u7684\u4E0A\u6E38\u4EA7\u7269:"));
|
|
4058
|
+
for (const c of staleness.changed) console.log(chalk.yellow(` - ${c.path}`));
|
|
4059
|
+
}
|
|
4060
|
+
console.log(chalk.bold(`
|
|
4061
|
+
\u{1F4E6} \u4EA7\u51FA (${outputs.length})`));
|
|
4062
|
+
for (const o of outputs) console.log(chalk.cyan(` \u251C\u2500 [${o.kind}] ${o.path}`));
|
|
4063
|
+
console.log(chalk.bold(`
|
|
4064
|
+
\u{1F4DD} \u4E8B\u4EF6 (${events.length})`));
|
|
4065
|
+
for (const e of events.slice(0, 20)) {
|
|
4066
|
+
const payload = e.payload ? JSON.parse(e.payload) : {};
|
|
4067
|
+
const extra = e.event === "note" ? ` ${payload.content}` : "";
|
|
4068
|
+
console.log(chalk.gray(` \u251C\u2500 ${e.created_at} [${e.event}] ${e.actor}${extra}`));
|
|
4069
|
+
}
|
|
4070
|
+
console.log();
|
|
4071
|
+
}
|
|
4072
|
+
var ALL_PLATFORMS = ["claude", "codex", "opencode", "cursor"];
|
|
4073
|
+
var PRIORITY_PROVIDERS = [
|
|
4074
|
+
"anthropic",
|
|
4075
|
+
"openai",
|
|
4076
|
+
"google",
|
|
4077
|
+
"xai",
|
|
4078
|
+
"deepseek",
|
|
4079
|
+
"mistral",
|
|
4080
|
+
"meta",
|
|
4081
|
+
"groq",
|
|
4082
|
+
"qwen",
|
|
4083
|
+
"moonshotai",
|
|
4084
|
+
"zhipuai",
|
|
4085
|
+
"z-ai",
|
|
4086
|
+
"cohere"
|
|
4087
|
+
];
|
|
4088
|
+
async function fetchProviders() {
|
|
4089
|
+
try {
|
|
4090
|
+
const res = await fetch("https://models.dev/api.json");
|
|
4091
|
+
const j = await res.json();
|
|
4092
|
+
const providers = [];
|
|
4093
|
+
for (const [pid, pv] of Object.entries(j)) {
|
|
4094
|
+
const models = Object.entries(pv.models ?? {}).filter(([, mv]) => mv.tool_call).map(([mid, mv]) => ({ id: mv.id ?? mid, name: mv.name ?? mid }));
|
|
4095
|
+
if (models.length) providers.push({ id: pv.id ?? pid, name: pv.name ?? pid, models });
|
|
4096
|
+
}
|
|
4097
|
+
const rank = (id) => {
|
|
4098
|
+
const i = PRIORITY_PROVIDERS.indexOf(id);
|
|
4099
|
+
return i === -1 ? PRIORITY_PROVIDERS.length : i;
|
|
4100
|
+
};
|
|
4101
|
+
return providers.sort((a, b) => rank(a.id) - rank(b.id) || a.name.localeCompare(b.name));
|
|
4102
|
+
} catch {
|
|
4103
|
+
return [];
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
async function promptInit() {
|
|
4107
|
+
const { input, select } = await import("@inquirer/prompts");
|
|
4108
|
+
const language = await select({
|
|
4109
|
+
message: "\u8BED\u8A00 / Language",
|
|
4110
|
+
choices: [
|
|
4111
|
+
{ name: "\u4E2D\u6587", value: "zh" },
|
|
4112
|
+
{ name: "English", value: "en" }
|
|
4113
|
+
]
|
|
4114
|
+
});
|
|
4115
|
+
const zh = language === "zh";
|
|
4116
|
+
const platform = await select({
|
|
4117
|
+
message: zh ? "\u5E73\u53F0" : "Platform",
|
|
4118
|
+
choices: ALL_PLATFORMS.map((p) => ({ name: p, value: p })),
|
|
4119
|
+
default: "claude"
|
|
4120
|
+
});
|
|
4121
|
+
const platforms = [platform];
|
|
4122
|
+
const endpoints = (await input({
|
|
4123
|
+
message: zh ? "\u7AEF(\u9017\u53F7\u5206\u9694)" : "Endpoints (comma-separated)",
|
|
4124
|
+
default: "service,web"
|
|
4125
|
+
})).split(",").map((s) => s.trim()).filter(Boolean);
|
|
4126
|
+
const providers = await fetchProviders();
|
|
4127
|
+
const modelObj = {};
|
|
4128
|
+
for (const p of platforms) {
|
|
4129
|
+
if (!providers.length) break;
|
|
4130
|
+
const provId = await select({
|
|
4131
|
+
message: zh ? `${p} \u2014\u2014 \u4F9B\u5E94\u5546(\u9009\u201C\u9ED8\u8BA4\u201D\u7528\u8BE5\u5E73\u53F0\u7F3A\u7701\u6A21\u578B)` : `${p} \u2014 provider ("default" keeps the platform default)`,
|
|
4132
|
+
choices: [
|
|
4133
|
+
{ name: zh ? "(\u9ED8\u8BA4 / default)" : "(default)", value: "" },
|
|
4134
|
+
...providers.map((pr) => ({ name: pr.name, value: pr.id }))
|
|
4135
|
+
],
|
|
4136
|
+
pageSize: 12
|
|
4137
|
+
});
|
|
4138
|
+
if (!provId) continue;
|
|
4139
|
+
const prov = providers.find((pr) => pr.id === provId);
|
|
4140
|
+
if (!prov) continue;
|
|
4141
|
+
const modelId = await select({
|
|
4142
|
+
message: zh ? `${p} \u2014\u2014 \u6A21\u578B` : `${p} \u2014 model`,
|
|
4143
|
+
choices: prov.models.map((m) => ({ name: m.name, value: m.id })),
|
|
4144
|
+
pageSize: 12
|
|
4145
|
+
});
|
|
4146
|
+
if (modelId) modelObj[p] = p === "opencode" ? `${provId}/${modelId}` : modelId;
|
|
4147
|
+
}
|
|
4148
|
+
return { platforms, endpoints, model: Object.keys(modelObj).length ? modelObj : void 0, language };
|
|
4149
|
+
}
|
|
4150
|
+
async function runInit(root, a) {
|
|
4151
|
+
const { existsSync: existsSync15 } = await import("node:fs");
|
|
4152
|
+
const { join: join17 } = await import("node:path");
|
|
4153
|
+
if (existsSync15(join17(root, "workbench.config.json"))) {
|
|
4154
|
+
console.log(chalk.red("\u9519\u8BEF: workbench.config.json \u5DF2\u5B58\u5728,init \u53EA\u7528\u4E8E\u7A7A\u9879\u76EE\u5F15\u5BFC(\u6539\u914D\u7F6E\u8BF7\u76F4\u63A5\u7F16\u8F91\u8BE5\u6587\u4EF6)"));
|
|
4155
|
+
process.exit(1);
|
|
4156
|
+
}
|
|
4157
|
+
let opts;
|
|
4158
|
+
if (!a.platforms && !a.endpoints && process.stdin.isTTY) {
|
|
4159
|
+
const p = await promptInit();
|
|
4160
|
+
opts = { endpoints: p.endpoints, platforms: p.platforms, model: p.model, language: p.language };
|
|
4161
|
+
} else {
|
|
4162
|
+
if (!a.endpoints) {
|
|
4163
|
+
console.log(chalk.red("\u9519\u8BEF: \u9700\u8981 --endpoints=service,admin,...(\u6216\u5728\u7EC8\u7AEF\u76F4\u63A5\u8FD0\u884C init \u8FDB\u5165\u4EA4\u4E92)"));
|
|
4164
|
+
process.exit(1);
|
|
4165
|
+
}
|
|
4166
|
+
const platforms = a.platforms ? String(a.platforms).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
4167
|
+
let model;
|
|
4168
|
+
if (a.model) {
|
|
4169
|
+
const raw = String(a.model).trim();
|
|
4170
|
+
model = raw.startsWith("{") ? JSON.parse(raw) : raw;
|
|
4171
|
+
}
|
|
4172
|
+
const language = a.language === "en" ? "en" : a.language === "zh" ? "zh" : void 0;
|
|
4173
|
+
opts = { endpoints: String(a.endpoints).split(","), platforms, model, language };
|
|
4174
|
+
}
|
|
4175
|
+
const r = initProject(root, {
|
|
4176
|
+
...opts,
|
|
4177
|
+
gitHooks: a.hooks !== "false",
|
|
4178
|
+
preset: a.preset !== "false",
|
|
4179
|
+
writeHooks: a.writehooks !== "false"
|
|
4180
|
+
});
|
|
4181
|
+
const cli = r.ctx.config.cli;
|
|
4182
|
+
console.log(chalk.green(`
|
|
4183
|
+
\u2705 \u9879\u76EE\u5F15\u5BFC\u5B8C\u6210
|
|
4184
|
+
`));
|
|
4185
|
+
console.log(` \u914D\u7F6E\u6587\u4EF6 ${r.configPath}`);
|
|
4186
|
+
console.log(` \u8BED\u8A00 ${r.ctx.config.language}`);
|
|
4187
|
+
console.log(` \u76EE\u6807\u5E73\u53F0 ${r.platforms.join(", ")}`);
|
|
4188
|
+
console.log(` \u6587\u6863\u9AA8\u67B6 ${r.scaffolded.length} \u4E2A\u76EE\u5F55`);
|
|
4189
|
+
console.log(` \u9884\u7F6E\u6587\u4EF6 ${r.preset.length ? r.preset.join(", ") : "\u65E0(preset/ \u4E3A\u7A7A\u6216\u5747\u5DF2\u5B58\u5728)"}`);
|
|
4190
|
+
console.log(` agent \u5B9A\u4E49 ${r.agents.length} \u4EFD`);
|
|
4191
|
+
console.log(` \u5143\u4EA7\u7269\u6CE8\u518C ${r.metaRegistered} \u4EFD(draft)`);
|
|
4192
|
+
console.log(` MCP ${r.mcpPaths.join(", ") || "\u672A\u5199"}`);
|
|
4193
|
+
console.log(` hooks \u63A5\u7EBF ${r.hookPaths.join(", ") || "\u672A\u5199(--writehooks=false)"}`);
|
|
4194
|
+
console.log(` git hooks ${r.hooks.join(", ") || "\u672A\u5B89\u88C5(\u975E git \u4ED3\u5E93)"}`);
|
|
4195
|
+
if (r.notes.length) {
|
|
4196
|
+
console.log(chalk.yellow(`
|
|
4197
|
+
\u5E73\u53F0\u63D0\u9192:`));
|
|
4198
|
+
for (const n of r.notes) console.log(chalk.yellow(` \u2022 ${n}`));
|
|
4199
|
+
}
|
|
4200
|
+
console.log(chalk.bold(`
|
|
4201
|
+
\u4E0B\u4E00\u6B65:`));
|
|
4202
|
+
console.log(` 1. \u7F16\u8F91 workbench.config.json \u7684 codeRoots(\u586B\u6BCF\u4E2A\u7AEF\u7684\u4EE3\u7801\u76EE\u5F55\u7EA6\u5B9A)`);
|
|
4203
|
+
console.log(` 2. \u542F\u52A8\u5DE5\u4F5C\u53F0: ${cli} serve \u2192 http://127.0.0.1:5620`);
|
|
4204
|
+
console.log(` 3. \u5BF9 AI \u63D0\u7B2C\u4E00\u4E2A\u9700\u6C42,\u5B83\u8D70\u6D41\u6C34\u7EBF\u4EA7\u51FA\u5951\u7EA6,\u4F60\u5728\u5F85\u5BA1\u961F\u5217\u70B9\u5934`);
|
|
4205
|
+
console.log(` 4. \u5951\u7EA6\u5BA1\u6279\u540E: ${cli} plan --module=<\u6A21\u5757> \u4E00\u952E\u6D3E\u53D1
|
|
4206
|
+
`);
|
|
4207
|
+
r.ctx.db.close();
|
|
4208
|
+
}
|
|
4209
|
+
var HELP = `opcflow CLI
|
|
4210
|
+
|
|
4211
|
+
\u4EFB\u52A1 list / show <id> / create / claim / update / remove / record
|
|
4212
|
+
\u4EA7\u51FA output / artifacts / scan / move / input
|
|
4213
|
+
\u4FE1\u4EFB submit / approve / reject / feedback / queue / dispute / sync
|
|
4214
|
+
\u6D41\u7A0B plan / qa / audit / intake / lint / graph / events
|
|
4215
|
+
\u8FDB\u5316 retro [--module= --json=] / export
|
|
4216
|
+
\u7EF4\u62A4 init / gen-agents / register-meta / install-hooks / migrate`;
|
|
4217
|
+
async function runCommand(ctx, command, a) {
|
|
4218
|
+
switch (command) {
|
|
4219
|
+
case "create": {
|
|
4220
|
+
if (!a.role || !a.creator) return fail("--role, --creator \u5FC5\u586B");
|
|
4221
|
+
const id = createTask(ctx, { module: a.module, role: a.role, endpoint: a.endpoint, page: a.page, type: a.type, assignee: a.assignee, creator: a.creator, content: a.content });
|
|
4222
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${id} \u521B\u5EFA\u6210\u529F`));
|
|
4223
|
+
break;
|
|
4224
|
+
}
|
|
4225
|
+
case "claim": {
|
|
4226
|
+
if (!a.id || !a.assignee) return fail("\u9700\u8981\u4EFB\u52A1 ID \u548C --assignee");
|
|
4227
|
+
const { warnings } = claimTask(ctx, { id: a.id, assignee: a.assignee });
|
|
4228
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${a.id} \u5DF2\u7531 ${a.assignee} \u9886\u53D6`));
|
|
4229
|
+
printWarnings(warnings);
|
|
4230
|
+
break;
|
|
4231
|
+
}
|
|
4232
|
+
case "update": {
|
|
4233
|
+
if (!a.id || !a.status || !a.operator) return fail("\u9700\u8981\u4EFB\u52A1 ID, --status \u548C --operator");
|
|
4234
|
+
const { warnings } = updateTask(ctx, { id: a.id, status: a.status, operator: a.operator, force: a.force === "true" });
|
|
4235
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${a.id} \u72B6\u6001\u5DF2\u66F4\u65B0\u4E3A ${a.status}`));
|
|
4236
|
+
printWarnings(warnings);
|
|
4237
|
+
break;
|
|
4238
|
+
}
|
|
4239
|
+
case "remove":
|
|
4240
|
+
case "delete": {
|
|
4241
|
+
if (!a.id || !a.operator) return fail("\u9700\u8981\u4EFB\u52A1 ID \u548C --operator");
|
|
4242
|
+
removeTask(ctx, { id: a.id, operator: a.operator, force: a.force === "true" });
|
|
4243
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${a.id} \u5DF2\u5220\u9664`));
|
|
4244
|
+
break;
|
|
4245
|
+
}
|
|
4246
|
+
case "record": {
|
|
4247
|
+
if (!a.id || !a._ || !a.operator) return fail("\u9700\u8981\u4EFB\u52A1 ID, --operator \u548C\u5185\u5BB9\u6587\u672C");
|
|
4248
|
+
recordNote(ctx, { id: a.id, content: a._, operator: a.operator });
|
|
4249
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${a.id} \u8BB0\u5F55\u5DF2\u6DFB\u52A0`));
|
|
4250
|
+
break;
|
|
4251
|
+
}
|
|
4252
|
+
case "output": {
|
|
4253
|
+
if (!a.role || !a.endpoint || !a._) return fail("\u9700\u8981 --role, --endpoint \u548C\u6587\u4EF6\u8DEF\u5F84");
|
|
4254
|
+
const { artifactId, linkedTaskId, kind } = registerOutput(ctx, { module: a.module, role: a.role, endpoint: a.endpoint, page: a.page, filePath: a._, taskId: a.task ? parseInt(a.task) : void 0 });
|
|
4255
|
+
console.log(chalk.green(`\u2705 \u8F93\u51FA\u6587\u4EF6\u5DF2\u6DFB\u52A0 (artifact #${artifactId}, kind=${kind}${linkedTaskId ? `, \u5173\u8054\u4EFB\u52A1 #${linkedTaskId}` : ""})`));
|
|
4256
|
+
break;
|
|
4257
|
+
}
|
|
4258
|
+
case "outputs":
|
|
4259
|
+
case "artifacts":
|
|
4260
|
+
printArtifacts(listArtifacts(ctx, { module: a.module, endpoint: a.endpoint, page: a.page, kind: a.kind }));
|
|
4261
|
+
break;
|
|
4262
|
+
case "list":
|
|
4263
|
+
printTasks(listTasks(ctx, { status: a.status, assignee: a.assignee, module: a.module, role: a.role, endpoint: a.endpoint, type: a.type, withStale: a.stale === "true" }));
|
|
4264
|
+
break;
|
|
4265
|
+
case "show": {
|
|
4266
|
+
if (!a.id) return fail("\u9700\u8981\u4EFB\u52A1 ID");
|
|
4267
|
+
if (a.json === "true") console.log(JSON.stringify(getTaskDetail(ctx, a.id), null, 2));
|
|
4268
|
+
else printTaskDetail(ctx, a.id);
|
|
4269
|
+
break;
|
|
4270
|
+
}
|
|
4271
|
+
case "records":
|
|
4272
|
+
case "events": {
|
|
4273
|
+
const rows = listEvents(ctx.db, { entityId: a.taskId ? parseInt(a.taskId) : a.id, entityType: a.taskId || a.id ? "task" : void 0, module: a.module, event: a.event, limit: a.limit ? parseInt(a.limit) : 50 });
|
|
4274
|
+
if (a.json === "true") return void console.log(JSON.stringify(rows, null, 2));
|
|
4275
|
+
for (const e of rows) {
|
|
4276
|
+
const payload = e.payload ? JSON.parse(e.payload) : {};
|
|
4277
|
+
const extra = e.event === "note" ? ` ${payload.content}` : "";
|
|
4278
|
+
console.log(chalk.gray(` ${e.created_at} [${e.entity_type}#${e.entity_id}] ${e.event} by ${e.actor}${extra}`));
|
|
4279
|
+
}
|
|
4280
|
+
break;
|
|
4281
|
+
}
|
|
4282
|
+
case "submit": {
|
|
4283
|
+
if (!a._ || !a.actor) return fail("\u9700\u8981\u6587\u4EF6\u8DEF\u5F84\u548C --actor");
|
|
4284
|
+
submitArtifact(ctx, { path: a._ }, a.actor);
|
|
4285
|
+
console.log(chalk.green(`\u2705 \u5DF2\u9001\u5BA1: ${a._}`));
|
|
4286
|
+
break;
|
|
4287
|
+
}
|
|
4288
|
+
case "approve": {
|
|
4289
|
+
if (!a._ || !a.actor) return fail("\u9700\u8981\u6587\u4EF6\u8DEF\u5F84\u548C --actor");
|
|
4290
|
+
const trivial = a.trivial === "true";
|
|
4291
|
+
const row = approveArtifact(ctx, { path: a._ }, a.actor, { trivial });
|
|
4292
|
+
console.log(chalk.green(`\u2705 \u5DF2\u5BA1\u6279\u901A\u8FC7${trivial ? "(trivial:\u5DF2 re-bless \u4E0B\u6E38\u5E76\u5173\u95ED\u6D3E\u751F review)" : ""}: ${row.path} (hash=${row.approved_hash?.slice(0, 8)})`));
|
|
4293
|
+
break;
|
|
4294
|
+
}
|
|
4295
|
+
case "reject": {
|
|
4296
|
+
if (!a._ || !a.actor || !a.reason) return fail("\u9700\u8981\u6587\u4EF6\u8DEF\u5F84, --actor \u548C --reason");
|
|
4297
|
+
rejectArtifact(ctx, { path: a._ }, a.actor, a.reason);
|
|
4298
|
+
console.log(chalk.yellow(`\u21A9 \u5DF2\u6253\u56DE: ${a._}`));
|
|
4299
|
+
break;
|
|
4300
|
+
}
|
|
4301
|
+
case "feedback": {
|
|
4302
|
+
if (!a._ || !a.actor || !a.verdict) return fail("\u9700\u8981\u6587\u4EF6\u8DEF\u5F84, --actor \u548C --verdict=+1|-1");
|
|
4303
|
+
const verdict = a.verdict === "+1" || a.verdict === "1" ? 1 : -1;
|
|
4304
|
+
const { endorsed } = feedbackArtifact(ctx, { path: a._ }, { verdict, comment: a.comment, actor: a.actor, taskId: a.task ? parseInt(a.task) : void 0 });
|
|
4305
|
+
console.log(chalk.green(`\u2705 \u53CD\u9988\u5DF2\u8BB0\u5F55 (${verdict > 0 ? "\u{1F44D}" : "\u{1F44E}"})${endorsed ? " \u2014 \u539F\u578B\u5DF2\u653E\u884C" : ""}`));
|
|
4306
|
+
break;
|
|
4307
|
+
}
|
|
4308
|
+
case "queue":
|
|
4309
|
+
printArtifacts(listArtifacts(ctx, {}).filter((r) => r.review_status === "pending" || r.review_status === "invalidated"));
|
|
4310
|
+
break;
|
|
4311
|
+
case "dispute": {
|
|
4312
|
+
if (!a._ || !a.actor || !a.reason) return fail("\u9700\u8981\u6587\u4EF6\u8DEF\u5F84, --actor \u548C --reason");
|
|
4313
|
+
disputeArtifact(ctx, { path: a._ }, a.actor, a.reason);
|
|
4314
|
+
console.log(chalk.yellow(`\u2696 \u5F02\u8BAE\u5DF2\u7559\u75D5,\u7B49\u5F85\u7528\u6237\u88C1\u51B3: ${a._}`));
|
|
4315
|
+
break;
|
|
4316
|
+
}
|
|
4317
|
+
case "sync": {
|
|
4318
|
+
const s = syncArtifacts(ctx, a.actor || "sync");
|
|
4319
|
+
console.log(chalk.green(`\u2705 \u5BF9\u8D26\u5B8C\u6210:\u68C0\u67E5 ${s.checked},\u53D8\u66F4 ${s.changed},\u5931\u6548 ${s.invalidated},\u5893\u7891 ${s.tombstoned},\u6D3E review ${s.reviewsSpawned}`));
|
|
4320
|
+
break;
|
|
4321
|
+
}
|
|
4322
|
+
case "plan": {
|
|
4323
|
+
if (!a.module) return fail("\u9700\u8981 --module");
|
|
4324
|
+
const s = planModule(ctx, a.module, a.creator || "product-manager");
|
|
4325
|
+
console.log(chalk.green(`\u2705 \u6D3E\u53D1\u5B8C\u6210:\u65B0\u5EFA ${s.created.length},\u8DF3\u8FC7 ${s.skipped},\u53D6\u6D88 ${s.cancelled}`));
|
|
4326
|
+
for (const t of s.created) console.log(chalk.cyan(` + #${t.id} ${t.role} ${t.endpoint ?? "-"} ${t.page ?? ""} (${t.type})`));
|
|
4327
|
+
printWarnings(s.warnings);
|
|
4328
|
+
break;
|
|
4329
|
+
}
|
|
4330
|
+
case "qa": {
|
|
4331
|
+
if (!a.id || !a.result || !a.operator) return fail("\u9700\u8981\u4EFB\u52A1 ID, --result=pass|fail \u548C --operator;fail \u9700 --reason");
|
|
4332
|
+
const { reworkTaskId } = recordQaResult(ctx, { id: a.id, result: a.result, reason: a.reason, operator: a.operator });
|
|
4333
|
+
console.log(chalk.green(`\u2705 \u9A8C\u6536\u7ED3\u679C\u5DF2\u8BB0\u5F55: ${a.result}${reworkTaskId ? `,\u5DF2\u6D3E rework #${reworkTaskId}` : ""}`));
|
|
4334
|
+
break;
|
|
4335
|
+
}
|
|
4336
|
+
case "audit": {
|
|
4337
|
+
if (!a.module) return fail("\u9700\u8981 --module");
|
|
4338
|
+
const r = auditModule(ctx, a.module);
|
|
4339
|
+
console.log(chalk.bold(`
|
|
4340
|
+
\u2550\u2550\u2550 \u6A21\u5757 ${r.module} \u5BF9\u8D26\u62A5\u544A \u2550\u2550\u2550 \u6E05\u7B97\u72B6\u6001: ${r.cleared ? chalk.green("\u2705 \u5DF2\u6E05\u7B97") : chalk.yellow("\u26A0 \u672A\u6E05\u7B97")}
|
|
4341
|
+
`));
|
|
4342
|
+
for (const c of r.contracts) {
|
|
4343
|
+
const st = { draft: chalk.gray("\u{1F4DD} \u8349\u7A3F"), pending: chalk.yellow("\u23F3 \u5F85\u5BA1"), approved: chalk.green("\u2705 \u5DF2\u5BA1\u6279"), invalidated: chalk.red("\u26A0 \u5DF2\u5931\u6548") }[c.status];
|
|
4344
|
+
console.log(` ${st} [${c.kind}] ${c.path}${c.onDisk ? "" : chalk.red(" (\u78C1\u76D8\u7F3A\u5931!)")}`);
|
|
4345
|
+
}
|
|
4346
|
+
console.log(chalk.bold(`
|
|
4347
|
+
code \u76EE\u5F55 ${r.codeDirs.length} \u4E2A`));
|
|
4348
|
+
if (r.suggestedSubmits.length > 0) {
|
|
4349
|
+
console.log(chalk.cyan(`
|
|
4350
|
+
\u5BF9\u8D26\u540E\u53EF\u6279\u91CF\u9001\u5BA1(\u4E0E\u4EE3\u7801\u6838\u5B9E\u4E00\u81F4\u540E\u6267\u884C):`));
|
|
4351
|
+
for (const p of r.suggestedSubmits) console.log(chalk.gray(` ${ctx.config.cli} submit --actor=architect -- ${p}`));
|
|
4352
|
+
}
|
|
4353
|
+
console.log();
|
|
4354
|
+
break;
|
|
4355
|
+
}
|
|
4356
|
+
case "intake": {
|
|
4357
|
+
const s = intakeIssues(ctx);
|
|
4358
|
+
console.log(chalk.green(`\u2705 intake:\u62C9\u53D6 ${s.fetched},\u65B0\u5EFA ${s.created.length},\u5DF2\u5173\u8054\u8DF3\u8FC7 ${s.skipped}`));
|
|
4359
|
+
for (const c of s.created) console.log(chalk.cyan(` gh#${c.issue} \u2192 \u4EFB\u52A1 #${c.taskId} (${c.lane === "hotfix" ? "\u5FEB\u8F66\u9053 hotfix" : "PM \u5206\u6790"})`));
|
|
4360
|
+
break;
|
|
4361
|
+
}
|
|
4362
|
+
case "retro":
|
|
4363
|
+
case "retrospective": {
|
|
4364
|
+
const report = runRetrospective(ctx, { module: a.module });
|
|
4365
|
+
if (a.json === "true") return void console.log(JSON.stringify(report, null, 2));
|
|
4366
|
+
const BUCKET_TEXT = {
|
|
4367
|
+
"skill-candidate": chalk.green("\u{1F331} skill \u5019\u9009"),
|
|
4368
|
+
"red-flag": chalk.red("\u{1F6A9} Red Flag"),
|
|
4369
|
+
observation: chalk.gray("\u{1F440} \u89C2\u5BDF")
|
|
4370
|
+
};
|
|
4371
|
+
console.log(chalk.bold(`
|
|
4372
|
+
\u2550\u2550\u2550 Retrospective${report.module ? `(\u6A21\u5757 ${report.module})` : ""} \u2550\u2550\u2550 \u534A\u8870\u671F ${report.halfLifeDays} \u5929
|
|
4373
|
+
`));
|
|
4374
|
+
if (report.groups.length === 0) console.log(chalk.yellow(" \u5C1A\u65E0\u53CD\u9988\u6570\u636E"));
|
|
4375
|
+
for (const g of report.groups) {
|
|
4376
|
+
const reason = g.reason === "mixed" ? "(\u4FE1\u53F7\u77DB\u76FE)" : g.reason === "insufficient" ? "(\u6837\u672C\u4E0D\u8DB3)" : "";
|
|
4377
|
+
console.log(` ${BUCKET_TEXT[g.bucket]}${reason} [${g.endpoint}/${g.kind}] +${g.posScore} / -${g.negScore}(${g.evidence.length} \u6761\u53CD\u9988)`);
|
|
4378
|
+
for (const e of g.evidence.filter((e2) => e2.verdict === -1 && e2.comment)) {
|
|
4379
|
+
console.log(chalk.red(` \u{1F44E} ${e.comment} (${e.path})`));
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
const ap = report.approval;
|
|
4383
|
+
console.log(chalk.bold(`
|
|
4384
|
+
\u5BA1\u6279\u541E\u5410:`) + ` \u901A\u8FC7 ${ap.approved},\u6253\u56DE ${ap.rejected}${ap.rejectionRate !== null ? `,\u6253\u56DE\u7387 ${Math.round(ap.rejectionRate * 100)}%` : ""}${ap.avgApprovalHours !== null ? `,\u9001\u5BA1\u2192\u901A\u8FC7\u5E73\u5747 ${ap.avgApprovalHours} \u5C0F\u65F6` : ""}`);
|
|
4385
|
+
console.log(chalk.bold(`
|
|
4386
|
+
\u4E0B\u4E00\u6B65:`));
|
|
4387
|
+
for (const gd of report.guidance) console.log(chalk.cyan(` \u2192 ${gd}`));
|
|
4388
|
+
console.log();
|
|
4389
|
+
break;
|
|
4390
|
+
}
|
|
4391
|
+
case "export": {
|
|
4392
|
+
const r = exportEventLog(ctx);
|
|
4393
|
+
console.log(chalk.green(`\u2705 \u5DF2\u5BFC\u51FA events ${r.events} \u6761,feedback ${r.feedbacks} \u6761 \u2192 ${r.files.join(", ")}`));
|
|
4394
|
+
break;
|
|
4395
|
+
}
|
|
4396
|
+
case "lint": {
|
|
4397
|
+
const role = a.role || "developer";
|
|
4398
|
+
const violations = runProtocolLints(ctx, { role, endpoint: a.endpoint });
|
|
4399
|
+
if (violations.length === 0) console.log(chalk.green("\u2705 \u534F\u8BAE lint \u901A\u8FC7,\u65E0\u8FDD\u4F8B"));
|
|
4400
|
+
else {
|
|
4401
|
+
console.log(chalk.red(`\u2717 ${violations.length} \u5904\u8FDD\u4F8B:`));
|
|
4402
|
+
for (const v of violations) console.log(chalk.red(` ${v.file}:${v.line} [${v.lint}] ${v.message}`));
|
|
4403
|
+
process.exit(1);
|
|
4404
|
+
}
|
|
4405
|
+
break;
|
|
4406
|
+
}
|
|
4407
|
+
case "graph": {
|
|
4408
|
+
if (!a.module) return fail("\u9700\u8981 --module");
|
|
4409
|
+
console.log(graphModule(ctx, a.module));
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4412
|
+
case "scan": {
|
|
4413
|
+
const s = scanArtifacts(ctx, a.actor || "system");
|
|
4414
|
+
console.log(chalk.green(`\u2705 \u626B\u63CF\u5B8C\u6210:\u65B0\u767B\u8BB0 ${s.registered},\u5185\u5BB9\u5237\u65B0 ${s.refreshed},\u65B0\u589E\u8FB9 ${s.edges},\u6392\u9664\u5143\u4EA7\u7269 ${s.skipped.length}`));
|
|
4415
|
+
break;
|
|
4416
|
+
}
|
|
4417
|
+
case "move": {
|
|
4418
|
+
if (!a.from || !a.to || !a.actor) return fail("\u9700\u8981 --from, --to \u548C --actor");
|
|
4419
|
+
const moved = moveArtifact(ctx, { from: a.from, to: a.to, actor: a.actor });
|
|
4420
|
+
console.log(chalk.green(`\u2705 \u5DF2\u79FB\u52A8(\u4FDD id #${moved.id}): ${a.from} \u2192 ${moved.path}`));
|
|
4421
|
+
break;
|
|
4422
|
+
}
|
|
4423
|
+
case "input": {
|
|
4424
|
+
if (!a.id || !a._ || !a.operator) return fail("\u9700\u8981\u4EFB\u52A1 ID, --operator \u548C\u4EA7\u7269\u8DEF\u5F84");
|
|
4425
|
+
addTaskInput(ctx, { id: a.id, path: a._, operator: a.operator });
|
|
4426
|
+
console.log(chalk.green(`\u2705 \u4EFB\u52A1 #${a.id} \u5DF2\u8865\u5145\u7533\u62A5\u4F9D\u8D56: ${a._}`));
|
|
4427
|
+
break;
|
|
4428
|
+
}
|
|
4429
|
+
case "gen-agents": {
|
|
4430
|
+
const { written } = genAgents(ctx);
|
|
4431
|
+
console.log(chalk.green(`\u2705 agent \u5B9A\u4E49\u5DF2\u4ECE\u6A21\u677F\u751F\u6210(\u8DEF\u5F84\u7531\u6CE8\u518C\u8868\u6CE8\u5165):`));
|
|
4432
|
+
for (const w of written) console.log(chalk.cyan(` \u2192 ${w}`));
|
|
4433
|
+
break;
|
|
4434
|
+
}
|
|
4435
|
+
case "register-meta": {
|
|
4436
|
+
const { registered, skipped } = registerMetaArtifacts(ctx, a.actor || "system");
|
|
4437
|
+
console.log(chalk.green(`\u2705 \u5143\u4EA7\u7269\u6CE8\u518C:\u65B0\u589E ${registered.length},\u5DF2\u5B58\u5728\u8DF3\u8FC7 ${skipped.length}`));
|
|
4438
|
+
for (const r of registered) console.log(chalk.cyan(` + [${r.kind}] ${r.path}`));
|
|
4439
|
+
break;
|
|
4440
|
+
}
|
|
4441
|
+
case "install-hooks": {
|
|
4442
|
+
const installed = installGitHooks(ctx);
|
|
4443
|
+
console.log(chalk.green(`\u2705 git hooks \u5DF2\u5B89\u88C5: ${installed.join(", ")}`));
|
|
4444
|
+
break;
|
|
4445
|
+
}
|
|
4446
|
+
case "migrate": {
|
|
4447
|
+
const summary = migrateLegacy(ctx, a.from);
|
|
4448
|
+
console.log(chalk.green(`\u2705 \u8FC1\u79FB\u5B8C\u6210:\u4EFB\u52A1 ${summary.tasks},\u4EA7\u7269 ${summary.artifacts}(\u5173\u8054 ${summary.linkedOutputs}),\u8BB0\u5F55 ${summary.notes}`));
|
|
4449
|
+
if (summary.missingFiles.length > 0) {
|
|
4450
|
+
console.log(chalk.yellow(`\u26A0 ${summary.missingFiles.length} \u4E2A\u4EA7\u51FA\u6587\u4EF6\u5DF2\u4E0D\u5728\u78C1\u76D8\u4E0A(hash \u7F6E\u7A7A)`));
|
|
4451
|
+
}
|
|
4452
|
+
break;
|
|
4453
|
+
}
|
|
4454
|
+
case "help":
|
|
4455
|
+
console.log(HELP);
|
|
4456
|
+
break;
|
|
4457
|
+
default:
|
|
4458
|
+
console.log(chalk.red(`\u672A\u77E5\u547D\u4EE4: ${command}`));
|
|
4459
|
+
console.log(HELP);
|
|
4460
|
+
process.exit(1);
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
function fail(msg) {
|
|
4464
|
+
console.log(chalk.red(`\u9519\u8BEF: ${msg}`));
|
|
4465
|
+
process.exit(1);
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
// cli.ts
|
|
4469
|
+
init_core();
|
|
4470
|
+
async function main() {
|
|
4471
|
+
const { command, a } = parseArgs(process.argv.slice(2));
|
|
4472
|
+
if (command === "init") {
|
|
4473
|
+
await runInit(a.project || process.cwd(), a);
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4476
|
+
if (command === "mcp") {
|
|
4477
|
+
const { buildMcpServer: buildMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
4478
|
+
const { StdioServerTransport: StdioServerTransport2 } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4479
|
+
const ctx2 = openWorkbench(a.project ?? process.env.WORKBENCH_PROJECT);
|
|
4480
|
+
await buildMcpServer2(ctx2).connect(new StdioServerTransport2());
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
if (command === "serve") {
|
|
4484
|
+
const { createServer: createServer2 } = await Promise.resolve().then(() => (init_app(), app_exports));
|
|
4485
|
+
const net = await import("node:net");
|
|
4486
|
+
const ctx2 = openWorkbench(a.project ?? process.env.WORKBENCH_PROJECT);
|
|
4487
|
+
const start = parseInt(a.port ?? process.env.WORKBENCH_PORT ?? "5620");
|
|
4488
|
+
const host = a.host ?? process.env.WORKBENCH_HOST ?? "0.0.0.0";
|
|
4489
|
+
const inUse = (p) => new Promise((resolve2) => {
|
|
4490
|
+
const sock = net.connect({ port: p, host: "127.0.0.1" });
|
|
4491
|
+
sock.setTimeout(400);
|
|
4492
|
+
sock.once("connect", () => {
|
|
4493
|
+
sock.destroy();
|
|
4494
|
+
resolve2(true);
|
|
4495
|
+
});
|
|
4496
|
+
sock.once("timeout", () => {
|
|
4497
|
+
sock.destroy();
|
|
4498
|
+
resolve2(false);
|
|
4499
|
+
});
|
|
4500
|
+
sock.once("error", () => resolve2(false));
|
|
4501
|
+
});
|
|
4502
|
+
let port = start;
|
|
4503
|
+
for (let i = 0; i < 30 && await inUse(port); i++) port++;
|
|
4504
|
+
if (port !== start) console.error(`\u7AEF\u53E3 ${start} \u88AB\u5360\u7528,\u6539\u7528 ${port}`);
|
|
4505
|
+
const app = await createServer2(ctx2);
|
|
4506
|
+
await app.listen({ port, host });
|
|
4507
|
+
console.log(`opcflow: http://127.0.0.1:${port} (host: ${host}, project: ${ctx2.root})`);
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4510
|
+
if (command === "hook") {
|
|
4511
|
+
const platform = a.platform ?? "claude";
|
|
4512
|
+
if (a._ === "pre") {
|
|
4513
|
+
const { writeGateHook: writeGateHook2 } = await Promise.resolve().then(() => (init_hook_pretooluse(), hook_pretooluse_exports));
|
|
4514
|
+
await writeGateHook2(platform).catch(() => {
|
|
4515
|
+
});
|
|
4516
|
+
} else if (a._ === "post") {
|
|
4517
|
+
const { refreshHook: refreshHook2 } = await Promise.resolve().then(() => (init_hook_refresh(), hook_refresh_exports));
|
|
4518
|
+
await refreshHook2(platform).catch(() => {
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
process.exit(0);
|
|
4522
|
+
}
|
|
4523
|
+
if (command === "postcommit") {
|
|
4524
|
+
try {
|
|
4525
|
+
const { scanArtifacts: scanArtifacts2, syncArtifacts: syncArtifacts2, detectOrphanCommit: detectOrphanCommit2 } = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
4526
|
+
const { exportEventLog: exportEventLog2 } = await Promise.resolve().then(() => (init_retro_command(), retro_command_exports));
|
|
4527
|
+
const ctx2 = openWorkbench(process.cwd());
|
|
4528
|
+
scanArtifacts2(ctx2, "post-commit");
|
|
4529
|
+
syncArtifacts2(ctx2, "post-commit");
|
|
4530
|
+
detectOrphanCommit2(ctx2);
|
|
4531
|
+
exportEventLog2(ctx2);
|
|
4532
|
+
} catch {
|
|
4533
|
+
}
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
4536
|
+
const ctx = openWorkbench(a.project);
|
|
4537
|
+
if (!command) {
|
|
4538
|
+
printTasks(listTasks(ctx, {}));
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
try {
|
|
4542
|
+
await runCommand(ctx, command, a);
|
|
4543
|
+
} catch (err) {
|
|
4544
|
+
console.error(`
|
|
4545
|
+
\u2717 ${err.message}
|
|
4546
|
+
`);
|
|
4547
|
+
process.exit(1);
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
main();
|