@dawipong/opcflow 0.5.0 → 0.6.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 +149 -100
- package/package.json +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
|
],
|
|
@@ -992,20 +1027,27 @@ function requirementSatisfied(ctx, req, matched) {
|
|
|
992
1027
|
(a) => kindSpec(ctx.config, a.kind).approval === "thumbs" ? prototypeEndorsed(ctx.db, a) : reviewStatus(a) === "approved"
|
|
993
1028
|
);
|
|
994
1029
|
}
|
|
995
|
-
function
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const dev = ctx.db.prepare(
|
|
1030
|
+
function siblingRoleBlocking(ctx, task) {
|
|
1031
|
+
for (const rule of ctx.config.taskPreconditions ?? []) {
|
|
1032
|
+
if (rule.role !== task.role) continue;
|
|
1033
|
+
if (rule.type && rule.type !== task.type) continue;
|
|
1034
|
+
const done = ctx.db.prepare(
|
|
1001
1035
|
`SELECT COUNT(*) AS c FROM tasks
|
|
1002
|
-
WHERE role =
|
|
1036
|
+
WHERE role = ? AND status = 'completed' AND module IS ? AND endpoint IS ?
|
|
1003
1037
|
AND (? IS NULL OR page IS ?)`
|
|
1004
|
-
).get(task.module, task.endpoint, task.page, task.page);
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1038
|
+
).get(rule.requiresSiblingRoleCompleted, task.module, task.endpoint, task.page, task.page);
|
|
1039
|
+
if (done.c === 0) {
|
|
1040
|
+
return `\u5BF9\u5E94\u7684 ${rule.requiresSiblingRoleCompleted} ${task.endpoint ?? ""} \u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210,\u65E0\u6CD5\u9886\u53D6 ${task.role} \u4EFB\u52A1\u3002`;
|
|
1007
1041
|
}
|
|
1008
1042
|
}
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
function validateClaim(ctx, task) {
|
|
1046
|
+
const warnings = [];
|
|
1047
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
1048
|
+
const mode = ctx.config.gates.approvalMode;
|
|
1049
|
+
const blockMsg = siblingRoleBlocking(ctx, task);
|
|
1050
|
+
if (blockMsg) throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${blockMsg}`);
|
|
1009
1051
|
if (task.module && (task.type === "build" || task.type === "hotfix") && !moduleCleared(ctx.db, task.module)) {
|
|
1010
1052
|
warnings.push(
|
|
1011
1053
|
`[\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 +1110,18 @@ function validateComplete(ctx, task, status, opts = {}) {
|
|
|
1068
1110
|
warnings.push(msg);
|
|
1069
1111
|
}
|
|
1070
1112
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
|
|
1113
|
+
const violations = runProtocolLints(ctx, { role: task.role, endpoint: task.endpoint });
|
|
1114
|
+
if (violations.length > 0) {
|
|
1115
|
+
const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
|
|
1075
1116
|
${v.text}`).join("\n");
|
|
1076
|
-
|
|
1077
|
-
|
|
1117
|
+
throw new Error(
|
|
1118
|
+
`[\u534F\u8BAE lint \u5931\u8D25] ${violations.length} \u5904\u8FDD\u4F8B,\u4FEE\u590D\u540E\u518D\u5B8C\u6210:
|
|
1078
1119
|
${detail}${violations.length > 10 ? `
|
|
1079
1120
|
...\u7B49 ${violations.length} \u5904` : ""}`
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1121
|
+
);
|
|
1082
1122
|
}
|
|
1083
|
-
|
|
1123
|
+
const producesMachineKind = producedKinds(ctx, task).some((k) => kindSpec(ctx.config, k).approval === "machine");
|
|
1124
|
+
if (producesMachineKind && task.endpoint && ctx.config.machineChecks.enabled) {
|
|
1084
1125
|
const cmds = ctx.config.machineChecks[task.endpoint];
|
|
1085
1126
|
if (Array.isArray(cmds)) {
|
|
1086
1127
|
for (const cmd of cmds) {
|
|
@@ -1125,7 +1166,7 @@ function taskBlocked(ctx, task) {
|
|
|
1125
1166
|
validateClaim(ctx, task);
|
|
1126
1167
|
return false;
|
|
1127
1168
|
} catch (err) {
|
|
1128
|
-
return err instanceof Error && err.message.includes("\
|
|
1169
|
+
return err instanceof Error && err.message.includes("[\u524D\u7F6E\u6761\u4EF6]");
|
|
1129
1170
|
}
|
|
1130
1171
|
}
|
|
1131
1172
|
function coordHealth(ctx, artifacts, tasks) {
|
|
@@ -1206,6 +1247,7 @@ function buildTree(ctx, opts = {}) {
|
|
|
1206
1247
|
eTasks.filter((t) => !t.page)
|
|
1207
1248
|
);
|
|
1208
1249
|
for (const child of pageNodes) health2 = worse(health2, child.health);
|
|
1250
|
+
if (coordFailed(ctx, module, endpoint, null)) health2 = "failed";
|
|
1209
1251
|
return {
|
|
1210
1252
|
key: `${module}/${endpoint}`,
|
|
1211
1253
|
title: endpoint,
|
|
@@ -1754,16 +1796,16 @@ function ownerRole(ctx, kind) {
|
|
|
1754
1796
|
if (kind === "code") return "developer";
|
|
1755
1797
|
return null;
|
|
1756
1798
|
}
|
|
1757
|
-
function
|
|
1758
|
-
const
|
|
1759
|
-
`SELECT
|
|
1799
|
+
function openReviewTargetKeys(ctx, artifactId) {
|
|
1800
|
+
const rows = ctx.db.prepare(
|
|
1801
|
+
`SELECT t.role, t.endpoint, t.module FROM tasks t
|
|
1760
1802
|
JOIN task_inputs ti ON ti.task_id = t.id
|
|
1761
1803
|
WHERE t.type = 'review' AND t.status IN ('pending', 'in_progress') AND ti.artifact_id = ?`
|
|
1762
|
-
).
|
|
1763
|
-
return
|
|
1804
|
+
).all(artifactId);
|
|
1805
|
+
return new Set(rows.map((r) => `${r.role}|${r.endpoint ?? ""}|${r.module ?? ""}`));
|
|
1764
1806
|
}
|
|
1765
1807
|
function spawnReviews(ctx, source, reason) {
|
|
1766
|
-
|
|
1808
|
+
const openKeys = openReviewTargetKeys(ctx, source.id);
|
|
1767
1809
|
const registry = getKindRegistry(ctx.config);
|
|
1768
1810
|
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
1811
|
const targets = /* @__PURE__ */ new Map();
|
|
@@ -1772,6 +1814,7 @@ function spawnReviews(ctx, source, reason) {
|
|
|
1772
1814
|
const role = ownerRole(ctx, child.kind);
|
|
1773
1815
|
if (!role) continue;
|
|
1774
1816
|
const key = `${role}|${child.endpoint ?? ""}|${child.module ?? source.module ?? ""}`;
|
|
1817
|
+
if (openKeys.has(key)) continue;
|
|
1775
1818
|
targets.set(key, { role, endpoint: child.endpoint, module: child.module ?? source.module });
|
|
1776
1819
|
}
|
|
1777
1820
|
let spawned = 0;
|
|
@@ -1969,6 +2012,7 @@ function updateTask(ctx, { id, status, operator, force = false }) {
|
|
|
1969
2012
|
throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u66F4\u65B0\u4EFB\u52A1\u72B6\u6001`);
|
|
1970
2013
|
}
|
|
1971
2014
|
const { warnings } = validateComplete(ctx, task, status, { force });
|
|
2015
|
+
let followupQaId = null;
|
|
1972
2016
|
const tx = ctx.db.transaction(() => {
|
|
1973
2017
|
ctx.db.prepare("UPDATE tasks SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, id);
|
|
1974
2018
|
logEvent(ctx.db, {
|
|
@@ -1981,15 +2025,15 @@ function updateTask(ctx, { id, status, operator, force = false }) {
|
|
|
1981
2025
|
endpoint: task.endpoint,
|
|
1982
2026
|
page: task.page
|
|
1983
2027
|
});
|
|
2028
|
+
if (task.type === "rework" && status === "completed") {
|
|
2029
|
+
followupQaId = spawnFollowupQa(ctx, task);
|
|
2030
|
+
}
|
|
1984
2031
|
});
|
|
1985
2032
|
tx();
|
|
2033
|
+
if (followupQaId !== null) warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${followupQaId}`);
|
|
1986
2034
|
if (task.type === "hotfix" && status === "completed") {
|
|
1987
2035
|
warnings.push(...checkContractTouch(ctx, task, operator));
|
|
1988
2036
|
}
|
|
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
2037
|
if (status === "completed" && task.external_ref?.startsWith("gh#")) {
|
|
1994
2038
|
const closed = closeLinkedIssue(ctx, task.external_ref, task.id);
|
|
1995
2039
|
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 +2982,8 @@ var init_retro_command = __esm({
|
|
|
2938
2982
|
});
|
|
2939
2983
|
|
|
2940
2984
|
// core/commands/plan.command.ts
|
|
2985
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2986
|
+
import { join as join12 } from "node:path";
|
|
2941
2987
|
function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
2942
2988
|
const module = normalizeModule(moduleRaw, ctx.config);
|
|
2943
2989
|
const summary = { created: [], skipped: 0, cancelled: 0, warnings: [] };
|
|
@@ -2961,7 +3007,7 @@ function planModule(ctx, moduleRaw, creator = "product-manager") {
|
|
|
2961
3007
|
if (ctx.config.gates.approvalMode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
|
|
2962
3008
|
summary.warnings.push(msg);
|
|
2963
3009
|
}
|
|
2964
|
-
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page);
|
|
3010
|
+
const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page && existsSync10(join12(ctx.root, a.path)));
|
|
2965
3011
|
const endpoints = [...new Set(pagePrds.map((a) => a.endpoint))];
|
|
2966
3012
|
const desired = [
|
|
2967
3013
|
{ role: "architect", endpoint: "common", page: null, type: "build", assignee: "architect", content: `\u8BBE\u8BA1 ${module} \u6A21\u5757\u6570\u636E\u5E93` },
|
|
@@ -3041,11 +3087,11 @@ var init_plan_command = __esm({
|
|
|
3041
3087
|
});
|
|
3042
3088
|
|
|
3043
3089
|
// core/commands/install-hooks.command.ts
|
|
3044
|
-
import { chmodSync, existsSync as
|
|
3045
|
-
import { join as
|
|
3090
|
+
import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3091
|
+
import { join as join13 } from "node:path";
|
|
3046
3092
|
function installGitHooks(ctx) {
|
|
3047
|
-
const hooksDir =
|
|
3048
|
-
if (!
|
|
3093
|
+
const hooksDir = join13(ctx.root, ".git", "hooks");
|
|
3094
|
+
if (!existsSync11(join13(ctx.root, ".git"))) throw new Error("\u4E0D\u662F git \u4ED3\u5E93");
|
|
3049
3095
|
mkdirSync5(hooksDir, { recursive: true });
|
|
3050
3096
|
const installed = [];
|
|
3051
3097
|
const postCommit = `#!/bin/sh
|
|
@@ -3053,9 +3099,9 @@ function installGitHooks(ctx) {
|
|
|
3053
3099
|
${WORKBENCH_BIN} postcommit >/dev/null 2>&1 &
|
|
3054
3100
|
exit 0
|
|
3055
3101
|
`;
|
|
3056
|
-
writeFileSync4(
|
|
3102
|
+
writeFileSync4(join13(hooksDir, "post-commit"), postCommit);
|
|
3057
3103
|
try {
|
|
3058
|
-
chmodSync(
|
|
3104
|
+
chmodSync(join13(hooksDir, "post-commit"), 493);
|
|
3059
3105
|
} catch {
|
|
3060
3106
|
}
|
|
3061
3107
|
installed.push("post-commit(sync \u5BF9\u8D26)");
|
|
@@ -3069,9 +3115,9 @@ if [ -n "$WORKBENCH_TASK_ID" ] && [ "$2" != "merge" ]; then
|
|
|
3069
3115
|
fi
|
|
3070
3116
|
exit 0
|
|
3071
3117
|
`;
|
|
3072
|
-
writeFileSync4(
|
|
3118
|
+
writeFileSync4(join13(hooksDir, "prepare-commit-msg"), prepare);
|
|
3073
3119
|
try {
|
|
3074
|
-
chmodSync(
|
|
3120
|
+
chmodSync(join13(hooksDir, "prepare-commit-msg"), 493);
|
|
3075
3121
|
} catch {
|
|
3076
3122
|
}
|
|
3077
3123
|
installed.push(`prepare-commit-msg(${ctx.config.git.trailerKey} trailer \u6CE8\u5165)`);
|
|
@@ -3086,8 +3132,8 @@ var init_install_hooks_command = __esm({
|
|
|
3086
3132
|
});
|
|
3087
3133
|
|
|
3088
3134
|
// core/commands/gen-agents.command.ts
|
|
3089
|
-
import { existsSync as
|
|
3090
|
-
import { dirname as dirname3, join as
|
|
3135
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3136
|
+
import { dirname as dirname3, join as join14 } from "node:path";
|
|
3091
3137
|
function expandPath(ctx, kind) {
|
|
3092
3138
|
const spec = getKindRegistry(ctx.config)[kind];
|
|
3093
3139
|
const raw = spec?.pathPatterns?.[0] ?? "";
|
|
@@ -3113,8 +3159,8 @@ function parseTemplate(rawInput) {
|
|
|
3113
3159
|
function genAgents(ctx, templatesDir) {
|
|
3114
3160
|
const lang = ctx.config.language;
|
|
3115
3161
|
const blocks = SHARED[lang] ?? SHARED.zh;
|
|
3116
|
-
const dir = templatesDir ??
|
|
3117
|
-
if (!
|
|
3162
|
+
const dir = templatesDir ?? join14(WORKBENCH_DIR, "templates/agents", lang);
|
|
3163
|
+
if (!existsSync12(dir)) throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${dir}`);
|
|
3118
3164
|
const cli = ctx.config.cli;
|
|
3119
3165
|
const baseTokens = {
|
|
3120
3166
|
CLI: cli,
|
|
@@ -3151,7 +3197,7 @@ function genAgents(ctx, templatesDir) {
|
|
|
3151
3197
|
if (!file.endsWith(".md")) continue;
|
|
3152
3198
|
const role = file.replace(/\.md$/, "");
|
|
3153
3199
|
if (!pipeline.has(role)) continue;
|
|
3154
|
-
const tpl = parseTemplate(readFileSync6(
|
|
3200
|
+
const tpl = parseTemplate(readFileSync6(join14(dir, file), "utf-8"));
|
|
3155
3201
|
for (const adapter of adapters) {
|
|
3156
3202
|
const memory = adapter.memoryBlock(role, lang);
|
|
3157
3203
|
const spec = {
|
|
@@ -3162,7 +3208,7 @@ function genAgents(ctx, templatesDir) {
|
|
|
3162
3208
|
body: renderTokens(tpl.body, memory)
|
|
3163
3209
|
};
|
|
3164
3210
|
const rel = `${adapter.agentsDir}/${adapter.agentFile(role)}`;
|
|
3165
|
-
const abs =
|
|
3211
|
+
const abs = join14(ctx.root, rel);
|
|
3166
3212
|
mkdirSync6(dirname3(abs), { recursive: true });
|
|
3167
3213
|
writeFileSync5(abs, adapter.renderAgent(spec));
|
|
3168
3214
|
written.push(rel);
|
|
@@ -3246,8 +3292,8 @@ When MCP is registered, prefer the \`wb_*\` typed tools (same source & transacti
|
|
|
3246
3292
|
});
|
|
3247
3293
|
|
|
3248
3294
|
// core/commands/audit.command.ts
|
|
3249
|
-
import { existsSync as
|
|
3250
|
-
import { join as
|
|
3295
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
3296
|
+
import { join as join15 } from "node:path";
|
|
3251
3297
|
function auditModule(ctx, moduleRaw) {
|
|
3252
3298
|
const module = normalizeModule(moduleRaw, ctx.config);
|
|
3253
3299
|
const kinds = contractKinds(ctx.config);
|
|
@@ -3257,9 +3303,9 @@ function auditModule(ctx, moduleRaw) {
|
|
|
3257
3303
|
path: a.path,
|
|
3258
3304
|
status: reviewStatus(a),
|
|
3259
3305
|
everApproved: everApproved(a),
|
|
3260
|
-
onDisk:
|
|
3306
|
+
onDisk: existsSync13(join15(ctx.root, a.path))
|
|
3261
3307
|
}));
|
|
3262
|
-
const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk:
|
|
3308
|
+
const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk: existsSync13(join15(ctx.root, a.path)) }));
|
|
3263
3309
|
return {
|
|
3264
3310
|
module,
|
|
3265
3311
|
cleared: moduleCleared(ctx.db, module),
|
|
@@ -3323,28 +3369,28 @@ var init_intake_command = __esm({
|
|
|
3323
3369
|
// core/commands/init.command.ts
|
|
3324
3370
|
import {
|
|
3325
3371
|
copyFileSync,
|
|
3326
|
-
existsSync as
|
|
3372
|
+
existsSync as existsSync14,
|
|
3327
3373
|
mkdirSync as mkdirSync7,
|
|
3328
3374
|
readFileSync as readFileSync7,
|
|
3329
3375
|
readdirSync as readdirSync6,
|
|
3330
3376
|
writeFileSync as writeFileSync6
|
|
3331
3377
|
} from "node:fs";
|
|
3332
|
-
import { dirname as dirname4, join as
|
|
3378
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
3333
3379
|
function deployPreset(root) {
|
|
3334
3380
|
const deployed = [];
|
|
3335
|
-
if (!
|
|
3381
|
+
if (!existsSync14(PRESET_DIR)) return deployed;
|
|
3336
3382
|
const walk = (rel) => {
|
|
3337
|
-
for (const ent of readdirSync6(
|
|
3383
|
+
for (const ent of readdirSync6(join16(PRESET_DIR, rel), {
|
|
3338
3384
|
withFileTypes: true
|
|
3339
3385
|
})) {
|
|
3340
|
-
const childRel = rel ?
|
|
3386
|
+
const childRel = rel ? join16(rel, ent.name) : ent.name;
|
|
3341
3387
|
if (ent.isDirectory()) {
|
|
3342
3388
|
walk(childRel);
|
|
3343
3389
|
} else if (ent.isFile()) {
|
|
3344
|
-
const dest =
|
|
3345
|
-
if (
|
|
3390
|
+
const dest = join16(root, childRel);
|
|
3391
|
+
if (existsSync14(dest)) continue;
|
|
3346
3392
|
mkdirSync7(dirname4(dest), { recursive: true });
|
|
3347
|
-
copyFileSync(
|
|
3393
|
+
copyFileSync(join16(PRESET_DIR, childRel), dest);
|
|
3348
3394
|
deployed.push(childRel);
|
|
3349
3395
|
}
|
|
3350
3396
|
}
|
|
@@ -3353,8 +3399,8 @@ function deployPreset(root) {
|
|
|
3353
3399
|
return deployed;
|
|
3354
3400
|
}
|
|
3355
3401
|
function ensureRootTsx(root) {
|
|
3356
|
-
const pkgPath =
|
|
3357
|
-
if (!
|
|
3402
|
+
const pkgPath = join16(root, "package.json");
|
|
3403
|
+
if (!existsSync14(pkgPath)) return false;
|
|
3358
3404
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
3359
3405
|
if (pkg.devDependencies?.tsx) return false;
|
|
3360
3406
|
pkg.devDependencies = { ...pkg.devDependencies ?? {}, tsx: TSX_VERSION };
|
|
@@ -3362,8 +3408,8 @@ function ensureRootTsx(root) {
|
|
|
3362
3408
|
return true;
|
|
3363
3409
|
}
|
|
3364
3410
|
function initProject(root, opts) {
|
|
3365
|
-
const configPath =
|
|
3366
|
-
if (
|
|
3411
|
+
const configPath = join16(root, CONFIG_FILENAME);
|
|
3412
|
+
if (existsSync14(configPath)) {
|
|
3367
3413
|
throw new Error(
|
|
3368
3414
|
`${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
3415
|
);
|
|
@@ -3397,10 +3443,10 @@ function initProject(root, opts) {
|
|
|
3397
3443
|
const scaffolded = [];
|
|
3398
3444
|
if (opts.scaffold !== false) {
|
|
3399
3445
|
for (const dir of DOC_DIRS) {
|
|
3400
|
-
const abs =
|
|
3401
|
-
if (!
|
|
3446
|
+
const abs = join16(root, dir);
|
|
3447
|
+
if (!existsSync14(abs)) {
|
|
3402
3448
|
mkdirSync7(abs, { recursive: true });
|
|
3403
|
-
writeFileSync6(
|
|
3449
|
+
writeFileSync6(join16(abs, ".gitkeep"), "");
|
|
3404
3450
|
scaffolded.push(dir);
|
|
3405
3451
|
}
|
|
3406
3452
|
}
|
|
@@ -3432,7 +3478,7 @@ function initProject(root, opts) {
|
|
|
3432
3478
|
}
|
|
3433
3479
|
const notes = adapters.flatMap((a) => a.notes);
|
|
3434
3480
|
let hooks = [];
|
|
3435
|
-
if (opts.gitHooks !== false &&
|
|
3481
|
+
if (opts.gitHooks !== false && existsSync14(join16(root, ".git"))) {
|
|
3436
3482
|
try {
|
|
3437
3483
|
hooks = installGitHooks(ctx);
|
|
3438
3484
|
} catch {
|
|
@@ -3474,7 +3520,7 @@ var init_init_command = __esm({
|
|
|
3474
3520
|
"docs/design/prototypes",
|
|
3475
3521
|
"docs/acceptance"
|
|
3476
3522
|
];
|
|
3477
|
-
PRESET_DIR =
|
|
3523
|
+
PRESET_DIR = join16(WORKBENCH_DIR, "preset");
|
|
3478
3524
|
TSX_VERSION = "^4";
|
|
3479
3525
|
}
|
|
3480
3526
|
});
|
|
@@ -3762,17 +3808,17 @@ __export(app_exports, {
|
|
|
3762
3808
|
import fastifyCors from "@fastify/cors";
|
|
3763
3809
|
import fastifyStatic from "@fastify/static";
|
|
3764
3810
|
import Fastify from "fastify";
|
|
3765
|
-
import { existsSync as
|
|
3766
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3811
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
|
|
3812
|
+
import { isAbsolute as isAbsolute2, join as join17, normalize, relative as relative2 } from "node:path";
|
|
3767
3813
|
function getArtifact(ctx, id) {
|
|
3768
3814
|
const row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(id);
|
|
3769
3815
|
if (!row) throw Object.assign(new Error(`artifact #${id} \u4E0D\u5B58\u5728`), { statusCode: 404 });
|
|
3770
3816
|
return row;
|
|
3771
3817
|
}
|
|
3772
3818
|
function guardedAbsPath(ctx, artifact, rel) {
|
|
3773
|
-
const base = normalize(
|
|
3819
|
+
const base = normalize(join17(ctx.root, artifact.path));
|
|
3774
3820
|
if (!rel) return base;
|
|
3775
|
-
const target = normalize(
|
|
3821
|
+
const target = normalize(join17(base, rel));
|
|
3776
3822
|
const r = relative2(base, target);
|
|
3777
3823
|
if (r === "" || r === "." || r.startsWith("..") || isAbsolute2(r)) {
|
|
3778
3824
|
throw Object.assign(new Error("\u8DEF\u5F84\u8D8A\u754C"), { statusCode: 403 });
|
|
@@ -3782,11 +3828,11 @@ function guardedAbsPath(ctx, artifact, rel) {
|
|
|
3782
3828
|
async function createServer(ctx) {
|
|
3783
3829
|
const app = Fastify({ logger: false });
|
|
3784
3830
|
await app.register(fastifyCors, { origin: true });
|
|
3785
|
-
const webDist =
|
|
3786
|
-
if (
|
|
3831
|
+
const webDist = join17(WORKBENCH_DIR, "web/dist");
|
|
3832
|
+
if (existsSync15(webDist)) {
|
|
3787
3833
|
await app.register(fastifyStatic, { root: webDist });
|
|
3788
3834
|
}
|
|
3789
|
-
const protoRoot =
|
|
3835
|
+
const protoRoot = join17(ctx.root, ctx.config.docs.design, "prototypes");
|
|
3790
3836
|
mkdirSync8(protoRoot, { recursive: true });
|
|
3791
3837
|
await app.register(fastifyStatic, { root: protoRoot, prefix: "/proto/", decorateReply: false });
|
|
3792
3838
|
app.get("/api/meta", async () => ({
|
|
@@ -3821,7 +3867,7 @@ async function createServer(ctx) {
|
|
|
3821
3867
|
let content = null;
|
|
3822
3868
|
let isDirectory = false;
|
|
3823
3869
|
let missing = false;
|
|
3824
|
-
if (!
|
|
3870
|
+
if (!existsSync15(abs)) {
|
|
3825
3871
|
missing = true;
|
|
3826
3872
|
} else if (statSync6(abs).isDirectory()) {
|
|
3827
3873
|
isDirectory = true;
|
|
@@ -3837,20 +3883,20 @@ async function createServer(ctx) {
|
|
|
3837
3883
|
const walk = (dir, rel) => {
|
|
3838
3884
|
for (const name of readdirSync7(dir).sort()) {
|
|
3839
3885
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
3840
|
-
const full =
|
|
3886
|
+
const full = join17(dir, name);
|
|
3841
3887
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
3842
3888
|
const st = statSync6(full);
|
|
3843
3889
|
if (st.isDirectory()) walk(full, relPath);
|
|
3844
3890
|
else files.push({ rel: relPath, size: st.size });
|
|
3845
3891
|
}
|
|
3846
3892
|
};
|
|
3847
|
-
if (
|
|
3893
|
+
if (existsSync15(base) && statSync6(base).isDirectory()) walk(base, "");
|
|
3848
3894
|
return { files };
|
|
3849
3895
|
});
|
|
3850
3896
|
app.get("/api/artifact/:id/file", async (req) => {
|
|
3851
3897
|
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
3852
3898
|
const abs = guardedAbsPath(ctx, artifact, req.query.rel);
|
|
3853
|
-
if (!
|
|
3899
|
+
if (!existsSync15(abs) || statSync6(abs).isDirectory()) {
|
|
3854
3900
|
throw Object.assign(new Error("\u6587\u4EF6\u4E0D\u5B58\u5728"), { statusCode: 404 });
|
|
3855
3901
|
}
|
|
3856
3902
|
if (statSync6(abs).size > MAX_INLINE_SIZE) {
|
|
@@ -3905,7 +3951,7 @@ data: {"cursor":${cursor2}}
|
|
|
3905
3951
|
endpoint: a.endpoint,
|
|
3906
3952
|
page: a.page,
|
|
3907
3953
|
review_status: reviewStatus(a),
|
|
3908
|
-
missing: !
|
|
3954
|
+
missing: !existsSync15(join17(ctx.root, a.path))
|
|
3909
3955
|
}));
|
|
3910
3956
|
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
3957
|
return { nodes, edges };
|
|
@@ -3922,7 +3968,7 @@ data: {"cursor":${cursor2}}
|
|
|
3922
3968
|
for (const name of readdirSync7(dir).sort()) {
|
|
3923
3969
|
if (files.length >= 30) return;
|
|
3924
3970
|
if (name.startsWith(".") || name === "node_modules" || name === "dist" || name === "build") continue;
|
|
3925
|
-
const full =
|
|
3971
|
+
const full = join17(dir, name);
|
|
3926
3972
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
3927
3973
|
if (statSync6(full).isDirectory()) walk(full, relPath);
|
|
3928
3974
|
else if (relPath.toLowerCase().includes(q) && !registered.has(relPath)) files.push(relPath);
|
|
@@ -4022,7 +4068,7 @@ data: {"cursor":${cursor2}}
|
|
|
4022
4068
|
app.get("/api/artifact/:id/diff", async (req) => {
|
|
4023
4069
|
const artifact = getArtifact(ctx, parseInt(req.params.id));
|
|
4024
4070
|
const abs = guardedAbsPath(ctx, artifact);
|
|
4025
|
-
const current =
|
|
4071
|
+
const current = existsSync15(abs) && !statSync6(abs).isDirectory() ? readFileSync8(abs, "utf-8") : null;
|
|
4026
4072
|
return { approved: approvedContent(ctx, artifact.id), current };
|
|
4027
4073
|
});
|
|
4028
4074
|
app.post(
|
|
@@ -4088,15 +4134,17 @@ function hookPlatform() {
|
|
|
4088
4134
|
const arg = process.argv.find((a) => a.startsWith("--platform="));
|
|
4089
4135
|
return arg ? arg.slice("--platform=".length) : "claude";
|
|
4090
4136
|
}
|
|
4091
|
-
function extractFilePath(input) {
|
|
4137
|
+
function extractFilePath(input, adapter) {
|
|
4138
|
+
const fromAdapter = adapter?.parseHookInput(input).filePath;
|
|
4139
|
+
if (fromAdapter) return fromAdapter;
|
|
4092
4140
|
return input?.tool_input?.file_path ?? // claude PreToolUse/PostToolUse
|
|
4093
4141
|
input?.tool_input?.filePath ?? input?.file_path ?? // cursor afterFileEdit / 通用
|
|
4094
4142
|
input?.filePath ?? input?.args?.file_path ?? // opencode tool 参数
|
|
4095
4143
|
input?.args?.filePath ?? input?.arguments?.file_path ?? // codex 兜底
|
|
4096
4144
|
input?.arguments?.filePath ?? input?.input?.file_path ?? void 0;
|
|
4097
4145
|
}
|
|
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;
|
|
4146
|
+
function hookProjectDir(adapter) {
|
|
4147
|
+
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
4148
|
}
|
|
4101
4149
|
async function readStdinJson() {
|
|
4102
4150
|
const chunks = [];
|
|
@@ -4120,14 +4168,16 @@ __export(hook_pretooluse_exports, {
|
|
|
4120
4168
|
});
|
|
4121
4169
|
async function writeGateHook(platform) {
|
|
4122
4170
|
const input = await readStdinJson();
|
|
4123
|
-
const
|
|
4171
|
+
const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
4172
|
+
const adapter = PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
|
|
4173
|
+
const filePath = extractFilePath(input, adapter);
|
|
4124
4174
|
if (!filePath) return;
|
|
4125
4175
|
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
4126
4176
|
const { normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
4127
4177
|
const { reviewStatus: reviewStatus2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
|
|
4128
4178
|
const { contractKinds: contractKinds2 } = await Promise.resolve().then(() => (init_kind(), kind_exports));
|
|
4129
4179
|
const { logEvent: logEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
4130
|
-
const ctx = openWorkbench2(hookProjectDir());
|
|
4180
|
+
const ctx = openWorkbench2(hookProjectDir(adapter));
|
|
4131
4181
|
const mode = ctx.config.gates.writeGate;
|
|
4132
4182
|
if (mode === "off") return;
|
|
4133
4183
|
const rel = normalizeRelPath2(ctx, filePath);
|
|
@@ -4154,12 +4204,10 @@ async function writeGateHook(platform) {
|
|
|
4154
4204
|
const msg = `\u8BE5\u6587\u4EF6\u662F\u5DF2\u5BA1\u6279\u5951\u7EA6(${artifact.kind}): ${rel}\u3002
|
|
4155
4205
|
\u4FEE\u6539\u524D\u8BF7\u5148\u9886\u53D6\u5BF9\u5E94\u4EFB\u52A1:${ctx.config.cli} list --module=${artifact.module ?? ""} --status=pending
|
|
4156
4206
|
\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);
|
|
4207
|
+
const resp = adapter?.respondBlocked(msg) ?? { exitCode: 2, stderr: msg };
|
|
4208
|
+
if (resp.stdout) process.stdout.write(resp.stdout);
|
|
4209
|
+
if (resp.stderr) console.error(resp.stderr);
|
|
4210
|
+
process.exit(resp.exitCode);
|
|
4163
4211
|
}
|
|
4164
4212
|
}
|
|
4165
4213
|
var init_hook_pretooluse = __esm({
|
|
@@ -4178,13 +4226,15 @@ var hook_refresh_exports = {};
|
|
|
4178
4226
|
__export(hook_refresh_exports, {
|
|
4179
4227
|
refreshHook: () => refreshHook
|
|
4180
4228
|
});
|
|
4181
|
-
async function refreshHook(
|
|
4229
|
+
async function refreshHook(platform) {
|
|
4182
4230
|
const input = await readStdinJson();
|
|
4183
|
-
const
|
|
4231
|
+
const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
|
|
4232
|
+
const adapter = platform && PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
|
|
4233
|
+
const filePath = extractFilePath(input, adapter);
|
|
4184
4234
|
if (!filePath) return;
|
|
4185
4235
|
const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
4186
4236
|
const { refreshArtifact: refreshArtifact2, normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
|
|
4187
|
-
const ctx = openWorkbench2(hookProjectDir());
|
|
4237
|
+
const ctx = openWorkbench2(hookProjectDir(adapter));
|
|
4188
4238
|
const rel = normalizeRelPath2(ctx, filePath);
|
|
4189
4239
|
const exact = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(rel);
|
|
4190
4240
|
if (exact) {
|
|
@@ -4284,7 +4334,6 @@ function printTaskDetail(ctx, id) {
|
|
|
4284
4334
|
}
|
|
4285
4335
|
console.log();
|
|
4286
4336
|
}
|
|
4287
|
-
var ALL_PLATFORMS = ["claude", "codex", "opencode", "cursor"];
|
|
4288
4337
|
var PRIORITY_PROVIDERS = [
|
|
4289
4338
|
"anthropic",
|
|
4290
4339
|
"openai",
|
|
@@ -4330,7 +4379,7 @@ async function promptInit() {
|
|
|
4330
4379
|
const zh = language === "zh";
|
|
4331
4380
|
const platform = await select({
|
|
4332
4381
|
message: zh ? "\u5E73\u53F0" : "Platform",
|
|
4333
|
-
choices:
|
|
4382
|
+
choices: PLATFORM_IDS.map((p) => ({ name: p, value: p })),
|
|
4334
4383
|
default: "claude"
|
|
4335
4384
|
});
|
|
4336
4385
|
const platforms = [platform];
|
|
@@ -4358,14 +4407,14 @@ async function promptInit() {
|
|
|
4358
4407
|
choices: prov.models.map((m) => ({ name: m.name, value: m.id })),
|
|
4359
4408
|
pageSize: 12
|
|
4360
4409
|
});
|
|
4361
|
-
if (modelId) modelObj[p] = p
|
|
4410
|
+
if (modelId) modelObj[p] = getAdapter(p).formatModel(provId, modelId);
|
|
4362
4411
|
}
|
|
4363
4412
|
return { platforms, endpoints, model: Object.keys(modelObj).length ? modelObj : void 0, language };
|
|
4364
4413
|
}
|
|
4365
4414
|
async function runInit(root, a) {
|
|
4366
|
-
const { existsSync:
|
|
4367
|
-
const { join:
|
|
4368
|
-
if (
|
|
4415
|
+
const { existsSync: existsSync16 } = await import("node:fs");
|
|
4416
|
+
const { join: join18 } = await import("node:path");
|
|
4417
|
+
if (existsSync16(join18(root, "workbench.config.json"))) {
|
|
4369
4418
|
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
4419
|
process.exit(1);
|
|
4371
4420
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dawipong/opcflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|