@dawipong/opcflow 0.6.0 → 0.8.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 +164 -78
- 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
|
@@ -735,6 +735,7 @@ __export(kind_exports, {
|
|
|
735
735
|
expandPattern: () => expandPattern,
|
|
736
736
|
getKindRegistry: () => getKindRegistry,
|
|
737
737
|
inferKind: () => inferKind,
|
|
738
|
+
kindPathTemplate: () => kindPathTemplate,
|
|
738
739
|
kindSpec: () => kindSpec,
|
|
739
740
|
normalizeModule: () => normalizeModule
|
|
740
741
|
});
|
|
@@ -766,6 +767,14 @@ function contractKinds(config) {
|
|
|
766
767
|
function expandPattern(pattern, config) {
|
|
767
768
|
return pattern.replace("{prd}", config.docs.prd).replace("{architecture}", config.docs.architecture).replace("{design}", config.docs.design).replace("{acceptance}", config.docs.acceptance);
|
|
768
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
|
+
}
|
|
769
778
|
function inferKind(relPath, config) {
|
|
770
779
|
const p = relPath.replace(/\\/g, "/");
|
|
771
780
|
const registry = getKindRegistry(config);
|
|
@@ -783,7 +792,7 @@ function normalizeModule(module, config) {
|
|
|
783
792
|
if (!module) return null;
|
|
784
793
|
return config.moduleMapping[module] ?? module;
|
|
785
794
|
}
|
|
786
|
-
var DEFAULT_KIND_REGISTRY, META_KINDS, PM_KINDS, KIND_TIERS;
|
|
795
|
+
var DEFAULT_KIND_REGISTRY, META_KINDS, PM_KINDS, KIND_TIERS, COORD_LABELS_ZH;
|
|
787
796
|
var init_kind = __esm({
|
|
788
797
|
"core/kind.ts"() {
|
|
789
798
|
"use strict";
|
|
@@ -805,7 +814,7 @@ var init_kind = __esm({
|
|
|
805
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" },
|
|
806
815
|
"design-system": { level: "endpoint", approval: "human", parents: ["baseline"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{design}/systems/"], coords: "{endpoint}" },
|
|
807
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}" },
|
|
808
|
-
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" },
|
|
809
818
|
acceptance: { level: "page", approval: "human", parents: ["page-prd", "prototype"], drivesStale: true, hashMode: "text-normalize", retrieval: "full", pathPatterns: ["{acceptance}/"], coords: "{endpoint}/{module}/{page}" },
|
|
810
819
|
code: { level: "module", approval: "machine", parents: ["baseline", "api-doc", "prototype"], drivesStale: false, hashMode: "directory", retrieval: "semantic" },
|
|
811
820
|
doc: { level: "module", approval: "none", parents: [], drivesStale: false, hashMode: "text-normalize", retrieval: "summary" }
|
|
@@ -824,6 +833,72 @@ var init_kind = __esm({
|
|
|
824
833
|
["prototype", "acceptance"],
|
|
825
834
|
["code"]
|
|
826
835
|
];
|
|
836
|
+
COORD_LABELS_ZH = { "{module}": "{\u6A21\u5757}", "{endpoint}": "{\u7AEF}", "{page}": "{\u9875\u9762}" };
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// core/roles.ts
|
|
841
|
+
function getRoleRegistry(config) {
|
|
842
|
+
const merged = {};
|
|
843
|
+
for (const [role, spec] of Object.entries(DEFAULT_ROLE_REGISTRY)) merged[role] = { ...spec };
|
|
844
|
+
for (const [role, kinds] of Object.entries(config.roleProduces ?? {})) {
|
|
845
|
+
merged[role] = { ...merged[role] ?? { produces: [] }, produces: kinds };
|
|
846
|
+
}
|
|
847
|
+
for (const [role, override] of Object.entries(config.roles ?? {})) {
|
|
848
|
+
merged[role] = { ...merged[role] ?? { produces: [] }, ...override };
|
|
849
|
+
}
|
|
850
|
+
return merged;
|
|
851
|
+
}
|
|
852
|
+
function ownerRoleOf(config, kind) {
|
|
853
|
+
for (const [role, spec] of Object.entries(getRoleRegistry(config))) {
|
|
854
|
+
if (spec.produces.includes(kind)) return role;
|
|
855
|
+
}
|
|
856
|
+
if (kind === "code") return "developer";
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
var DEFAULT_ROLE_REGISTRY;
|
|
860
|
+
var init_roles = __esm({
|
|
861
|
+
"core/roles.ts"() {
|
|
862
|
+
"use strict";
|
|
863
|
+
init_kind();
|
|
864
|
+
DEFAULT_ROLE_REGISTRY = {
|
|
865
|
+
"product-manager": {
|
|
866
|
+
produces: ["project", "roles", "glossary", "flow", "module-prd", "page-prd"]
|
|
867
|
+
},
|
|
868
|
+
architect: {
|
|
869
|
+
produces: ["db-doc", "api-doc"],
|
|
870
|
+
requires: [{ desc: "PM \u4EA7\u51FA(\u6A21\u5757/\u9875\u9762 PRD)", kinds: PM_KINDS }],
|
|
871
|
+
dispatch: [
|
|
872
|
+
{ at: "module", endpoint: "common", content: "\u8BBE\u8BA1 {module} \u6A21\u5757\u6570\u636E\u5E93" },
|
|
873
|
+
{ at: "module", endpoint: "service", content: "\u8BBE\u8BA1 {module} \u6A21\u5757 API \u6587\u6863" }
|
|
874
|
+
]
|
|
875
|
+
},
|
|
876
|
+
designer: {
|
|
877
|
+
produces: ["design-system", "design-prompt", "prototype"],
|
|
878
|
+
requires: [{ desc: "PM \u4EA7\u51FA(\u6A21\u5757/\u9875\u9762 PRD)", kinds: PM_KINDS }],
|
|
879
|
+
dispatch: [
|
|
880
|
+
{ at: "endpoint", ifMissingKind: "design-system", produces: ["design-system"], content: "\u5EFA\u7ACB {endpoint} \u7AEF\u8BBE\u8BA1\u7CFB\u7EDF(\u8BE5\u7AEF\u9996\u4E2A\u9875\u9762\u8BBE\u8BA1\u524D\u7F6E)" },
|
|
881
|
+
{ at: "page", produces: ["design-prompt", "prototype"], content: "\u8BBE\u8BA1 {endpoint}/{page} \u9875\u9762(\u63D0\u793A\u8BCD+\u539F\u578B)" }
|
|
882
|
+
]
|
|
883
|
+
},
|
|
884
|
+
developer: {
|
|
885
|
+
produces: ["code"],
|
|
886
|
+
requires: [
|
|
887
|
+
{ desc: "\u6570\u636E\u5E93\u6587\u6863", kinds: ["db-doc"] },
|
|
888
|
+
{ desc: "API \u6587\u6863", kinds: ["api-doc"], when: { endpointNot: "service" } },
|
|
889
|
+
{ desc: "designer {endpoint} \u8BBE\u8BA1\u7A3F", kinds: ["design-prompt", "prototype"], when: { endpointNot: "service" } }
|
|
890
|
+
],
|
|
891
|
+
dispatch: [
|
|
892
|
+
{ at: "module", endpoint: "service", content: "\u5B9E\u73B0 {module} \u6A21\u5757 service \u7AEF" },
|
|
893
|
+
{ at: "page", content: "\u5B9E\u73B0 {endpoint}/{page} \u9875\u9762" }
|
|
894
|
+
],
|
|
895
|
+
onQaFail: "rework"
|
|
896
|
+
},
|
|
897
|
+
qa: {
|
|
898
|
+
produces: ["acceptance"],
|
|
899
|
+
dispatch: [{ at: "page", type: "qa", content: "\u9A8C\u6536 {endpoint}/{page} \u9875\u9762" }]
|
|
900
|
+
}
|
|
901
|
+
};
|
|
827
902
|
}
|
|
828
903
|
});
|
|
829
904
|
|
|
@@ -964,31 +1039,22 @@ function levelFilter(level, task) {
|
|
|
964
1039
|
}
|
|
965
1040
|
}
|
|
966
1041
|
function producedKinds(ctx, task) {
|
|
967
|
-
const
|
|
968
|
-
if (
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
return
|
|
972
|
-
}
|
|
973
|
-
function existRequirements(task) {
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
return [{ desc: "\u6570\u636E\u5E93\u6587\u6863", level: "exist", kinds: ["db-doc"], filter: { module: m } }];
|
|
982
|
-
}
|
|
983
|
-
return [
|
|
984
|
-
{ desc: "\u6570\u636E\u5E93\u6587\u6863", level: "exist", kinds: ["db-doc"], filter: { module: m } },
|
|
985
|
-
{ desc: "API \u6587\u6863", level: "exist", kinds: ["api-doc"], filter: { module: m } },
|
|
986
|
-
{ desc: `designer ${task.endpoint} \u8BBE\u8BA1\u7A3F`, level: "exist", kinds: ["design-prompt", "prototype"], filter: { module: m, endpoint: task.endpoint } }
|
|
987
|
-
];
|
|
988
|
-
}
|
|
989
|
-
default:
|
|
990
|
-
return [];
|
|
1042
|
+
const spec = getRoleRegistry(ctx.config)[task.role];
|
|
1043
|
+
if (!spec) return [];
|
|
1044
|
+
const wantPage = !!task.page;
|
|
1045
|
+
const rule = (spec.dispatch ?? []).find((d) => (wantPage ? d.at === "page" : d.at !== "page") && d.produces);
|
|
1046
|
+
return rule?.produces ?? spec.produces;
|
|
1047
|
+
}
|
|
1048
|
+
function existRequirements(ctx, task) {
|
|
1049
|
+
const spec = getRoleRegistry(ctx.config)[task.role];
|
|
1050
|
+
const reqs = [];
|
|
1051
|
+
for (const r of spec?.requires ?? []) {
|
|
1052
|
+
if (r.when?.endpoint && task.endpoint !== r.when.endpoint) continue;
|
|
1053
|
+
if (r.when?.endpointNot && task.endpoint === r.when.endpointNot) continue;
|
|
1054
|
+
const filter = r.kinds.includes("design-prompt") || r.kinds.includes("prototype") ? { module: task.module, endpoint: task.endpoint } : { module: task.module };
|
|
1055
|
+
reqs.push({ desc: r.desc.replaceAll("{endpoint}", task.endpoint ?? ""), level: "exist", kinds: r.kinds, filter });
|
|
991
1056
|
}
|
|
1057
|
+
return reqs;
|
|
992
1058
|
}
|
|
993
1059
|
function approvedRequirements(ctx, task) {
|
|
994
1060
|
const registry = getKindRegistry(ctx.config);
|
|
@@ -1017,8 +1083,7 @@ function approvedRequirements(ctx, task) {
|
|
|
1017
1083
|
}
|
|
1018
1084
|
function claimRequirements(ctx, task) {
|
|
1019
1085
|
if (task.type !== "build" && task.type !== "qa") return [];
|
|
1020
|
-
|
|
1021
|
-
return [...existRequirements(task), ...approvedRequirements(ctx, task)];
|
|
1086
|
+
return [...existRequirements(ctx, task), ...approvedRequirements(ctx, task)];
|
|
1022
1087
|
}
|
|
1023
1088
|
function requirementSatisfied(ctx, req, matched) {
|
|
1024
1089
|
if (req.level === "exist") return matched.length > 0;
|
|
@@ -1144,6 +1209,7 @@ var init_gates = __esm({
|
|
|
1144
1209
|
init_derive();
|
|
1145
1210
|
init_kind();
|
|
1146
1211
|
init_lints();
|
|
1212
|
+
init_roles();
|
|
1147
1213
|
}
|
|
1148
1214
|
});
|
|
1149
1215
|
|
|
@@ -1790,11 +1856,7 @@ var init_artifact_commands = __esm({
|
|
|
1790
1856
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1791
1857
|
import { join as join7 } from "node:path";
|
|
1792
1858
|
function ownerRole(ctx, kind) {
|
|
1793
|
-
|
|
1794
|
-
if (kinds.includes(kind)) return role;
|
|
1795
|
-
}
|
|
1796
|
-
if (kind === "code") return "developer";
|
|
1797
|
-
return null;
|
|
1859
|
+
return ownerRoleOf(ctx.config, kind);
|
|
1798
1860
|
}
|
|
1799
1861
|
function openReviewTargetKeys(ctx, artifactId) {
|
|
1800
1862
|
const rows = ctx.db.prepare(
|
|
@@ -1938,15 +2000,20 @@ var init_sync_command = __esm({
|
|
|
1938
2000
|
init_events();
|
|
1939
2001
|
init_git();
|
|
1940
2002
|
init_kind();
|
|
2003
|
+
init_roles();
|
|
1941
2004
|
init_artifact_commands();
|
|
1942
2005
|
}
|
|
1943
2006
|
});
|
|
1944
2007
|
|
|
1945
2008
|
// core/commands/task.commands.ts
|
|
2009
|
+
function getRoleRegistryOwner(ctx, kind) {
|
|
2010
|
+
return ownerRoleOf(ctx.config, kind) ?? "qa";
|
|
2011
|
+
}
|
|
1946
2012
|
function createTask(ctx, p) {
|
|
1947
2013
|
const type = p.type ?? "build";
|
|
1948
2014
|
if (!VALID_TYPES.includes(type)) throw new Error(`\u65E0\u6548\u7684\u4EFB\u52A1\u7C7B\u578B: ${type},\u53EF\u9009\u503C: ${VALID_TYPES.join(", ")}`);
|
|
1949
|
-
|
|
2015
|
+
const validRoles = Object.keys(getRoleRegistry(ctx.config));
|
|
2016
|
+
if (!validRoles.includes(p.role)) throw new Error(`\u65E0\u6548\u7684\u89D2\u8272: ${p.role},\u53EF\u9009\u503C: ${validRoles.join(", ")}`);
|
|
1950
2017
|
const module = normalizeModule(p.module, ctx.config);
|
|
1951
2018
|
const tx = ctx.db.transaction(() => {
|
|
1952
2019
|
const result = ctx.db.prepare(
|
|
@@ -2041,11 +2108,12 @@ function updateTask(ctx, { id, status, operator, force = false }) {
|
|
|
2041
2108
|
return { id, warnings };
|
|
2042
2109
|
}
|
|
2043
2110
|
function spawnFollowupQa(ctx, reworkTask) {
|
|
2111
|
+
const qaRole = getRoleRegistryOwner(ctx, "acceptance");
|
|
2044
2112
|
const tx = ctx.db.transaction(() => {
|
|
2045
2113
|
const result = ctx.db.prepare(
|
|
2046
2114
|
`INSERT INTO tasks (module, role, endpoint, page, type, status, assignee, creator, content)
|
|
2047
|
-
VALUES (?,
|
|
2048
|
-
).run(reworkTask.module, reworkTask.endpoint, reworkTask.page, `[\u590D\u9A8C] rework #${reworkTask.id} \u5DF2\u5B8C\u6210,\u8BF7\u91CD\u65B0\u9A8C\u6536`);
|
|
2115
|
+
VALUES (?, ?, ?, ?, 'qa', 'pending', ?, 'system', ?)`
|
|
2116
|
+
).run(reworkTask.module, qaRole, reworkTask.endpoint, reworkTask.page, qaRole, `[\u590D\u9A8C] rework #${reworkTask.id} \u5DF2\u5B8C\u6210,\u8BF7\u91CD\u65B0\u9A8C\u6536`);
|
|
2049
2117
|
const qaId = result.lastInsertRowid;
|
|
2050
2118
|
logEvent(ctx.db, {
|
|
2051
2119
|
entityType: "task",
|
|
@@ -2205,7 +2273,7 @@ function getTaskDetail(ctx, id) {
|
|
|
2205
2273
|
}
|
|
2206
2274
|
return { task, events, outputs, staleness: taskStaleness(ctx.db, id) };
|
|
2207
2275
|
}
|
|
2208
|
-
var VALID_STATUS, VALID_TYPES
|
|
2276
|
+
var VALID_STATUS, VALID_TYPES;
|
|
2209
2277
|
var init_task_commands = __esm({
|
|
2210
2278
|
"core/commands/task.commands.ts"() {
|
|
2211
2279
|
"use strict";
|
|
@@ -2215,11 +2283,11 @@ var init_task_commands = __esm({
|
|
|
2215
2283
|
init_git();
|
|
2216
2284
|
init_gh();
|
|
2217
2285
|
init_kind();
|
|
2286
|
+
init_roles();
|
|
2218
2287
|
init_artifact_commands();
|
|
2219
2288
|
init_sync_command();
|
|
2220
2289
|
VALID_STATUS = ["pending", "in_progress", "completed", "cancelled"];
|
|
2221
2290
|
VALID_TYPES = ["build", "review", "qa", "hotfix", "baseline", "legacy", "rework"];
|
|
2222
|
-
VALID_ROLES = ["product-manager", "architect", "designer", "developer", "qa"];
|
|
2223
2291
|
}
|
|
2224
2292
|
});
|
|
2225
2293
|
|
|
@@ -2742,7 +2810,8 @@ var init_scan_command = __esm({
|
|
|
2742
2810
|
// core/commands/qa.command.ts
|
|
2743
2811
|
function recordQaResult(ctx, p) {
|
|
2744
2812
|
const task = getTaskRow(ctx, p.id);
|
|
2745
|
-
|
|
2813
|
+
const acceptanceRole = ownerRoleOf(ctx.config, "acceptance") ?? "qa";
|
|
2814
|
+
if (task.role !== acceptanceRole) throw new Error(`\u4EFB\u52A1 #${p.id} \u4E0D\u662F ${acceptanceRole} \u4EFB\u52A1`);
|
|
2746
2815
|
if (task.assignee !== p.operator) throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u8BB0\u5F55\u9A8C\u6536\u7ED3\u679C`);
|
|
2747
2816
|
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`);
|
|
2748
2817
|
const eventName = p.result === "pass" ? "qa_passed" : "qa_failed";
|
|
@@ -2770,11 +2839,12 @@ function recordQaResult(ctx, p) {
|
|
|
2770
2839
|
}
|
|
2771
2840
|
return { reworkTaskId: null };
|
|
2772
2841
|
}
|
|
2842
|
+
const reworkRole = ownerRoleOf(ctx.config, "code") ?? "developer";
|
|
2773
2843
|
const tx2 = ctx.db.transaction(() => {
|
|
2774
2844
|
const result = ctx.db.prepare(
|
|
2775
2845
|
`INSERT INTO tasks (module, role, endpoint, page, type, status, assignee, creator, content)
|
|
2776
|
-
VALUES (?,
|
|
2777
|
-
).run(task.module, task.endpoint, task.page, `[\u8FD4\u5DE5] QA #${task.id} \u9A8C\u6536\u4E0D\u901A\u8FC7:${p.reason}`);
|
|
2846
|
+
VALUES (?, ?, ?, ?, 'rework', 'pending', ?, 'system', ?)`
|
|
2847
|
+
).run(task.module, reworkRole, task.endpoint, task.page, reworkRole, `[\u8FD4\u5DE5] QA #${task.id} \u9A8C\u6536\u4E0D\u901A\u8FC7:${p.reason}`);
|
|
2778
2848
|
const reworkId = result.lastInsertRowid;
|
|
2779
2849
|
logEvent(ctx.db, {
|
|
2780
2850
|
entityType: "task",
|
|
@@ -2794,6 +2864,7 @@ var init_qa_command = __esm({
|
|
|
2794
2864
|
"core/commands/qa.command.ts"() {
|
|
2795
2865
|
"use strict";
|
|
2796
2866
|
init_events();
|
|
2867
|
+
init_roles();
|
|
2797
2868
|
init_artifact_commands();
|
|
2798
2869
|
init_task_commands();
|
|
2799
2870
|
}
|
|
@@ -3009,35 +3080,25 @@ function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
|
3009
3080
|
}
|
|
3010
3081
|
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page && existsSync10(join12(ctx.root, a.path)));
|
|
3011
3082
|
const endpoints = [...new Set(pagePrds.map((a) => a.endpoint))];
|
|
3012
|
-
const
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3083
|
+
const registry = getRoleRegistry(ctx.config);
|
|
3084
|
+
const interp = (tpl, v) => tpl.replaceAll("{module}", v.module).replaceAll("{endpoint}", v.endpoint ?? "").replaceAll("{page}", v.page ?? "");
|
|
3085
|
+
const desired = [];
|
|
3086
|
+
for (const role of ctx.config.pipeline) {
|
|
3087
|
+
for (const d of registry[role]?.dispatch ?? []) {
|
|
3088
|
+
if (d.at === "module") {
|
|
3089
|
+
desired.push({ role, endpoint: d.endpoint ?? null, page: null, type: d.type ?? "build", assignee: role, content: interp(d.content, { module }) });
|
|
3090
|
+
} else if (d.at === "endpoint") {
|
|
3091
|
+
for (const endpoint of endpoints) {
|
|
3092
|
+
if (d.ifMissingKind && artifacts.some((a) => a.kind === d.ifMissingKind && a.endpoint === endpoint)) continue;
|
|
3093
|
+
desired.push({ role, endpoint, page: null, type: d.type ?? "build", assignee: role, content: interp(d.content, { module, endpoint }) });
|
|
3094
|
+
}
|
|
3095
|
+
} else {
|
|
3096
|
+
for (const prd of pagePrds) {
|
|
3097
|
+
desired.push({ role, endpoint: prd.endpoint, page: prd.page, type: d.type ?? "build", assignee: role, content: interp(d.content, { module, endpoint: prd.endpoint, page: prd.page }) });
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3028
3100
|
}
|
|
3029
3101
|
}
|
|
3030
|
-
for (const prd of pagePrds) {
|
|
3031
|
-
desired.push(
|
|
3032
|
-
{ 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)` },
|
|
3033
|
-
{ role: "developer", endpoint: prd.endpoint, page: prd.page, type: "build", assignee: "developer", content: `\u5B9E\u73B0 ${prd.endpoint}/${prd.page} \u9875\u9762` },
|
|
3034
|
-
{ role: "qa", endpoint: prd.endpoint, page: prd.page, type: "qa", assignee: "qa", content: `\u9A8C\u6536 ${prd.endpoint}/${prd.page} \u9875\u9762` }
|
|
3035
|
-
);
|
|
3036
|
-
}
|
|
3037
|
-
const activeRoles = new Set(ctx.config.pipeline);
|
|
3038
|
-
const filtered = desired.filter((d) => activeRoles.has(d.role));
|
|
3039
|
-
desired.length = 0;
|
|
3040
|
-
desired.push(...filtered);
|
|
3041
3102
|
const exists = ctx.db.prepare(
|
|
3042
3103
|
`SELECT COUNT(*) AS c FROM tasks
|
|
3043
3104
|
WHERE module IS ? AND role = ? AND endpoint IS ? AND page IS ? AND type = ? AND status != 'cancelled'`
|
|
@@ -3082,6 +3143,7 @@ var init_plan_command = __esm({
|
|
|
3082
3143
|
init_derive();
|
|
3083
3144
|
init_events();
|
|
3084
3145
|
init_kind();
|
|
3146
|
+
init_roles();
|
|
3085
3147
|
init_task_commands();
|
|
3086
3148
|
}
|
|
3087
3149
|
});
|
|
@@ -3132,7 +3194,7 @@ var init_install_hooks_command = __esm({
|
|
|
3132
3194
|
});
|
|
3133
3195
|
|
|
3134
3196
|
// core/commands/gen-agents.command.ts
|
|
3135
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6,
|
|
3197
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3136
3198
|
import { dirname as dirname3, join as join14 } from "node:path";
|
|
3137
3199
|
function expandPath(ctx, kind) {
|
|
3138
3200
|
const spec = getKindRegistry(ctx.config)[kind];
|
|
@@ -3178,6 +3240,16 @@ function genAgents(ctx, templatesDir) {
|
|
|
3178
3240
|
PATH_DESIGN_PROMPTS: expandPath(ctx, "design-prompt"),
|
|
3179
3241
|
PATH_PROTOTYPES: expandPath(ctx, "prototype"),
|
|
3180
3242
|
PATH_ACCEPTANCE: expandPath(ctx, "acceptance"),
|
|
3243
|
+
// 路径投影:完整路径模板由 kind 注册表(前缀+coords 文法+ext)推导——
|
|
3244
|
+
// 项目覆盖 coords 后重跑 gen-agents,agent 指示与 scan 解析自动同步(单一真相源)
|
|
3245
|
+
TPL_FLOW: kindPathTemplate(ctx.config, "flow", lang) ?? "",
|
|
3246
|
+
TPL_MODULE_PRD: kindPathTemplate(ctx.config, "module-prd", lang) ?? "",
|
|
3247
|
+
TPL_PAGE_PRD: kindPathTemplate(ctx.config, "page-prd", lang) ?? "",
|
|
3248
|
+
TPL_DB_DOC: kindPathTemplate(ctx.config, "db-doc", lang) ?? "",
|
|
3249
|
+
TPL_DESIGN_SYSTEM: kindPathTemplate(ctx.config, "design-system", lang) ?? "",
|
|
3250
|
+
TPL_DESIGN_PROMPT: kindPathTemplate(ctx.config, "design-prompt", lang) ?? "",
|
|
3251
|
+
TPL_PROTOTYPE: kindPathTemplate(ctx.config, "prototype", lang) ?? "",
|
|
3252
|
+
TPL_ACCEPTANCE: kindPathTemplate(ctx.config, "acceptance", lang) ?? "",
|
|
3181
3253
|
ENDPOINTS: ctx.config.endpoints.join(" / "),
|
|
3182
3254
|
PIPELINE: ctx.config.pipeline.join(" \u2192 "),
|
|
3183
3255
|
CODE_ROOTS: ctx.config.endpoints.map((e) => `| ${e} | ${(ctx.config.codeRoots[e] ?? []).join(blocks.sep) || blocks.todo} |`).join("\n")
|
|
@@ -3191,13 +3263,19 @@ function genAgents(ctx, templatesDir) {
|
|
|
3191
3263
|
return content;
|
|
3192
3264
|
};
|
|
3193
3265
|
const adapters = resolvePlatforms(ctx.config.platforms);
|
|
3194
|
-
const
|
|
3266
|
+
const registry = getRoleRegistry(ctx.config);
|
|
3195
3267
|
const written = [];
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3268
|
+
const projectTplDir = join14(ctx.root, "docs/workbench/templates/agents", lang);
|
|
3269
|
+
for (const role of [...new Set(ctx.config.pipeline)]) {
|
|
3270
|
+
if (!registry[role]) {
|
|
3271
|
+
throw new Error(`pipeline \u89D2\u8272 ${role} \u672A\u5728\u89D2\u8272\u6CE8\u518C\u8868\u4E2D\u5B9A\u4E49(\u5185\u7F6E\u6216 config.roles)`);
|
|
3272
|
+
}
|
|
3273
|
+
const candidates = [join14(projectTplDir, `${role}.md`), join14(dir, `${role}.md`)];
|
|
3274
|
+
const file = candidates.find((f) => existsSync12(f));
|
|
3275
|
+
if (!file) {
|
|
3276
|
+
throw new Error(`\u89D2\u8272 ${role} \u7F3A\u5C11 agent \u6A21\u677F:\u81EA\u5B9A\u4E49\u89D2\u8272\u8BF7\u63D0\u4F9B docs/workbench/templates/agents/${lang}/${role}.md`);
|
|
3277
|
+
}
|
|
3278
|
+
const tpl = parseTemplate(readFileSync6(file, "utf-8"));
|
|
3201
3279
|
for (const adapter of adapters) {
|
|
3202
3280
|
const memory = adapter.memoryBlock(role, lang);
|
|
3203
3281
|
const spec = {
|
|
@@ -3222,6 +3300,7 @@ var init_gen_agents_command = __esm({
|
|
|
3222
3300
|
"use strict";
|
|
3223
3301
|
init_config();
|
|
3224
3302
|
init_kind();
|
|
3303
|
+
init_roles();
|
|
3225
3304
|
init_platforms();
|
|
3226
3305
|
TRUST_PROTOCOL_ZH = `## \u4FE1\u4EFB\u534F\u8BAE(\u6700\u9AD8\u4F18\u5148\u7EA7)
|
|
3227
3306
|
|
|
@@ -3345,11 +3424,13 @@ function intakeIssues(ctx) {
|
|
|
3345
3424
|
continue;
|
|
3346
3425
|
}
|
|
3347
3426
|
const isBug = issue.labels.some((l) => l.name.toLowerCase() === "bug");
|
|
3427
|
+
const bugRole = ctx.config.intake?.bugRole ?? "developer";
|
|
3428
|
+
const defaultRole = ctx.config.intake?.defaultRole ?? "product-manager";
|
|
3348
3429
|
const id = createTask(ctx, {
|
|
3349
|
-
role: isBug ?
|
|
3350
|
-
endpoint:
|
|
3430
|
+
role: isBug ? bugRole : defaultRole,
|
|
3431
|
+
endpoint: null,
|
|
3351
3432
|
type: isBug ? "hotfix" : "build",
|
|
3352
|
-
assignee: isBug ?
|
|
3433
|
+
assignee: isBug ? bugRole : defaultRole,
|
|
3353
3434
|
creator: "intake",
|
|
3354
3435
|
content: `[gh#${issue.number}] ${issue.title}
|
|
3355
3436
|
${issue.url}`,
|
|
@@ -3533,6 +3614,7 @@ __export(core_exports, {
|
|
|
3533
3614
|
CONFIG_FILENAME: () => CONFIG_FILENAME,
|
|
3534
3615
|
CURRENT_SCHEMA_VERSION: () => CURRENT_SCHEMA_VERSION,
|
|
3535
3616
|
DEFAULT_KIND_REGISTRY: () => DEFAULT_KIND_REGISTRY,
|
|
3617
|
+
DEFAULT_ROLE_REGISTRY: () => DEFAULT_ROLE_REGISTRY,
|
|
3536
3618
|
KIND_TIERS: () => KIND_TIERS,
|
|
3537
3619
|
META_KINDS: () => META_KINDS,
|
|
3538
3620
|
MIGRATIONS: () => MIGRATIONS,
|
|
@@ -3566,6 +3648,7 @@ __export(core_exports, {
|
|
|
3566
3648
|
genAgents: () => genAgents,
|
|
3567
3649
|
getAdapter: () => getAdapter,
|
|
3568
3650
|
getKindRegistry: () => getKindRegistry,
|
|
3651
|
+
getRoleRegistry: () => getRoleRegistry,
|
|
3569
3652
|
getTaskDetail: () => getTaskDetail,
|
|
3570
3653
|
getTaskRow: () => getTaskRow,
|
|
3571
3654
|
gitHead: () => gitHead,
|
|
@@ -3578,6 +3661,7 @@ __export(core_exports, {
|
|
|
3578
3661
|
initProject: () => initProject,
|
|
3579
3662
|
installGitHooks: () => installGitHooks,
|
|
3580
3663
|
intakeIssues: () => intakeIssues,
|
|
3664
|
+
kindPathTemplate: () => kindPathTemplate,
|
|
3581
3665
|
kindSpec: () => kindSpec,
|
|
3582
3666
|
listArtifacts: () => listArtifacts,
|
|
3583
3667
|
listEvents: () => listEvents,
|
|
@@ -3593,6 +3677,7 @@ __export(core_exports, {
|
|
|
3593
3677
|
openWorkbench: () => openWorkbench,
|
|
3594
3678
|
openWorkbenchAt: () => openWorkbenchAt,
|
|
3595
3679
|
ownerRole: () => ownerRole,
|
|
3680
|
+
ownerRoleOf: () => ownerRoleOf,
|
|
3596
3681
|
planModule: () => planModule,
|
|
3597
3682
|
prototypeEndorsed: () => prototypeEndorsed,
|
|
3598
3683
|
recordNote: () => recordNote,
|
|
@@ -3630,6 +3715,7 @@ var init_core = __esm({
|
|
|
3630
3715
|
init_hash();
|
|
3631
3716
|
init_events();
|
|
3632
3717
|
init_kind();
|
|
3718
|
+
init_roles();
|
|
3633
3719
|
init_platforms();
|
|
3634
3720
|
init_derive();
|
|
3635
3721
|
init_lints();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dawipong/opcflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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 完成后):执行验收**
|