@dawipong/opcflow 0.5.0 → 0.7.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/dist/cli.mjs +172 -102
- package/package.json +1 -1
- package/templates/agents/en/architect.md +1 -1
- package/templates/agents/en/designer.md +4 -4
- package/templates/agents/en/developer.md +3 -3
- package/templates/agents/en/product-manager.md +3 -3
- package/templates/agents/en/qa.md +1 -1
- package/templates/agents/zh/architect.md +1 -1
- package/templates/agents/zh/designer.md +4 -4
- package/templates/agents/zh/developer.md +3 -3
- package/templates/agents/zh/product-manager.md +3 -3
- package/templates/agents/zh/qa.md +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -66,6 +66,7 @@ var init_config = __esm({
|
|
|
66
66
|
moduleMapping: {},
|
|
67
67
|
feedbackHalfLifeDays: 15,
|
|
68
68
|
gates: { approvalMode: "warn", writeGate: "observe" },
|
|
69
|
+
taskPreconditions: [{ role: "qa", type: "qa", requiresSiblingRoleCompleted: "developer" }],
|
|
69
70
|
git: { taskTrailer: "off", trailerKey: "Task" },
|
|
70
71
|
legacyDb: "tasks/task.db",
|
|
71
72
|
dataDir: ".workbench",
|
|
@@ -409,6 +410,13 @@ var init_events = __esm({
|
|
|
409
410
|
});
|
|
410
411
|
|
|
411
412
|
// core/platforms.ts
|
|
413
|
+
var platforms_exports = {};
|
|
414
|
+
__export(platforms_exports, {
|
|
415
|
+
PLATFORM_IDS: () => PLATFORM_IDS,
|
|
416
|
+
getAdapter: () => getAdapter,
|
|
417
|
+
resolveModel: () => resolveModel,
|
|
418
|
+
resolvePlatforms: () => resolvePlatforms
|
|
419
|
+
});
|
|
412
420
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
413
421
|
import { dirname as dirname2, join as join4 } from "node:path";
|
|
414
422
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
@@ -500,6 +508,12 @@ var init_platforms = __esm({
|
|
|
500
508
|
hooksScanDir: ".claude/hooks",
|
|
501
509
|
defaultModel: "opus",
|
|
502
510
|
memoryBlock: (role, lang) => claudeMemory(role, lang),
|
|
511
|
+
projectDirEnvVar: "CLAUDE_PROJECT_DIR",
|
|
512
|
+
parseHookInput: (raw) => ({
|
|
513
|
+
filePath: raw?.tool_input?.file_path ?? raw?.tool_input?.filePath
|
|
514
|
+
}),
|
|
515
|
+
respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
|
|
516
|
+
formatModel: (_providerId, modelId) => modelId,
|
|
503
517
|
notes: [],
|
|
504
518
|
agentFile: (role) => `${role}.md`,
|
|
505
519
|
renderAgent: (spec) => yamlFrontmatter(
|
|
@@ -543,6 +557,12 @@ var init_platforms = __esm({
|
|
|
543
557
|
hooksScanDir: null,
|
|
544
558
|
defaultModel: "gpt-5.1-codex",
|
|
545
559
|
memoryBlock: (_role, lang) => agentsMdMemory("Codex", lang),
|
|
560
|
+
projectDirEnvVar: "CODEX_PROJECT_DIR",
|
|
561
|
+
parseHookInput: (raw) => ({
|
|
562
|
+
filePath: raw?.arguments?.file_path ?? raw?.arguments?.filePath
|
|
563
|
+
}),
|
|
564
|
+
respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
|
|
565
|
+
formatModel: (_providerId, modelId) => modelId,
|
|
546
566
|
notes: [
|
|
547
567
|
'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"',
|
|
548
568
|
"Codex:skill \u8D70 .agents/skills/(\u975E .codex/skills)"
|
|
@@ -583,6 +603,12 @@ var init_platforms = __esm({
|
|
|
583
603
|
hooksScanDir: null,
|
|
584
604
|
defaultModel: "anthropic/claude-opus-4-8",
|
|
585
605
|
memoryBlock: (_role, lang) => agentsMdMemory("OpenCode", lang),
|
|
606
|
+
projectDirEnvVar: "OPENCODE_PROJECT_DIR",
|
|
607
|
+
parseHookInput: (raw) => ({
|
|
608
|
+
filePath: raw?.args?.file_path ?? raw?.args?.filePath
|
|
609
|
+
}),
|
|
610
|
+
respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
|
|
611
|
+
formatModel: (providerId, modelId) => `${providerId}/${modelId}`,
|
|
586
612
|
notes: [
|
|
587
613
|
"OpenCode:\u6A21\u578B\u4E32\u662F provider/model \u683C\u5F0F(\u89C1 models.dev);API key \u5EFA\u8BAE\u8D70\u73AF\u5883\u53D8\u91CF\u6216 {env:...}"
|
|
588
614
|
],
|
|
@@ -651,6 +677,15 @@ export const opcflow = async () => ({
|
|
|
651
677
|
hooksScanDir: null,
|
|
652
678
|
defaultModel: "claude-opus-4-8",
|
|
653
679
|
memoryBlock: (_role, lang) => cursorMemory(lang),
|
|
680
|
+
projectDirEnvVar: "CURSOR_PROJECT_DIR",
|
|
681
|
+
parseHookInput: (raw) => ({
|
|
682
|
+
filePath: raw?.file_path ?? raw?.filePath
|
|
683
|
+
}),
|
|
684
|
+
respondBlocked: (msg) => ({
|
|
685
|
+
exitCode: 0,
|
|
686
|
+
stdout: JSON.stringify({ permission: "deny", userMessage: msg, agentMessage: msg })
|
|
687
|
+
}),
|
|
688
|
+
formatModel: (_providerId, modelId) => modelId,
|
|
654
689
|
notes: [
|
|
655
690
|
"Cursor:\u4E3B agent \u6A21\u578B\u7531 UI \u9009,--model \u53EA\u4F5C\u7528\u4E8E\u751F\u6210\u7684 subagent(.cursor/agents/*.md)"
|
|
656
691
|
],
|
|
@@ -700,6 +735,7 @@ __export(kind_exports, {
|
|
|
700
735
|
expandPattern: () => expandPattern,
|
|
701
736
|
getKindRegistry: () => getKindRegistry,
|
|
702
737
|
inferKind: () => inferKind,
|
|
738
|
+
kindPathTemplate: () => kindPathTemplate,
|
|
703
739
|
kindSpec: () => kindSpec,
|
|
704
740
|
normalizeModule: () => normalizeModule
|
|
705
741
|
});
|
|
@@ -731,6 +767,14 @@ function contractKinds(config) {
|
|
|
731
767
|
function expandPattern(pattern, config) {
|
|
732
768
|
return pattern.replace("{prd}", config.docs.prd).replace("{architecture}", config.docs.architecture).replace("{design}", config.docs.design).replace("{acceptance}", config.docs.acceptance);
|
|
733
769
|
}
|
|
770
|
+
function kindPathTemplate(config, kind, lang) {
|
|
771
|
+
const spec = getKindRegistry(config)[kind];
|
|
772
|
+
if (!spec?.coords || !spec.pathPatterns?.[0]) return null;
|
|
773
|
+
const prefix = expandPattern(spec.pathPatterns[0], config);
|
|
774
|
+
const ext = spec.ext ?? "md";
|
|
775
|
+
const segs = spec.coords.split("/").map((s) => lang === "zh" ? COORD_LABELS_ZH[s] ?? s : s);
|
|
776
|
+
return `${prefix}${segs.join("/")}.${ext}`;
|
|
777
|
+
}
|
|
734
778
|
function inferKind(relPath, config) {
|
|
735
779
|
const p = relPath.replace(/\\/g, "/");
|
|
736
780
|
const registry = getKindRegistry(config);
|
|
@@ -748,7 +792,7 @@ function normalizeModule(module, config) {
|
|
|
748
792
|
if (!module) return null;
|
|
749
793
|
return config.moduleMapping[module] ?? module;
|
|
750
794
|
}
|
|
751
|
-
var DEFAULT_KIND_REGISTRY, META_KINDS, PM_KINDS, KIND_TIERS;
|
|
795
|
+
var DEFAULT_KIND_REGISTRY, META_KINDS, PM_KINDS, KIND_TIERS, COORD_LABELS_ZH;
|
|
752
796
|
var init_kind = __esm({
|
|
753
797
|
"core/kind.ts"() {
|
|
754
798
|
"use strict";
|
|
@@ -770,7 +814,7 @@ var init_kind = __esm({
|
|
|
770
814
|
"api-doc": { level: "module", approval: "human", parents: ["module-prd", "db-doc"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{architecture}/api/"], coords: "{module}", defaultEndpoint: "service" },
|
|
771
815
|
"design-system": { level: "endpoint", approval: "human", parents: ["baseline"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{design}/systems/"], coords: "{endpoint}" },
|
|
772
816
|
"design-prompt": { level: "page", approval: "none", parents: ["page-prd", "api-doc", "design-system"], drivesStale: false, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{design}/prompts/"], coords: "{endpoint}/{module}/{page}" },
|
|
773
|
-
prototype: { level: "page", approval: "thumbs", parents: ["design-prompt", "design-system"], drivesStale: true, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{design}/prototypes/"], coords: "{endpoint}/{module}/{page}" },
|
|
817
|
+
prototype: { level: "page", approval: "thumbs", parents: ["design-prompt", "design-system"], drivesStale: true, hashMode: "text-normalize", retrieval: "summary", pathPatterns: ["{design}/prototypes/"], coords: "{endpoint}/{module}/{page}", ext: "html" },
|
|
774
818
|
acceptance: { level: "page", approval: "human", parents: ["page-prd", "prototype"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{acceptance}/"], coords: "{endpoint}/{module}/{page}" },
|
|
775
819
|
code: { level: "module", approval: "machine", parents: ["baseline", "api-doc", "prototype"], drivesStale: false, hashMode: "directory", retrieval: "semantic" },
|
|
776
820
|
doc: { level: "module", approval: "none", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "summary" }
|
|
@@ -789,6 +833,7 @@ var init_kind = __esm({
|
|
|
789
833
|
["prototype", "acceptance"],
|
|
790
834
|
["code"]
|
|
791
835
|
];
|
|
836
|
+
COORD_LABELS_ZH = { "{module}": "{\u6A21\u5757}", "{endpoint}": "{\u7AEF}", "{page}": "{\u9875\u9762}" };
|
|
792
837
|
}
|
|
793
838
|
});
|
|
794
839
|
|
|
@@ -992,20 +1037,27 @@ function requirementSatisfied(ctx, req, matched) {
|
|
|
992
1037
|
(a) => kindSpec(ctx.config, a.kind).approval === "thumbs" ? prototypeEndorsed(ctx.db, a) : reviewStatus(a) === "approved"
|
|
993
1038
|
);
|
|
994
1039
|
}
|
|
995
|
-
function
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const dev = ctx.db.prepare(
|
|
1040
|
+
function siblingRoleBlocking(ctx, task) {
|
|
1041
|
+
for (const rule of ctx.config.taskPreconditions ?? []) {
|
|
1042
|
+
if (rule.role !== task.role) continue;
|
|
1043
|
+
if (rule.type && rule.type !== task.type) continue;
|
|
1044
|
+
const done = ctx.db.prepare(
|
|
1001
1045
|
`SELECT COUNT(*) AS c FROM tasks
|
|
1002
|
-
WHERE role =
|
|
1046
|
+
WHERE role = ? AND status = 'completed' AND module IS ? AND endpoint IS ?
|
|
1003
1047
|
AND (? IS NULL OR page IS ?)`
|
|
1004
|
-
).get(task.module, task.endpoint, task.page, task.page);
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1048
|
+
).get(rule.requiresSiblingRoleCompleted, task.module, task.endpoint, task.page, task.page);
|
|
1049
|
+
if (done.c === 0) {
|
|
1050
|
+
return `\u5BF9\u5E94\u7684 ${rule.requiresSiblingRoleCompleted} ${task.endpoint ?? ""} \u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210,\u65E0\u6CD5\u9886\u53D6 ${task.role} \u4EFB\u52A1\u3002`;
|
|
1007
1051
|
}
|
|
1008
1052
|
}
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
function validateClaim(ctx, task) {
|
|
1056
|
+
const warnings = [];
|
|
1057
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
1058
|
+
const mode = ctx.config.gates.approvalMode;
|
|
1059
|
+
const blockMsg = siblingRoleBlocking(ctx, task);
|
|
1060
|
+
if (blockMsg) throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${blockMsg}`);
|
|
1009
1061
|
if (task.module && (task.type === "build" || task.type === "hotfix") && !moduleCleared(ctx.db, task.module)) {
|
|
1010
1062
|
warnings.push(
|
|
1011
1063
|
`[\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}`
|
|
@@ -1068,19 +1120,18 @@ function validateComplete(ctx, task, status, opts = {}) {
|
|
|
1068
1120
|
warnings.push(msg);
|
|
1069
1121
|
}
|
|
1070
1122
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
|
|
1123
|
+
const violations = runProtocolLints(ctx, { role: task.role, endpoint: task.endpoint });
|
|
1124
|
+
if (violations.length > 0) {
|
|
1125
|
+
const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
|
|
1075
1126
|
${v.text}`).join("\n");
|
|
1076
|
-
|
|
1077
|
-
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`[\u534F\u8BAE lint \u5931\u8D25] ${violations.length} \u5904\u8FDD\u4F8B,\u4FEE\u590D\u540E\u518D\u5B8C\u6210:
|
|
1078
1129
|
${detail}${violations.length > 10 ? `
|
|
1079
1130
|
...\u7B49 ${violations.length} \u5904` : ""}`
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1131
|
+
);
|
|
1082
1132
|
}
|
|
1083
|
-
|
|
1133
|
+
const producesMachineKind = producedKinds(ctx, task).some((k) => kindSpec(ctx.config, k).approval === "machine");
|
|
1134
|
+
if (producesMachineKind && task.endpoint && ctx.config.machineChecks.enabled) {
|
|
1084
1135
|
const cmds = ctx.config.machineChecks[task.endpoint];
|
|
1085
1136
|
if (Array.isArray(cmds)) {
|
|
1086
1137
|
for (const cmd of cmds) {
|
|
@@ -1125,7 +1176,7 @@ function taskBlocked(ctx, task) {
|
|
|
1125
1176
|
validateClaim(ctx, task);
|
|
1126
1177
|
return false;
|
|
1127
1178
|
} catch (err) {
|
|
1128
|
-
return err instanceof Error && err.message.includes("\
|
|
1179
|
+
return err instanceof Error && err.message.includes("[\u524D\u7F6E\u6761\u4EF6]");
|
|
1129
1180
|
}
|
|
1130
1181
|
}
|
|
1131
1182
|
function coordHealth(ctx, artifacts, tasks) {
|
|
@@ -1206,6 +1257,7 @@ function buildTree(ctx, opts = {}) {
|
|
|
1206
1257
|
eTasks.filter((t) => !t.page)
|
|
1207
1258
|
);
|
|
1208
1259
|
for (const child of pageNodes) health2 = worse(health2, child.health);
|
|
1260
|
+
if (coordFailed(ctx, module, endpoint, null)) health2 = "failed";
|
|
1209
1261
|
return {
|
|
1210
1262
|
key: `${module}/${endpoint}`,
|
|
1211
1263
|
title: endpoint,
|
|
@@ -1754,16 +1806,16 @@ function ownerRole(ctx, kind) {
|
|
|
1754
1806
|
if (kind === "code") return "developer";
|
|
1755
1807
|
return null;
|
|
1756
1808
|
}
|
|
1757
|
-
function
|
|
1758
|
-
const
|
|
1759
|
-
`SELECT
|
|
1809
|
+
function openReviewTargetKeys(ctx, artifactId) {
|
|
1810
|
+
const rows = ctx.db.prepare(
|
|
1811
|
+
`SELECT t.role, t.endpoint, t.module FROM tasks t
|
|
1760
1812
|
JOIN task_inputs ti ON ti.task_id = t.id
|
|
1761
1813
|
WHERE t.type = 'review' AND t.status IN ('pending', 'in_progress') AND ti.artifact_id = ?`
|
|
1762
|
-
).
|
|
1763
|
-
return
|
|
1814
|
+
).all(artifactId);
|
|
1815
|
+
return new Set(rows.map((r) => `${r.role}|${r.endpoint ?? ""}|${r.module ?? ""}`));
|
|
1764
1816
|
}
|
|
1765
1817
|
function spawnReviews(ctx, source, reason) {
|
|
1766
|
-
|
|
1818
|
+
const openKeys = openReviewTargetKeys(ctx, source.id);
|
|
1767
1819
|
const registry = getKindRegistry(ctx.config);
|
|
1768
1820
|
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);
|
|
1769
1821
|
const targets = /* @__PURE__ */ new Map();
|
|
@@ -1772,6 +1824,7 @@ function spawnReviews(ctx, source, reason) {
|
|
|
1772
1824
|
const role = ownerRole(ctx, child.kind);
|
|
1773
1825
|
if (!role) continue;
|
|
1774
1826
|
const key = `${role}|${child.endpoint ?? ""}|${child.module ?? source.module ?? ""}`;
|
|
1827
|
+
if (openKeys.has(key)) continue;
|
|
1775
1828
|
targets.set(key, { role, endpoint: child.endpoint, module: child.module ?? source.module });
|
|
1776
1829
|
}
|
|
1777
1830
|
let spawned = 0;
|
|
@@ -1969,6 +2022,7 @@ function updateTask(ctx, { id, status, operator, force = false }) {
|
|
|
1969
2022
|
throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u66F4\u65B0\u4EFB\u52A1\u72B6\u6001`);
|
|
1970
2023
|
}
|
|
1971
2024
|
const { warnings } = validateComplete(ctx, task, status, { force });
|
|
2025
|
+
let followupQaId = null;
|
|
1972
2026
|
const tx = ctx.db.transaction(() => {
|
|
1973
2027
|
ctx.db.prepare("UPDATE tasks SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, id);
|
|
1974
2028
|
logEvent(ctx.db, {
|
|
@@ -1981,15 +2035,15 @@ function updateTask(ctx, { id, status, operator, force = false }) {
|
|
|
1981
2035
|
endpoint: task.endpoint,
|
|
1982
2036
|
page: task.page
|
|
1983
2037
|
});
|
|
2038
|
+
if (task.type === "rework" && status === "completed") {
|
|
2039
|
+
followupQaId = spawnFollowupQa(ctx, task);
|
|
2040
|
+
}
|
|
1984
2041
|
});
|
|
1985
2042
|
tx();
|
|
2043
|
+
if (followupQaId !== null) warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${followupQaId}`);
|
|
1986
2044
|
if (task.type === "hotfix" && status === "completed") {
|
|
1987
2045
|
warnings.push(...checkContractTouch(ctx, task, operator));
|
|
1988
2046
|
}
|
|
1989
|
-
if (task.type === "rework" && status === "completed") {
|
|
1990
|
-
const qaId = spawnFollowupQa(ctx, task);
|
|
1991
|
-
warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${qaId}`);
|
|
1992
|
-
}
|
|
1993
2047
|
if (status === "completed" && task.external_ref?.startsWith("gh#")) {
|
|
1994
2048
|
const closed = closeLinkedIssue(ctx, task.external_ref, task.id);
|
|
1995
2049
|
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`);
|
|
@@ -2938,6 +2992,8 @@ var init_retro_command = __esm({
|
|
|
2938
2992
|
});
|
|
2939
2993
|
|
|
2940
2994
|
// core/commands/plan.command.ts
|
|
2995
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2996
|
+
import { join as join12 } from "node:path";
|
|
2941
2997
|
function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
2942
2998
|
const module = normalizeModule(moduleRaw, ctx.config);
|
|
2943
2999
|
const summary = { created: [], skipped: 0, cancelled: 0, warnings: [] };
|
|
@@ -2961,7 +3017,7 @@ function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
|
2961
3017
|
if (ctx.config.gates.approvalMode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
|
|
2962
3018
|
summary.warnings.push(msg);
|
|
2963
3019
|
}
|
|
2964
|
-
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page);
|
|
3020
|
+
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page && existsSync10(join12(ctx.root, a.path)));
|
|
2965
3021
|
const endpoints = [...new Set(pagePrds.map((a) => a.endpoint))];
|
|
2966
3022
|
const desired = [
|
|
2967
3023
|
{ role: "architect", endpoint: "common", page: null, type: "build", assignee: "architect", content: `\u8BBE\u8BA1 ${module} \u6A21\u5757\u6570\u636E\u5E93` },
|
|
@@ -3041,11 +3097,11 @@ var init_plan_command = __esm({
|
|
|
3041
3097
|
});
|
|
3042
3098
|
|
|
3043
3099
|
// core/commands/install-hooks.command.ts
|
|
3044
|
-
import { chmodSync, existsSync as
|
|
3045
|
-
import { join as
|
|
3100
|
+
import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3101
|
+
import { join as join13 } from "node:path";
|
|
3046
3102
|
function installGitHooks(ctx) {
|
|
3047
|
-
const hooksDir =
|
|
3048
|
-
if (!
|
|
3103
|
+
const hooksDir = join13(ctx.root, ".git", "hooks");
|
|
3104
|
+
if (!existsSync11(join13(ctx.root, ".git"))) throw new Error("\u4E0D\u662F git \u4ED3\u5E93");
|
|
3049
3105
|
mkdirSync5(hooksDir, { recursive: true });
|
|
3050
3106
|
const installed = [];
|
|
3051
3107
|
const postCommit = `#!/bin/sh
|
|
@@ -3053,9 +3109,9 @@ function installGitHooks(ctx) {
|
|
|
3053
3109
|
${WORKBENCH_BIN} postcommit >/dev/null 2>&1 &
|
|
3054
3110
|
exit 0
|
|
3055
3111
|
`;
|
|
3056
|
-
writeFileSync4(
|
|
3112
|
+
writeFileSync4(join13(hooksDir, "post-commit"), postCommit);
|
|
3057
3113
|
try {
|
|
3058
|
-
chmodSync(
|
|
3114
|
+
chmodSync(join13(hooksDir, "post-commit"), 493);
|
|
3059
3115
|
} catch {
|
|
3060
3116
|
}
|
|
3061
3117
|
installed.push("post-commit(sync \u5BF9\u8D26)");
|
|
@@ -3069,9 +3125,9 @@ if [ -n "$WORKBENCH_TASK_ID" ] && [ "$2" != "merge" ]; then
|
|
|
3069
3125
|
fi
|
|
3070
3126
|
exit 0
|
|
3071
3127
|
`;
|
|
3072
|
-
writeFileSync4(
|
|
3128
|
+
writeFileSync4(join13(hooksDir, "prepare-commit-msg"), prepare);
|
|
3073
3129
|
try {
|
|
3074
|
-
chmodSync(
|
|
3130
|
+
chmodSync(join13(hooksDir, "prepare-commit-msg"), 493);
|
|
3075
3131
|
} catch {
|
|
3076
3132
|
}
|
|
3077
3133
|
installed.push(`prepare-commit-msg(${ctx.config.git.trailerKey} trailer \u6CE8\u5165)`);
|
|
@@ -3086,8 +3142,8 @@ var init_install_hooks_command = __esm({
|
|
|
3086
3142
|
});
|
|
3087
3143
|
|
|
3088
3144
|
// core/commands/gen-agents.command.ts
|
|
3089
|
-
import { existsSync as
|
|
3090
|
-
import { dirname as dirname3, join as
|
|
3145
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3146
|
+
import { dirname as dirname3, join as join14 } from "node:path";
|
|
3091
3147
|
function expandPath(ctx, kind) {
|
|
3092
3148
|
const spec = getKindRegistry(ctx.config)[kind];
|
|
3093
3149
|
const raw = spec?.pathPatterns?.[0] ?? "";
|
|
@@ -3113,8 +3169,8 @@ function parseTemplate(rawInput) {
|
|
|
3113
3169
|
function genAgents(ctx, templatesDir) {
|
|
3114
3170
|
const lang = ctx.config.language;
|
|
3115
3171
|
const blocks = SHARED[lang] ?? SHARED.zh;
|
|
3116
|
-
const dir = templatesDir ??
|
|
3117
|
-
if (!
|
|
3172
|
+
const dir = templatesDir ?? join14(WORKBENCH_DIR, "templates/agents", lang);
|
|
3173
|
+
if (!existsSync12(dir)) throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${dir}`);
|
|
3118
3174
|
const cli = ctx.config.cli;
|
|
3119
3175
|
const baseTokens = {
|
|
3120
3176
|
CLI: cli,
|
|
@@ -3132,6 +3188,16 @@ function genAgents(ctx, templatesDir) {
|
|
|
3132
3188
|
PATH_DESIGN_PROMPTS: expandPath(ctx, "design-prompt"),
|
|
3133
3189
|
PATH_PROTOTYPES: expandPath(ctx, "prototype"),
|
|
3134
3190
|
PATH_ACCEPTANCE: expandPath(ctx, "acceptance"),
|
|
3191
|
+
// 路径投影:完整路径模板由 kind 注册表(前缀+coords 文法+ext)推导——
|
|
3192
|
+
// 项目覆盖 coords 后重跑 gen-agents,agent 指示与 scan 解析自动同步(单一真相源)
|
|
3193
|
+
TPL_FLOW: kindPathTemplate(ctx.config, "flow", lang) ?? "",
|
|
3194
|
+
TPL_MODULE_PRD: kindPathTemplate(ctx.config, "module-prd", lang) ?? "",
|
|
3195
|
+
TPL_PAGE_PRD: kindPathTemplate(ctx.config, "page-prd", lang) ?? "",
|
|
3196
|
+
TPL_DB_DOC: kindPathTemplate(ctx.config, "db-doc", lang) ?? "",
|
|
3197
|
+
TPL_DESIGN_SYSTEM: kindPathTemplate(ctx.config, "design-system", lang) ?? "",
|
|
3198
|
+
TPL_DESIGN_PROMPT: kindPathTemplate(ctx.config, "design-prompt", lang) ?? "",
|
|
3199
|
+
TPL_PROTOTYPE: kindPathTemplate(ctx.config, "prototype", lang) ?? "",
|
|
3200
|
+
TPL_ACCEPTANCE: kindPathTemplate(ctx.config, "acceptance", lang) ?? "",
|
|
3135
3201
|
ENDPOINTS: ctx.config.endpoints.join(" / "),
|
|
3136
3202
|
PIPELINE: ctx.config.pipeline.join(" \u2192 "),
|
|
3137
3203
|
CODE_ROOTS: ctx.config.endpoints.map((e) => `| ${e} | ${(ctx.config.codeRoots[e] ?? []).join(blocks.sep) || blocks.todo} |`).join("\n")
|
|
@@ -3151,7 +3217,7 @@ function genAgents(ctx, templatesDir) {
|
|
|
3151
3217
|
if (!file.endsWith(".md")) continue;
|
|
3152
3218
|
const role = file.replace(/\.md$/, "");
|
|
3153
3219
|
if (!pipeline.has(role)) continue;
|
|
3154
|
-
const tpl = parseTemplate(readFileSync6(
|
|
3220
|
+
const tpl = parseTemplate(readFileSync6(join14(dir, file), "utf-8"));
|
|
3155
3221
|
for (const adapter of adapters) {
|
|
3156
3222
|
const memory = adapter.memoryBlock(role, lang);
|
|
3157
3223
|
const spec = {
|
|
@@ -3162,7 +3228,7 @@ function genAgents(ctx, templatesDir) {
|
|
|
3162
3228
|
body: renderTokens(tpl.body, memory)
|
|
3163
3229
|
};
|
|
3164
3230
|
const rel = `${adapter.agentsDir}/${adapter.agentFile(role)}`;
|
|
3165
|
-
const abs =
|
|
3231
|
+
const abs = join14(ctx.root, rel);
|
|
3166
3232
|
mkdirSync6(dirname3(abs), { recursive: true });
|
|
3167
3233
|
writeFileSync5(abs, adapter.renderAgent(spec));
|
|
3168
3234
|
written.push(rel);
|
|
@@ -3246,8 +3312,8 @@ When MCP is registered, prefer the \`wb_*\` typed tools (same source & transacti
|
|
|
3246
3312
|
});
|
|
3247
3313
|
|
|
3248
3314
|
// core/commands/audit.command.ts
|
|
3249
|
-
import { existsSync as
|
|
3250
|
-
import { join as
|
|
3315
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
3316
|
+
import { join as join15 } from "node:path";
|
|
3251
3317
|
function auditModule(ctx, moduleRaw) {
|
|
3252
3318
|
const module = normalizeModule(moduleRaw, ctx.config);
|
|
3253
3319
|
const kinds = contractKinds(ctx.config);
|
|
@@ -3257,9 +3323,9 @@ function auditModule(ctx, moduleRaw) {
|
|
|
3257
3323
|
path: a.path,
|
|
3258
3324
|
status: reviewStatus(a),
|
|
3259
3325
|
everApproved: everApproved(a),
|
|
3260
|
-
onDisk:
|
|
3326
|
+
onDisk: existsSync13(join15(ctx.root, a.path))
|
|
3261
3327
|
}));
|
|
3262
|
-
const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk:
|
|
3328
|
+
const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk: existsSync13(join15(ctx.root, a.path)) }));
|
|
3263
3329
|
return {
|
|
3264
3330
|
module,
|
|
3265
3331
|
cleared: moduleCleared(ctx.db, module),
|
|
@@ -3323,28 +3389,28 @@ var init_intake_command = __esm({
|
|
|
3323
3389
|
// core/commands/init.command.ts
|
|
3324
3390
|
import {
|
|
3325
3391
|
copyFileSync,
|
|
3326
|
-
existsSync as
|
|
3392
|
+
existsSync as existsSync14,
|
|
3327
3393
|
mkdirSync as mkdirSync7,
|
|
3328
3394
|
readFileSync as readFileSync7,
|
|
3329
3395
|
readdirSync as readdirSync6,
|
|
3330
3396
|
writeFileSync as writeFileSync6
|
|
3331
3397
|
} from "node:fs";
|
|
3332
|
-
import { dirname as dirname4, join as
|
|
3398
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
3333
3399
|
function deployPreset(root) {
|
|
3334
3400
|
const deployed = [];
|
|
3335
|
-
if (!
|
|
3401
|
+
if (!existsSync14(PRESET_DIR)) return deployed;
|
|
3336
3402
|
const walk = (rel) => {
|
|
3337
|
-
for (const ent of readdirSync6(
|
|
3403
|
+
for (const ent of readdirSync6(join16(PRESET_DIR, rel), {
|
|
3338
3404
|
withFileTypes: true
|
|
3339
3405
|
})) {
|
|
3340
|
-
const childRel = rel ?
|
|
3406
|
+
const childRel = rel ? join16(rel, ent.name) : ent.name;
|
|
3341
3407
|
if (ent.isDirectory()) {
|
|
3342
3408
|
walk(childRel);
|
|
3343
3409
|
} else if (ent.isFile()) {
|
|
3344
|
-
const dest =
|
|
3345
|
-
if (
|
|
3410
|
+
const dest = join16(root, childRel);
|
|
3411
|
+
if (existsSync14(dest)) continue;
|
|
3346
3412
|
mkdirSync7(dirname4(dest), { recursive: true });
|
|
3347
|
-
copyFileSync(
|
|
3413
|
+
copyFileSync(join16(PRESET_DIR, childRel), dest);
|
|
3348
3414
|
deployed.push(childRel);
|
|
3349
3415
|
}
|
|
3350
3416
|
}
|
|
@@ -3353,8 +3419,8 @@ function deployPreset(root) {
|
|
|
3353
3419
|
return deployed;
|
|
3354
3420
|
}
|
|
3355
3421
|
function ensureRootTsx(root) {
|
|
3356
|
-
const pkgPath =
|
|
3357
|
-
if (!
|
|
3422
|
+
const pkgPath = join16(root, "package.json");
|
|
3423
|
+
if (!existsSync14(pkgPath)) return false;
|
|
3358
3424
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
3359
3425
|
if (pkg.devDependencies?.tsx) return false;
|
|
3360
3426
|
pkg.devDependencies = { ...pkg.devDependencies ?? {}, tsx: TSX_VERSION };
|
|
@@ -3362,8 +3428,8 @@ function ensureRootTsx(root) {
|
|
|
3362
3428
|
return true;
|
|
3363
3429
|
}
|
|
3364
3430
|
function initProject(root, opts) {
|
|
3365
|
-
const configPath =
|
|
3366
|
-
if (
|
|
3431
|
+
const configPath = join16(root, CONFIG_FILENAME);
|
|
3432
|
+
if (existsSync14(configPath)) {
|
|
3367
3433
|
throw new Error(
|
|
3368
3434
|
`${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)`
|
|
3369
3435
|
);
|
|
@@ -3397,10 +3463,10 @@ function initProject(root, opts) {
|
|
|
3397
3463
|
const scaffolded = [];
|
|
3398
3464
|
if (opts.scaffold !== false) {
|
|
3399
3465
|
for (const dir of DOC_DIRS) {
|
|
3400
|
-
const abs =
|
|
3401
|
-
if (!
|
|
3466
|
+
const abs = join16(root, dir);
|
|
3467
|
+
if (!existsSync14(abs)) {
|
|
3402
3468
|
mkdirSync7(abs, { recursive: true });
|
|
3403
|
-
writeFileSync6(
|
|
3469
|
+
writeFileSync6(join16(abs, ".gitkeep"), "");
|
|
3404
3470
|
scaffolded.push(dir);
|
|
3405
3471
|
}
|
|
3406
3472
|
}
|
|
@@ -3432,7 +3498,7 @@ function initProject(root, opts) {
|
|
|
3432
3498
|
}
|
|
3433
3499
|
const notes = adapters.flatMap((a) => a.notes);
|
|
3434
3500
|
let hooks = [];
|
|
3435
|
-
if (opts.gitHooks !== false &&
|
|
3501
|
+
if (opts.gitHooks !== false && existsSync14(join16(root, ".git"))) {
|
|
3436
3502
|
try {
|
|
3437
3503
|
hooks = installGitHooks(ctx);
|
|
3438
3504
|
} catch {
|
|
@@ -3474,7 +3540,7 @@ var init_init_command = __esm({
|
|
|
3474
3540
|
"docs/design/prototypes",
|
|
3475
3541
|
"docs/acceptance"
|
|
3476
3542
|
];
|
|
3477
|
-
PRESET_DIR =
|
|
3543
|
+
PRESET_DIR = join16(WORKBENCH_DIR, "preset");
|
|
3478
3544
|
TSX_VERSION = "^4";
|
|
3479
3545
|
}
|
|
3480
3546
|
});
|
|
@@ -3532,6 +3598,7 @@ __export(core_exports, {
|
|
|
3532
3598
|
initProject: () => initProject,
|
|
3533
3599
|
installGitHooks: () => installGitHooks,
|
|
3534
3600
|
intakeIssues: () => intakeIssues,
|
|
3601
|
+
kindPathTemplate: () => kindPathTemplate,
|
|
3535
3602
|
kindSpec: () => kindSpec,
|
|
3536
3603
|
listArtifacts: () => listArtifacts,
|
|
3537
3604
|
listEvents: () => listEvents,
|
|
@@ -3762,17 +3829,17 @@ __export(app_exports, {
|
|
|
3762
3829
|
import fastifyCors from "@fastify/cors";
|
|
3763
3830
|
import fastifyStatic from "@fastify/static";
|
|
3764
3831
|
import Fastify from "fastify";
|
|
3765
|
-
import { existsSync as
|
|
3766
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3832
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
|
|
3833
|
+
import { isAbsolute as isAbsolute2, join as join17, normalize, relative as relative2 } from "node:path";
|
|
3767
3834
|
function getArtifact(ctx, id) {
|
|
3768
3835
|
const row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(id);
|
|
3769
3836
|
if (!row) throw Object.assign(new Error(`artifact #${id} \u4E0D\u5B58\u5728`), { statusCode: 404 });
|
|
3770
3837
|
return row;
|
|
3771
3838
|
}
|
|
3772
3839
|
function guardedAbsPath(ctx, artifact, rel) {
|
|
3773
|
-
const base = normalize(
|
|
3840
|
+
const base = normalize(join17(ctx.root, artifact.path));
|
|
3774
3841
|
if (!rel) return base;
|
|
3775
|
-
const target = normalize(
|
|
3842
|
+
const target = normalize(join17(base, rel));
|
|
3776
3843
|
const r = relative2(base, target);
|
|
3777
3844
|
if (r === "" || r === "." || r.startsWith("..") || isAbsolute2(r)) {
|
|
3778
3845
|
throw Object.assign(new Error("\u8DEF\u5F84\u8D8A\u754C"), { statusCode: 403 });
|
|
@@ -3782,11 +3849,11 @@ function guardedAbsPath(ctx, artifact, rel) {
|
|
|
3782
3849
|
async function createServer(ctx) {
|
|
3783
3850
|
const app = Fastify({ logger: false });
|
|
3784
3851
|
await app.register(fastifyCors, { origin: true });
|
|
3785
|
-
const webDist =
|
|
3786
|
-
if (
|
|
3852
|
+
const webDist = join17(WORKBENCH_DIR, "web/dist");
|
|
3853
|
+
if (existsSync15(webDist)) {
|
|
3787
3854
|
await app.register(fastifyStatic, { root: webDist });
|
|
3788
3855
|
}
|
|
3789
|
-
const protoRoot =
|
|
3856
|
+
const protoRoot = join17(ctx.root, ctx.config.docs.design, "prototypes");
|
|
3790
3857
|
mkdirSync8(protoRoot, { recursive: true });
|
|
3791
3858
|
await app.register(fastifyStatic, { root: protoRoot, prefix: "/proto/", decorateReply: false });
|
|
3792
3859
|
app.get("/api/meta", async () => ({
|
|
@@ -3821,7 +3888,7 @@ async function createServer(ctx) {
|
|
|
3821
3888
|
let content = null;
|
|
3822
3889
|
let isDirectory = false;
|
|
3823
3890
|
let missing = false;
|
|
3824
|
-
if (!
|
|
3891
|
+
if (!existsSync15(abs)) {
|
|
3825
3892
|
missing = true;
|
|
3826
3893
|
} else if (statSync6(abs).isDirectory()) {
|
|
3827
3894
|
isDirectory = true;
|
|
@@ -3837,20 +3904,20 @@ async function createServer(ctx) {
|
|
|
3837
3904
|
const walk = (dir, rel) => {
|
|
3838
3905
|
for (const name of readdirSync7(dir).sort()) {
|
|
3839
3906
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
3840
|
-
const full =
|
|
3907
|
+
const full = join17(dir, name);
|
|
3841
3908
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
3842
3909
|
const st = statSync6(full);
|
|
3843
3910
|
if (st.isDirectory()) walk(full, relPath);
|
|
3844
3911
|
else files.push({ rel: relPath, size: st.size });
|
|
3845
3912
|
}
|
|
3846
3913
|
};
|
|
3847
|
-
if (
|
|
3914
|
+
if (existsSync15(base) && statSync6(base).isDirectory()) walk(base, "");
|
|
3848
3915
|
return { files };
|
|
3849
3916
|
});
|
|
3850
3917
|
app.get("/api/artifact/:id/file", async (req) => {
|
|
3851
3918
|
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3852
3919
|
const abs = guardedAbsPath(ctx, artifact, req.query.rel);
|
|
3853
|
-
if (!
|
|
3920
|
+
if (!existsSync15(abs) || statSync6(abs).isDirectory()) {
|
|
3854
3921
|
throw Object.assign(new Error("\u6587\u4EF6\u4E0D\u5B58\u5728"), { statusCode: 404 });
|
|
3855
3922
|
}
|
|
3856
3923
|
if (statSync6(abs).size > MAX_INLINE_SIZE) {
|
|
@@ -3905,7 +3972,7 @@ data: {"cursor":${cursor2}}
|
|
|
3905
3972
|
endpoint: a.endpoint,
|
|
3906
3973
|
page: a.page,
|
|
3907
3974
|
review_status: reviewStatus(a),
|
|
3908
|
-
missing: !
|
|
3975
|
+
missing: !existsSync15(join17(ctx.root, a.path))
|
|
3909
3976
|
}));
|
|
3910
3977
|
const edges = ctx.db.prepare("SELECT id, from_id, to_id, source FROM artifact_edges").all().filter((e) => ids.has(e.from_id) && ids.has(e.to_id));
|
|
3911
3978
|
return { nodes, edges };
|
|
@@ -3922,7 +3989,7 @@ data: {"cursor":${cursor2}}
|
|
|
3922
3989
|
for (const name of readdirSync7(dir).sort()) {
|
|
3923
3990
|
if (files.length >= 30) return;
|
|
3924
3991
|
if (name.startsWith(".") || name === "node_modules" || name === "dist" || name === "build") continue;
|
|
3925
|
-
const full =
|
|
3992
|
+
const full = join17(dir, name);
|
|
3926
3993
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
3927
3994
|
if (statSync6(full).isDirectory()) walk(full, relPath);
|
|
3928
3995
|
else if (relPath.toLowerCase().includes(q) && !registered.has(relPath)) files.push(relPath);
|
|
@@ -4022,7 +4089,7 @@ data: {"cursor":${cursor2}}
|
|
|
4022
4089
|
app.get("/api/artifact/:id/diff", async (req) => {
|
|
4023
4090
|
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
4024
4091
|
const abs = guardedAbsPath(ctx, artifact);
|
|
4025
|
-
const current =
|
|
4092
|
+
const current = existsSync15(abs) && !statSync6(abs).isDirectory() ? readFileSync8(abs, "utf-8") : null;
|
|
4026
4093
|
return { approved: approvedContent(ctx, artifact.id), current };
|
|
4027
4094
|
});
|
|
4028
4095
|
app.post(
|
|
@@ -4088,15 +4155,17 @@ function hookPlatform() {
|
|
|
4088
4155
|
const arg = process.argv.find((a) => a.startsWith("--platform="));
|
|
4089
4156
|
return arg ? arg.slice("--platform=".length) : "claude";
|
|
4090
4157
|
}
|
|
4091
|
-
function extractFilePath(input) {
|
|
4158
|
+
function extractFilePath(input, adapter) {
|
|
4159
|
+
const fromAdapter = adapter?.parseHookInput(input).filePath;
|
|
4160
|
+
if (fromAdapter) return fromAdapter;
|
|
4092
4161
|
return input?.tool_input?.file_path ?? // claude PreToolUse/PostToolUse
|
|
4093
4162
|
input?.tool_input?.filePath ?? input?.file_path ?? // cursor afterFileEdit / 通用
|
|
4094
4163
|
input?.filePath ?? input?.args?.file_path ?? // opencode tool 参数
|
|
4095
4164
|
input?.args?.filePath ?? input?.arguments?.file_path ?? // codex 兜底
|
|
4096
4165
|
input?.arguments?.filePath ?? input?.input?.file_path ?? void 0;
|
|
4097
4166
|
}
|
|
4098
|
-
function hookProjectDir() {
|
|
4099
|
-
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;
|
|
4167
|
+
function hookProjectDir(adapter) {
|
|
4168
|
+
return (adapter && process.env[adapter.projectDirEnvVar]) ?? 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;
|
|
4100
4169
|
}
|
|
4101
4170
|
async function readStdinJson() {
|
|
4102
4171
|
const chunks = [];
|
|
@@ -4120,14 +4189,16 @@ __export(hook_pretooluse_exports, {
|
|
|
4120
4189
|
});
|
|
4121
4190
|
async function writeGateHook(platform) {
|
|
4122
4191
|
const input = await readStdinJson();
|
|
4123
|
-
const
|
|
4192
|
+
const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
4193
|
+
const adapter = PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
|
|
4194
|
+
const filePath = extractFilePath(input, adapter);
|
|
4124
4195
|
if (!filePath) return;
|
|
4125
4196
|
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
4126
4197
|
const { normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
4127
4198
|
const { reviewStatus: reviewStatus2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
|
|
4128
4199
|
const { contractKinds: contractKinds2 } = await Promise.resolve().then(() => (init_kind(), kind_exports));
|
|
4129
4200
|
const { logEvent: logEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
4130
|
-
const ctx = openWorkbench2(hookProjectDir());
|
|
4201
|
+
const ctx = openWorkbench2(hookProjectDir(adapter));
|
|
4131
4202
|
const mode = ctx.config.gates.writeGate;
|
|
4132
4203
|
if (mode === "off") return;
|
|
4133
4204
|
const rel = normalizeRelPath2(ctx, filePath);
|
|
@@ -4154,12 +4225,10 @@ async function writeGateHook(platform) {
|
|
|
4154
4225
|
const msg = `\u8BE5\u6587\u4EF6\u662F\u5DF2\u5BA1\u6279\u5951\u7EA6(${artifact.kind}): ${rel}\u3002
|
|
4155
4226
|
\u4FEE\u6539\u524D\u8BF7\u5148\u9886\u53D6\u5BF9\u5E94\u4EFB\u52A1:${ctx.config.cli} list --module=${artifact.module ?? ""} --status=pending
|
|
4156
4227
|
\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`;
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
console.error(msg);
|
|
4162
|
-
process.exit(2);
|
|
4228
|
+
const resp = adapter?.respondBlocked(msg) ?? { exitCode: 2, stderr: msg };
|
|
4229
|
+
if (resp.stdout) process.stdout.write(resp.stdout);
|
|
4230
|
+
if (resp.stderr) console.error(resp.stderr);
|
|
4231
|
+
process.exit(resp.exitCode);
|
|
4163
4232
|
}
|
|
4164
4233
|
}
|
|
4165
4234
|
var init_hook_pretooluse = __esm({
|
|
@@ -4178,13 +4247,15 @@ var hook_refresh_exports = {};
|
|
|
4178
4247
|
__export(hook_refresh_exports, {
|
|
4179
4248
|
refreshHook: () => refreshHook
|
|
4180
4249
|
});
|
|
4181
|
-
async function refreshHook(
|
|
4250
|
+
async function refreshHook(platform) {
|
|
4182
4251
|
const input = await readStdinJson();
|
|
4183
|
-
const
|
|
4252
|
+
const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
4253
|
+
const adapter = platform && PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
|
|
4254
|
+
const filePath = extractFilePath(input, adapter);
|
|
4184
4255
|
if (!filePath) return;
|
|
4185
4256
|
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
4186
4257
|
const { refreshArtifact: refreshArtifact2, normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
4187
|
-
const ctx = openWorkbench2(hookProjectDir());
|
|
4258
|
+
const ctx = openWorkbench2(hookProjectDir(adapter));
|
|
4188
4259
|
const rel = normalizeRelPath2(ctx, filePath);
|
|
4189
4260
|
const exact = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(rel);
|
|
4190
4261
|
if (exact) {
|
|
@@ -4284,7 +4355,6 @@ function printTaskDetail(ctx, id) {
|
|
|
4284
4355
|
}
|
|
4285
4356
|
console.log();
|
|
4286
4357
|
}
|
|
4287
|
-
var ALL_PLATFORMS = ["claude", "codex", "opencode", "cursor"];
|
|
4288
4358
|
var PRIORITY_PROVIDERS = [
|
|
4289
4359
|
"anthropic",
|
|
4290
4360
|
"openai",
|
|
@@ -4330,7 +4400,7 @@ async function promptInit() {
|
|
|
4330
4400
|
const zh = language === "zh";
|
|
4331
4401
|
const platform = await select({
|
|
4332
4402
|
message: zh ? "\u5E73\u53F0" : "Platform",
|
|
4333
|
-
choices:
|
|
4403
|
+
choices: PLATFORM_IDS.map((p) => ({ name: p, value: p })),
|
|
4334
4404
|
default: "claude"
|
|
4335
4405
|
});
|
|
4336
4406
|
const platforms = [platform];
|
|
@@ -4358,14 +4428,14 @@ async function promptInit() {
|
|
|
4358
4428
|
choices: prov.models.map((m) => ({ name: m.name, value: m.id })),
|
|
4359
4429
|
pageSize: 12
|
|
4360
4430
|
});
|
|
4361
|
-
if (modelId) modelObj[p] = p
|
|
4431
|
+
if (modelId) modelObj[p] = getAdapter(p).formatModel(provId, modelId);
|
|
4362
4432
|
}
|
|
4363
4433
|
return { platforms, endpoints, model: Object.keys(modelObj).length ? modelObj : void 0, language };
|
|
4364
4434
|
}
|
|
4365
4435
|
async function runInit(root, a) {
|
|
4366
|
-
const { existsSync:
|
|
4367
|
-
const { join:
|
|
4368
|
-
if (
|
|
4436
|
+
const { existsSync: existsSync16 } = await import("node:fs");
|
|
4437
|
+
const { join: join18 } = await import("node:path");
|
|
4438
|
+
if (existsSync16(join18(root, "workbench.config.json"))) {
|
|
4369
4439
|
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)"));
|
|
4370
4440
|
process.exit(1);
|
|
4371
4441
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dawipong/opcflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Spec-anchored, drift-enforced execution layer for AI coding agents — artifact DAG, trust gates, multi-role pipeline; generates agents/MCP/hooks for Claude Code, Codex, OpenCode & Cursor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dawi",
|
|
@@ -29,7 +29,7 @@ tech selection (language / framework / ORM / build), directory structure per end
|
|
|
29
29
|
| Artifact | Path |
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| Database model definition | Per approved TECH.md conventions (path/tech set by the baseline) |
|
|
32
|
-
| Database docs | {{
|
|
32
|
+
| Database docs | {{TPL_DB_DOC}} |
|
|
33
33
|
| API contract docs | {{PATH_API_DOCS}}{endpoint}/{module}.md (cross-endpoint shared goes in common/) |
|
|
34
34
|
| Technical baseline (changes go through review) | ARCHITECTURE.md / TECH.md |
|
|
35
35
|
|
|
@@ -21,9 +21,9 @@ You are @designer. Each of your three artifacts travels a different trust channe
|
|
|
21
21
|
|
|
22
22
|
| Artifact | Path | Trust channel |
|
|
23
23
|
| --- | --- | --- |
|
|
24
|
-
| Design system (one per endpoint) | {{
|
|
25
|
-
| Page design prompt | {{
|
|
26
|
-
| HTML prototype | {{
|
|
24
|
+
| Design system (one per endpoint) | {{TPL_DESIGN_SYSTEM}} | **Human approval** (endpoint-level contract; one change makes every prototype for that endpoint stale) |
|
|
25
|
+
| Page design prompt | {{TPL_DESIGN_PROMPT}} | Register only (working draft, not submitted) |
|
|
26
|
+
| HTML prototype | {{TPL_PROTOTYPE}} | **👍 = feedback + approval in one** (user releases after previewing in opcflow) |
|
|
27
27
|
|
|
28
28
|
## Workflow (page task)
|
|
29
29
|
|
|
@@ -37,7 +37,7 @@ You are @designer. Each of your three artifacts travels a different trust channe
|
|
|
37
37
|
|
|
38
38
|
## Endpoint Design-System Task (once per endpoint)
|
|
39
39
|
|
|
40
|
-
Write to {{
|
|
40
|
+
Write to {{TPL_DESIGN_SYSTEM}} (palette / spacing / font sizes / component forms / **that endpoint's hard constraints** — platform limits, component-library specs, etc. are all legislated here) → register → **submit for review**.
|
|
41
41
|
For endpoints that already have prototypes or production pages, **reverse-engineer** from the established facts (legislate, don't design from scratch); for a brand-new endpoint, propose an initial version from the approved baseline (the UI stack in TECH.md) and the project's positioning.
|
|
42
42
|
|
|
43
43
|
## Red Flags
|
|
@@ -22,10 +22,10 @@ You are @developer. **Approved contract = implement directly, zero divergence**
|
|
|
22
22
|
| Input | Path |
|
|
23
23
|
| --- | --- |
|
|
24
24
|
| Technical baseline (selection/directories/protocol conventions) | ARCHITECTURE.md / TECH.md |
|
|
25
|
-
| Page PRD (incl. acceptance points) | {{
|
|
25
|
+
| Page PRD (incl. acceptance points) | {{TPL_PAGE_PRD}} |
|
|
26
26
|
| API contract | {{PATH_API_DOCS}}{endpoint}/{module}.md |
|
|
27
|
-
| DB docs | {{
|
|
28
|
-
| 👍-approved prototype (UI truth) | {{
|
|
27
|
+
| DB docs | {{TPL_DB_DOC}} |
|
|
28
|
+
| 👍-approved prototype (UI truth) | {{TPL_PROTOTYPE}} |
|
|
29
29
|
|
|
30
30
|
## Code Directory Conventions (config-injected; follow when building code)
|
|
31
31
|
|
|
@@ -25,9 +25,9 @@ You are @product-manager. Responsibility: translate requirements into **layer-by
|
|
|
25
25
|
| Project overview | {{PATH_PROJECT}} | Project-level contract |
|
|
26
26
|
| Role permission matrix | {{PATH_ROLES}} | Project-level contract |
|
|
27
27
|
| Domain glossary | {{PATH_GLOSSARY}} | Project-level contract |
|
|
28
|
-
| Business flow + entity state machine | {{
|
|
29
|
-
| Module PRD | {{
|
|
30
|
-
| Page PRD | {{
|
|
28
|
+
| Business flow + entity state machine | {{TPL_FLOW}} | Module-level contract |
|
|
29
|
+
| Module PRD | {{TPL_MODULE_PRD}} | Module-level contract |
|
|
30
|
+
| Page PRD | {{TPL_PAGE_PRD}} | Page-level contract |
|
|
31
31
|
|
|
32
32
|
## Core Discipline: Layer-by-Layer Confirmation
|
|
33
33
|
|
|
@@ -20,7 +20,7 @@ You are @qa. **Judgment authority belongs to PM (acceptance points), execution a
|
|
|
20
20
|
## Two-Phase Acceptance
|
|
21
21
|
|
|
22
22
|
**Phase one (before or after developer starts): translate acceptance criteria**
|
|
23
|
-
Read the "acceptance points" section of the approved page PRD → translate into executable cases, write to {{
|
|
23
|
+
Read the "acceptance points" section of the approved page PRD → translate into executable cases, write to {{TPL_ACCEPTANCE}} → register output → **submit for review** (it's a contract; developer writes against it).
|
|
24
24
|
When a point is ambiguous: **dispute or send back to PM**, do not fill in the wording yourself.
|
|
25
25
|
|
|
26
26
|
**Phase two (after developer finishes): execute acceptance**
|
|
@@ -29,7 +29,7 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
29
29
|
| 产物 | 路径 |
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| 数据库模型定义 | 按 approved TECH.md 的约定(路径/技术随基线定) |
|
|
32
|
-
| 数据库文档 | {{
|
|
32
|
+
| 数据库文档 | {{TPL_DB_DOC}} |
|
|
33
33
|
| API 契约文档 | {{PATH_API_DOCS}}{端}/{模块}.md(跨端共用放 common/) |
|
|
34
34
|
| 技术基线(变更走审批) | ARCHITECTURE.md / TECH.md |
|
|
35
35
|
|
|
@@ -21,9 +21,9 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
21
21
|
|
|
22
22
|
| 产物 | 路径 | 信任通道 |
|
|
23
23
|
| --- | --- | --- |
|
|
24
|
-
| 设计系统(每端一份) | {{
|
|
25
|
-
| 页面设计提示词 | {{
|
|
26
|
-
| HTML 原型 | {{
|
|
24
|
+
| 设计系统(每端一份) | {{TPL_DESIGN_SYSTEM}} | **人工审批**(端级契约,改一次全端原型 stale) |
|
|
25
|
+
| 页面设计提示词 | {{TPL_DESIGN_PROMPT}} | 仅登记(工作底稿,不送审) |
|
|
26
|
+
| HTML 原型 | {{TPL_PROTOTYPE}} | **👍 = 反馈+审批合一**(用户在 opcflow 预览后放行) |
|
|
27
27
|
|
|
28
28
|
## 工作流程(页面任务)
|
|
29
29
|
|
|
@@ -37,7 +37,7 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
37
37
|
|
|
38
38
|
## 端设计系统任务(每端一次)
|
|
39
39
|
|
|
40
|
-
写入 {{
|
|
40
|
+
写入 {{TPL_DESIGN_SYSTEM}}(色板/间距/字号/组件形态/**该端硬约束**——平台限制、组件库规范等都在此立法)→ 登记 → **submit 送审**。
|
|
41
41
|
已有原型或生产页面的端,从既成事实**反向提炼**(立法,不凭空设计);全新的端,依据 approved 基线(TECH.md 的 UI 栈)与项目定位提出初版。
|
|
42
42
|
|
|
43
43
|
## Red Flags
|
|
@@ -22,10 +22,10 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
22
22
|
| 输入 | 路径 |
|
|
23
23
|
| --- | --- |
|
|
24
24
|
| 技术基线(选型/目录/协议约定) | ARCHITECTURE.md / TECH.md |
|
|
25
|
-
| 页面 PRD(含验收要点) | {{
|
|
25
|
+
| 页面 PRD(含验收要点) | {{TPL_PAGE_PRD}} |
|
|
26
26
|
| API 契约 | {{PATH_API_DOCS}}{端}/{模块}.md |
|
|
27
|
-
| DB 文档 | {{
|
|
28
|
-
| 已 👍 原型(UI 真相) | {{
|
|
27
|
+
| DB 文档 | {{TPL_DB_DOC}} |
|
|
28
|
+
| 已 👍 原型(UI 真相) | {{TPL_PROTOTYPE}} |
|
|
29
29
|
|
|
30
30
|
## 代码目录约定(config 注入,建代码时遵守)
|
|
31
31
|
|
|
@@ -25,9 +25,9 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
25
25
|
| 项目全景 | {{PATH_PROJECT}} | 项目级契约 |
|
|
26
26
|
| 角色权限矩阵 | {{PATH_ROLES}} | 项目级契约 |
|
|
27
27
|
| 领域术语表 | {{PATH_GLOSSARY}} | 项目级契约 |
|
|
28
|
-
| 业务流程+实体状态机 | {{
|
|
29
|
-
| 模块 PRD | {{
|
|
30
|
-
| 页面 PRD | {{
|
|
28
|
+
| 业务流程+实体状态机 | {{TPL_FLOW}} | 模块级契约 |
|
|
29
|
+
| 模块 PRD | {{TPL_MODULE_PRD}} | 模块级契约 |
|
|
30
|
+
| 页面 PRD | {{TPL_PAGE_PRD}} | 页面级契约 |
|
|
31
31
|
|
|
32
32
|
## 核心纪律:逐层确认制
|
|
33
33
|
|
|
@@ -20,7 +20,7 @@ tools: Read, Write, Edit, Glob, Grep, Bash
|
|
|
20
20
|
## 两段式验收
|
|
21
21
|
|
|
22
22
|
**第一段(developer 开工前后皆可):翻译验收标准**
|
|
23
|
-
读 approved 页面 PRD 的"验收要点"章节 → 翻译成可执行用例,写入 {{
|
|
23
|
+
读 approved 页面 PRD 的"验收要点"章节 → 翻译成可执行用例,写入 {{TPL_ACCEPTANCE}} → output 登记 → **submit 送审**(它是契约,developer 对着它写)。
|
|
24
24
|
遇到要点含混:**dispute 或退回 PM**,禁止自行脑补口径。
|
|
25
25
|
|
|
26
26
|
**第二段(developer 完成后):执行验收**
|