@devcxl/opencode-cabbage 0.3.2 → 1.0.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/README.md +34 -19
- package/README.zh.md +34 -19
- package/assets/agents/dev-lifecycle.md +60 -49
- package/assets/agents/team/architect.md +5 -1
- package/assets/agents/team/developer.md +89 -0
- package/assets/agents/team/goal-verify.md +5 -1
- package/assets/agents/team/reviewer.md +6 -0
- package/assets/commands/code.md +3 -2
- package/assets/commands/design.md +3 -2
- package/assets/commands/release.md +3 -2
- package/assets/commands/requirements.md +3 -2
- package/assets/commands/review.md +3 -2
- package/assets/commands/setup.md +3 -2
- package/assets/commands/tasks.md +2 -1
- package/assets/prompts/bootstrap.md +9 -8
- package/assets/prompts/stage-contract.md +55 -0
- package/assets/skills/flow-code/SKILL.md +38 -140
- package/assets/skills/flow-design/SKILL.md +28 -79
- package/assets/skills/flow-release/SKILL.md +31 -51
- package/assets/skills/flow-requirements/SKILL.md +31 -196
- package/assets/skills/flow-review/SKILL.md +26 -123
- package/assets/skills/flow-setup/SKILL.md +40 -43
- package/assets/skills/flow-tasks/SKILL.md +23 -153
- package/assets/skills/flow-tdd/SKILL.md +30 -31
- package/dist/kernel/caller.d.ts +48 -0
- package/dist/kernel/caller.js +88 -0
- package/dist/kernel/caller.js.map +1 -0
- package/dist/kernel/context.d.ts +27 -0
- package/dist/kernel/context.js +73 -0
- package/dist/kernel/context.js.map +1 -0
- package/dist/kernel/flow.d.ts +166 -0
- package/dist/kernel/flow.js +387 -0
- package/dist/kernel/flow.js.map +1 -0
- package/dist/kernel/legacy.d.ts +19 -0
- package/dist/kernel/legacy.js +34 -0
- package/dist/kernel/legacy.js.map +1 -0
- package/dist/kernel/mutex.d.ts +8 -0
- package/dist/kernel/mutex.js +23 -0
- package/dist/kernel/mutex.js.map +1 -0
- package/dist/kernel/permission.d.ts +21 -0
- package/dist/kernel/permission.js +37 -0
- package/dist/kernel/permission.js.map +1 -0
- package/dist/kernel/profile.d.ts +35 -0
- package/dist/kernel/profile.js +144 -0
- package/dist/kernel/profile.js.map +1 -0
- package/dist/kernel/records.d.ts +87 -0
- package/dist/kernel/records.js +197 -0
- package/dist/kernel/records.js.map +1 -0
- package/dist/kernel/release.d.ts +104 -0
- package/dist/kernel/release.js +229 -0
- package/dist/kernel/release.js.map +1 -0
- package/dist/kernel/review.d.ts +34 -0
- package/dist/kernel/review.js +81 -0
- package/dist/kernel/review.js.map +1 -0
- package/dist/kernel/session-index.d.ts +43 -0
- package/dist/kernel/session-index.js +120 -0
- package/dist/kernel/session-index.js.map +1 -0
- package/dist/kernel/setup.d.ts +70 -0
- package/dist/kernel/setup.js +324 -0
- package/dist/kernel/setup.js.map +1 -0
- package/dist/kernel/slug.d.ts +23 -0
- package/dist/kernel/slug.js +67 -0
- package/dist/kernel/slug.js.map +1 -0
- package/dist/kernel/task.d.ts +208 -0
- package/dist/kernel/task.js +788 -0
- package/dist/kernel/task.js.map +1 -0
- package/dist/{flowrun → kernel/tdd}/adapter.d.ts +1 -1
- package/dist/kernel/tdd/adapter.js.map +1 -0
- package/dist/{flowrun → kernel/tdd}/digest.d.ts +1 -1
- package/dist/kernel/tdd/digest.js.map +1 -0
- package/dist/{flowrun → kernel/tdd}/evaluator.d.ts +1 -1
- package/dist/kernel/tdd/evaluator.js.map +1 -0
- package/dist/kernel/tdd/evidence.d.ts +42 -0
- package/dist/kernel/tdd/evidence.js +48 -0
- package/dist/kernel/tdd/evidence.js.map +1 -0
- package/dist/{flowrun → kernel/tdd}/state.d.ts +1 -1
- package/dist/kernel/tdd/state.js.map +1 -0
- package/dist/kernel/types.d.ts +180 -0
- package/dist/kernel/types.js +2 -0
- package/dist/kernel/types.js.map +1 -0
- package/dist/kernel/worktree.d.ts +45 -0
- package/dist/kernel/worktree.js +127 -0
- package/dist/kernel/worktree.js.map +1 -0
- package/dist/plugin/agents.d.ts +5 -17
- package/dist/plugin/agents.js +0 -23
- package/dist/plugin/agents.js.map +1 -1
- package/dist/plugin/flow-control.d.ts +80 -0
- package/dist/plugin/flow-control.js +254 -0
- package/dist/plugin/flow-control.js.map +1 -0
- package/dist/plugin/goal.d.ts +7 -46
- package/dist/plugin/goal.js +32 -116
- package/dist/plugin/goal.js.map +1 -1
- package/dist/plugin/prompt-lint.js +78 -5
- package/dist/plugin/prompt-lint.js.map +1 -1
- package/dist/plugin/release-control.d.ts +37 -0
- package/dist/plugin/release-control.js +304 -0
- package/dist/plugin/release-control.js.map +1 -0
- package/dist/plugin/server.d.ts +19 -0
- package/dist/plugin/server.js +147 -302
- package/dist/plugin/server.js.map +1 -1
- package/dist/plugin/setup-control.d.ts +45 -0
- package/dist/plugin/setup-control.js +80 -0
- package/dist/plugin/setup-control.js.map +1 -0
- package/dist/plugin/shell.d.ts +7 -34
- package/dist/plugin/shell.js +12 -137
- package/dist/plugin/shell.js.map +1 -1
- package/dist/plugin/skills.d.ts +1 -1
- package/dist/plugin/skills.js +1 -7
- package/dist/plugin/skills.js.map +1 -1
- package/dist/plugin/task-control.d.ts +90 -0
- package/dist/plugin/task-control.js +201 -0
- package/dist/plugin/task-control.js.map +1 -0
- package/dist/plugin/tdd-checkpoint.d.ts +118 -0
- package/dist/plugin/tdd-checkpoint.js +581 -0
- package/dist/plugin/tdd-checkpoint.js.map +1 -0
- package/dist/util/paths.d.ts +0 -2
- package/dist/util/paths.js +1 -7
- package/dist/util/paths.js.map +1 -1
- package/package.json +1 -1
- package/assets/agents/team/backend.md +0 -100
- package/assets/agents/team/frontend.md +0 -98
- package/assets/commands/handoff.md +0 -4
- package/assets/commands/test.md +0 -4
- package/assets/context/CONTEXT.md +0 -41
- package/assets/skills/flow-handoff/SKILL.md +0 -76
- package/assets/skills/flow-test/SKILL.md +0 -75
- package/dist/flowrun/adapter.js.map +0 -1
- package/dist/flowrun/audit.d.ts +0 -5
- package/dist/flowrun/audit.js +0 -67
- package/dist/flowrun/audit.js.map +0 -1
- package/dist/flowrun/ci.d.ts +0 -64
- package/dist/flowrun/ci.js +0 -184
- package/dist/flowrun/ci.js.map +0 -1
- package/dist/flowrun/coverage.d.ts +0 -70
- package/dist/flowrun/coverage.js +0 -207
- package/dist/flowrun/coverage.js.map +0 -1
- package/dist/flowrun/digest.js.map +0 -1
- package/dist/flowrun/evaluator.js.map +0 -1
- package/dist/flowrun/gate.d.ts +0 -28
- package/dist/flowrun/gate.js +0 -139
- package/dist/flowrun/gate.js.map +0 -1
- package/dist/flowrun/github.d.ts +0 -24
- package/dist/flowrun/github.js +0 -154
- package/dist/flowrun/github.js.map +0 -1
- package/dist/flowrun/index.d.ts +0 -14
- package/dist/flowrun/index.js +0 -15
- package/dist/flowrun/index.js.map +0 -1
- package/dist/flowrun/merge.d.ts +0 -57
- package/dist/flowrun/merge.js +0 -180
- package/dist/flowrun/merge.js.map +0 -1
- package/dist/flowrun/migration.d.ts +0 -11
- package/dist/flowrun/migration.js +0 -276
- package/dist/flowrun/migration.js.map +0 -1
- package/dist/flowrun/resilience.d.ts +0 -18
- package/dist/flowrun/resilience.js +0 -80
- package/dist/flowrun/resilience.js.map +0 -1
- package/dist/flowrun/state.js.map +0 -1
- package/dist/flowrun/transitions.d.ts +0 -66
- package/dist/flowrun/transitions.js +0 -168
- package/dist/flowrun/transitions.js.map +0 -1
- package/dist/flowrun/types.d.ts +0 -356
- package/dist/flowrun/types.js +0 -21
- package/dist/flowrun/types.js.map +0 -1
- package/dist/flowrun/validator.d.ts +0 -19
- package/dist/flowrun/validator.js +0 -209
- package/dist/flowrun/validator.js.map +0 -1
- package/dist/plugin/broker.d.ts +0 -73
- package/dist/plugin/broker.js +0 -140
- package/dist/plugin/broker.js.map +0 -1
- package/dist/plugin/tdd-tool.d.ts +0 -62
- package/dist/plugin/tdd-tool.js +0 -336
- package/dist/plugin/tdd-tool.js.map +0 -1
- /package/dist/{flowrun → kernel/tdd}/adapter.js +0 -0
- /package/dist/{flowrun → kernel/tdd}/digest.js +0 -0
- /package/dist/{flowrun → kernel/tdd}/evaluator.js +0 -0
- /package/dist/{flowrun → kernel/tdd}/state.js +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按 key 隔离的互斥锁,用于单进程内串行化并发操作。
|
|
3
|
+
* 相同 key 的操作依次执行,不同 key 的操作可并行。
|
|
4
|
+
*/
|
|
5
|
+
export class KeyedMutex {
|
|
6
|
+
locks = new Map();
|
|
7
|
+
async runExclusive(key, fn) {
|
|
8
|
+
const prev = this.locks.get(key) ?? Promise.resolve();
|
|
9
|
+
let release;
|
|
10
|
+
const next = new Promise(r => {
|
|
11
|
+
release = r;
|
|
12
|
+
});
|
|
13
|
+
this.locks.set(key, next);
|
|
14
|
+
await prev;
|
|
15
|
+
try {
|
|
16
|
+
return await fn();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
release();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=mutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/kernel/mutex.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,UAAU;IACb,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAA;IAEzD,KAAK,CAAC,YAAY,CAAI,GAAoB,EAAE,EAAoB;QAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;QACrD,IAAI,OAAmB,CAAA;QACvB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE;YACjC,OAAO,GAAG,CAAC,CAAA;QACb,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAEzB,MAAM,IAAI,CAAA;QACV,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;gBAAS,CAAC;YACT,OAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode permission 匹配纯函数(spec §7.2)。
|
|
3
|
+
*
|
|
4
|
+
* 语义与 OpenCode 当前实现对齐:
|
|
5
|
+
* - bash 模式规则:pattern 支持 `*` 通配(`*` 兜底、`cmd*` 前缀匹配)
|
|
6
|
+
* - 最后匹配优先:后声明的规则覆盖先声明的同命中规则
|
|
7
|
+
* - auto 模式:仅 allow 放行,deny/ask 均拒绝
|
|
8
|
+
*/
|
|
9
|
+
export type PermissionAction = "allow" | "deny" | "ask";
|
|
10
|
+
/** pattern 是否命中 command:`*` 全匹配,`前缀*` 前缀匹配,否则精确匹配 */
|
|
11
|
+
export declare function permissionPatternMatches(pattern: string, command: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* 按规则集解析 command 的最终动作。
|
|
14
|
+
* 按声明顺序遍历,命中即记录,后命中覆盖先前 → 最后匹配优先。
|
|
15
|
+
* 无任何命中返回 "ask"(默认询问,auto 模式下等效拒绝)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function matchPermission(rules: Record<string, string>, command: string): PermissionAction;
|
|
18
|
+
/**
|
|
19
|
+
* auto 模式门禁:仅 allow 放行;deny 与 ask(无命中)均拒绝。
|
|
20
|
+
*/
|
|
21
|
+
export declare function isAllowedInAutoMode(rules: Record<string, string>, command: string): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode permission 匹配纯函数(spec §7.2)。
|
|
3
|
+
*
|
|
4
|
+
* 语义与 OpenCode 当前实现对齐:
|
|
5
|
+
* - bash 模式规则:pattern 支持 `*` 通配(`*` 兜底、`cmd*` 前缀匹配)
|
|
6
|
+
* - 最后匹配优先:后声明的规则覆盖先声明的同命中规则
|
|
7
|
+
* - auto 模式:仅 allow 放行,deny/ask 均拒绝
|
|
8
|
+
*/
|
|
9
|
+
/** pattern 是否命中 command:`*` 全匹配,`前缀*` 前缀匹配,否则精确匹配 */
|
|
10
|
+
export function permissionPatternMatches(pattern, command) {
|
|
11
|
+
if (pattern === "*")
|
|
12
|
+
return true;
|
|
13
|
+
if (pattern.endsWith("*"))
|
|
14
|
+
return command.startsWith(pattern.slice(0, -1));
|
|
15
|
+
return command === pattern;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 按规则集解析 command 的最终动作。
|
|
19
|
+
* 按声明顺序遍历,命中即记录,后命中覆盖先前 → 最后匹配优先。
|
|
20
|
+
* 无任何命中返回 "ask"(默认询问,auto 模式下等效拒绝)。
|
|
21
|
+
*/
|
|
22
|
+
export function matchPermission(rules, command) {
|
|
23
|
+
let action = "ask";
|
|
24
|
+
for (const [pattern, raw] of Object.entries(rules)) {
|
|
25
|
+
if (!permissionPatternMatches(pattern, command))
|
|
26
|
+
continue;
|
|
27
|
+
action = raw === "allow" ? "allow" : raw === "deny" ? "deny" : "ask";
|
|
28
|
+
}
|
|
29
|
+
return action;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* auto 模式门禁:仅 allow 放行;deny 与 ask(无命中)均拒绝。
|
|
33
|
+
*/
|
|
34
|
+
export function isAllowedInAutoMode(rules, command) {
|
|
35
|
+
return matchPermission(rules, command) === "allow";
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.js","sourceRoot":"","sources":["../../src/kernel/permission.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,qDAAqD;AACrD,MAAM,UAAU,wBAAwB,CAAC,OAAe,EAAE,OAAe;IACvE,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1E,OAAO,OAAO,KAAK,OAAO,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAA6B,EAAE,OAAe;IAC5E,IAAI,MAAM,GAAqB,KAAK,CAAA;IACpC,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC;YAAE,SAAQ;QACzD,MAAM,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAA;IACtE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA6B,EAAE,OAAe;IAChF,OAAO,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;AACpD,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** AGENTS.md 中 Project Profile 区块的固定标题 */
|
|
2
|
+
export declare const PROFILE_SECTION_TITLE = "## Project Profile";
|
|
3
|
+
export type TddDefaultMode = "strict" | "relaxed" | "bypass";
|
|
4
|
+
/** 解析后的结构化项目 Profile(只含可机检字段) */
|
|
5
|
+
export interface ProjectProfile {
|
|
6
|
+
testCommand: string | null;
|
|
7
|
+
regressionCommand: string | null;
|
|
8
|
+
testFilePatterns: string[];
|
|
9
|
+
implementationFilePatterns: string[];
|
|
10
|
+
tddDefaultMode: TddDefaultMode | null;
|
|
11
|
+
versionBumpRule: string | null;
|
|
12
|
+
versionFile: string | null;
|
|
13
|
+
tagFormat: string | null;
|
|
14
|
+
releaseWorkflowPath: string | null;
|
|
15
|
+
riskPatterns: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface ProfileParseResult {
|
|
18
|
+
profile: ProjectProfile;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
}
|
|
21
|
+
/** 无 Profile 时的空值:所有可选字段为 null、列表字段为空数组 */
|
|
22
|
+
export declare function emptyProfile(): ProjectProfile;
|
|
23
|
+
/**
|
|
24
|
+
* 解析 markdown 中的 `## Project Profile` 区块。
|
|
25
|
+
* 定位标题到下一个 `##` 标题之间的条目行,按白名单提取;
|
|
26
|
+
* 未知 key 记 warning(不阻断),重复 key 取最后(文档语义)。
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseProjectProfile(markdown: string): ProfileParseResult;
|
|
29
|
+
/** 从项目根目录读取 AGENTS.md 并解析;文件缺失或区块缺失返回空 Profile,不抛错 */
|
|
30
|
+
export declare function readProjectProfile(projectDir: string): Promise<ProjectProfile>;
|
|
31
|
+
/**
|
|
32
|
+
* 确认回写辅助:将 profileBlock(完整 `## Project Profile` 区块)写入 markdown。
|
|
33
|
+
* 已存在区块则整块替换,否则追加到末尾。
|
|
34
|
+
*/
|
|
35
|
+
export declare function upsertProjectProfile(markdown: string, profileBlock: string): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** AGENTS.md 中 Project Profile 区块的固定标题 */
|
|
4
|
+
export const PROFILE_SECTION_TITLE = "## Project Profile";
|
|
5
|
+
/** 无 Profile 时的空值:所有可选字段为 null、列表字段为空数组 */
|
|
6
|
+
export function emptyProfile() {
|
|
7
|
+
return {
|
|
8
|
+
testCommand: null,
|
|
9
|
+
regressionCommand: null,
|
|
10
|
+
testFilePatterns: [],
|
|
11
|
+
implementationFilePatterns: [],
|
|
12
|
+
tddDefaultMode: null,
|
|
13
|
+
versionBumpRule: null,
|
|
14
|
+
versionFile: null,
|
|
15
|
+
tagFormat: null,
|
|
16
|
+
releaseWorkflowPath: null,
|
|
17
|
+
riskPatterns: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** 条目行:`- key: value`,key 仅含小写字母/数字/空格/下划线/连字符 */
|
|
21
|
+
const KEY_VALUE_PATTERN = /^- ([a-z0-9 _-]+):\s*(.+)$/;
|
|
22
|
+
const VALID_TDD_MODES = new Set(["strict", "relaxed", "bypass"]);
|
|
23
|
+
/**
|
|
24
|
+
* 解析 markdown 中的 `## Project Profile` 区块。
|
|
25
|
+
* 定位标题到下一个 `##` 标题之间的条目行,按白名单提取;
|
|
26
|
+
* 未知 key 记 warning(不阻断),重复 key 取最后(文档语义)。
|
|
27
|
+
*/
|
|
28
|
+
export function parseProjectProfile(markdown) {
|
|
29
|
+
const profile = emptyProfile();
|
|
30
|
+
const warnings = [];
|
|
31
|
+
const section = findProfileSection(markdown);
|
|
32
|
+
if (!section) {
|
|
33
|
+
return { profile, warnings };
|
|
34
|
+
}
|
|
35
|
+
const lines = markdown.split("\n");
|
|
36
|
+
for (let i = section.start + 1; i < section.end; i++) {
|
|
37
|
+
const match = KEY_VALUE_PATTERN.exec(lines[i]);
|
|
38
|
+
if (!match)
|
|
39
|
+
continue;
|
|
40
|
+
applyKey(profile, warnings, match[1], cleanValue(match[2]));
|
|
41
|
+
}
|
|
42
|
+
return { profile, warnings };
|
|
43
|
+
}
|
|
44
|
+
/** 从项目根目录读取 AGENTS.md 并解析;文件缺失或区块缺失返回空 Profile,不抛错 */
|
|
45
|
+
export async function readProjectProfile(projectDir) {
|
|
46
|
+
let markdown;
|
|
47
|
+
try {
|
|
48
|
+
markdown = await readFile(join(projectDir, "AGENTS.md"), "utf8");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return emptyProfile();
|
|
52
|
+
}
|
|
53
|
+
return parseProjectProfile(markdown).profile;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 确认回写辅助:将 profileBlock(完整 `## Project Profile` 区块)写入 markdown。
|
|
57
|
+
* 已存在区块则整块替换,否则追加到末尾。
|
|
58
|
+
*/
|
|
59
|
+
export function upsertProjectProfile(markdown, profileBlock) {
|
|
60
|
+
const section = findProfileSection(markdown);
|
|
61
|
+
if (!section) {
|
|
62
|
+
const trimmed = markdown.trimEnd();
|
|
63
|
+
if (trimmed === "")
|
|
64
|
+
return profileBlock.trimStart();
|
|
65
|
+
return `${trimmed}\n\n${profileBlock}`;
|
|
66
|
+
}
|
|
67
|
+
const lines = markdown.split("\n");
|
|
68
|
+
const before = lines.slice(0, section.start).join("\n").trimEnd();
|
|
69
|
+
const after = lines.slice(section.end).join("\n").trimStart();
|
|
70
|
+
return [before, profileBlock.trim(), after].filter(part => part !== "").join("\n\n");
|
|
71
|
+
}
|
|
72
|
+
/** 定位 Profile 区块:标题行索引与结束行索引(下一个 `##` 标题,含;无则到文件末尾) */
|
|
73
|
+
function findProfileSection(markdown) {
|
|
74
|
+
const lines = markdown.split("\n");
|
|
75
|
+
const start = lines.findIndex(line => line.trim() === PROFILE_SECTION_TITLE);
|
|
76
|
+
if (start === -1)
|
|
77
|
+
return null;
|
|
78
|
+
let end = lines.length;
|
|
79
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
80
|
+
if (/^#{2,}\s/.test(lines[i])) {
|
|
81
|
+
end = i;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { start, end };
|
|
86
|
+
}
|
|
87
|
+
/** 去掉值中的反引号与两端空白 */
|
|
88
|
+
function cleanValue(raw) {
|
|
89
|
+
return raw.replaceAll("`", "").trim();
|
|
90
|
+
}
|
|
91
|
+
/** 逗号分隔的模式列表:trim 并过滤空项 */
|
|
92
|
+
function splitPatterns(value) {
|
|
93
|
+
return value
|
|
94
|
+
.split(",")
|
|
95
|
+
.map(part => part.trim())
|
|
96
|
+
.filter(part => part !== "");
|
|
97
|
+
}
|
|
98
|
+
function applyKey(profile, warnings, key, value) {
|
|
99
|
+
switch (key) {
|
|
100
|
+
case "test command":
|
|
101
|
+
profile.testCommand = value;
|
|
102
|
+
return;
|
|
103
|
+
case "regression command":
|
|
104
|
+
profile.regressionCommand = value;
|
|
105
|
+
return;
|
|
106
|
+
case "test file patterns":
|
|
107
|
+
profile.testFilePatterns = splitPatterns(value);
|
|
108
|
+
return;
|
|
109
|
+
case "implementation file patterns":
|
|
110
|
+
profile.implementationFilePatterns = splitPatterns(value);
|
|
111
|
+
return;
|
|
112
|
+
case "tdd default mode":
|
|
113
|
+
if (VALID_TDD_MODES.has(value)) {
|
|
114
|
+
profile.tddDefaultMode = value;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
warnings.push(`Invalid tdd default mode: ${value}`);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
case "version bump rule":
|
|
121
|
+
profile.versionBumpRule = value;
|
|
122
|
+
return;
|
|
123
|
+
case "version file":
|
|
124
|
+
profile.versionFile = value;
|
|
125
|
+
return;
|
|
126
|
+
case "tag format":
|
|
127
|
+
if (value.includes("{version}")) {
|
|
128
|
+
profile.tagFormat = value;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
warnings.push(`tag format must contain {version} placeholder: ${value}`);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
case "release workflow":
|
|
135
|
+
profile.releaseWorkflowPath = value;
|
|
136
|
+
return;
|
|
137
|
+
case "risk patterns":
|
|
138
|
+
profile.riskPatterns = splitPatterns(value);
|
|
139
|
+
return;
|
|
140
|
+
default:
|
|
141
|
+
warnings.push(`Unknown profile key: ${key}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/kernel/profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { FlowRecordRef, TaskRecordRef } from "./types.js";
|
|
2
|
+
/** TDD evidence 受控 comment 的 marker(单个 comment 承载全部证据) */
|
|
3
|
+
export declare const EVIDENCE_MARKER_START = "<!-- cabbage-tdd-evidence:start -->";
|
|
4
|
+
export declare const EVIDENCE_MARKER_END = "<!-- cabbage-tdd-evidence:end -->";
|
|
5
|
+
/** Flow Record(Parent Issue)标签 */
|
|
6
|
+
export declare const FLOW_LABEL = "cabbage:flow";
|
|
7
|
+
/** 阶段标签前缀:cabbage:stage:<stage> */
|
|
8
|
+
export declare const STAGE_LABEL_PREFIX = "cabbage:stage:";
|
|
9
|
+
type GhFn = (args: string) => Promise<{
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function setRecordsGhExecutor(fn: GhFn | null): void;
|
|
14
|
+
/** 构造 marker 包裹的受控 comment body,START marker 行内携带 revision */
|
|
15
|
+
export declare function buildEvidenceCommentBody(block: string, revision: number): string;
|
|
16
|
+
/** 从受控 comment body 提取 revision 与 marker 区间内容;无 marker 或畸形返回 null */
|
|
17
|
+
export declare function extractEvidenceBlock(body: string): {
|
|
18
|
+
revision: number;
|
|
19
|
+
content: string;
|
|
20
|
+
} | null;
|
|
21
|
+
export interface CreateFlowRecordInput {
|
|
22
|
+
slug: string;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
/** 创建 Draft Parent Issue(Flow Record),打 cabbage:flow 标签 */
|
|
26
|
+
export declare function createFlowRecord(input: CreateFlowRecordInput): Promise<{
|
|
27
|
+
ok: true;
|
|
28
|
+
ref: FlowRecordRef;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
error: string;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function readFlowRecord(parentIssueNumber: number): Promise<{
|
|
34
|
+
ok: true;
|
|
35
|
+
body: string;
|
|
36
|
+
} | {
|
|
37
|
+
ok: false;
|
|
38
|
+
code: "NOT_FOUND";
|
|
39
|
+
error: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* 乐观锁更新 Flow Record body:mutex 串行 + previousBody 比对。
|
|
43
|
+
* 比对不一致(外部并发修改)→ 重读最新 body 重试 1 次(retried: true)。
|
|
44
|
+
*/
|
|
45
|
+
export declare function updateFlowRecord(parentIssueNumber: number, previousBody: string, buildNewBody: (current: string) => string): Promise<{
|
|
46
|
+
ok: true;
|
|
47
|
+
retried: boolean;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
error: string;
|
|
51
|
+
}>;
|
|
52
|
+
export interface CreateTaskRecordInput {
|
|
53
|
+
title: string;
|
|
54
|
+
body: string;
|
|
55
|
+
parentIssueNumber: number;
|
|
56
|
+
}
|
|
57
|
+
/** 创建关联 Parent 的 Sub Issue(Task Record) */
|
|
58
|
+
export declare function createTaskRecord(input: CreateTaskRecordInput): Promise<{
|
|
59
|
+
ok: true;
|
|
60
|
+
ref: TaskRecordRef;
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
error: string;
|
|
64
|
+
}>;
|
|
65
|
+
export interface TddEvidenceComment {
|
|
66
|
+
id: number;
|
|
67
|
+
body: string;
|
|
68
|
+
revision: number;
|
|
69
|
+
}
|
|
70
|
+
/** 读取 Task Record 的单个受控 evidence comment;不存在返回 null */
|
|
71
|
+
export declare function readTddEvidenceComment(issueNumber: number): Promise<TddEvidenceComment | null>;
|
|
72
|
+
/**
|
|
73
|
+
* 追加 TDD evidence 到 Task Record 的单个受控 comment:
|
|
74
|
+
* 无受控 comment → 新建(revision 1);有 → marker 区间内 append,revision+1(PATCH 更新)。
|
|
75
|
+
* mutex 按 issueNumber 串行;block 已存在则幂等跳过(追加不重复)。
|
|
76
|
+
*/
|
|
77
|
+
export declare function appendTddEvidence(issueNumber: number, block: string): Promise<{
|
|
78
|
+
ok: boolean;
|
|
79
|
+
revision: number;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** 设置 Flow Record 的阶段标签:移除其它 cabbage:stage:*,添加当前阶段 */
|
|
83
|
+
export declare function setStageLabel(parentIssueNumber: number, stage: string): Promise<{
|
|
84
|
+
ok: boolean;
|
|
85
|
+
error?: string;
|
|
86
|
+
}>;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { gh as ghCli } from "../util/gh.js";
|
|
2
|
+
import { escapeShellArg } from "../util/shell.js";
|
|
3
|
+
import { KeyedMutex } from "./mutex.js";
|
|
4
|
+
/** TDD evidence 受控 comment 的 marker(单个 comment 承载全部证据) */
|
|
5
|
+
export const EVIDENCE_MARKER_START = "<!-- cabbage-tdd-evidence:start -->";
|
|
6
|
+
export const EVIDENCE_MARKER_END = "<!-- cabbage-tdd-evidence:end -->";
|
|
7
|
+
/** Flow Record(Parent Issue)标签 */
|
|
8
|
+
export const FLOW_LABEL = "cabbage:flow";
|
|
9
|
+
/** 阶段标签前缀:cabbage:stage:<stage> */
|
|
10
|
+
export const STAGE_LABEL_PREFIX = "cabbage:stage:";
|
|
11
|
+
let recordsGhExecutor = null;
|
|
12
|
+
export function setRecordsGhExecutor(fn) {
|
|
13
|
+
recordsGhExecutor = fn;
|
|
14
|
+
}
|
|
15
|
+
async function gh(args, timeout) {
|
|
16
|
+
if (recordsGhExecutor) {
|
|
17
|
+
return recordsGhExecutor(args);
|
|
18
|
+
}
|
|
19
|
+
return ghCli(args, timeout);
|
|
20
|
+
}
|
|
21
|
+
// ─── evidence comment 纯函数 ───
|
|
22
|
+
/** 构造 marker 包裹的受控 comment body,START marker 行内携带 revision */
|
|
23
|
+
export function buildEvidenceCommentBody(block, revision) {
|
|
24
|
+
return `${EVIDENCE_MARKER_START} revision:${revision}\n${block}\n${EVIDENCE_MARKER_END}`;
|
|
25
|
+
}
|
|
26
|
+
/** 从受控 comment body 提取 revision 与 marker 区间内容;无 marker 或畸形返回 null */
|
|
27
|
+
export function extractEvidenceBlock(body) {
|
|
28
|
+
const startIdx = body.indexOf(EVIDENCE_MARKER_START);
|
|
29
|
+
const endIdx = body.indexOf(EVIDENCE_MARKER_END);
|
|
30
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const newlineAfterStart = body.indexOf("\n", startIdx);
|
|
34
|
+
if (newlineAfterStart === -1 || newlineAfterStart > endIdx) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const header = body.slice(startIdx, newlineAfterStart);
|
|
38
|
+
const revisionMatch = header.match(/revision:(\d+)/);
|
|
39
|
+
if (!revisionMatch) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const content = body.slice(newlineAfterStart + 1, endIdx).trim();
|
|
43
|
+
if (content === "") {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return { revision: Number(revisionMatch[1]), content };
|
|
47
|
+
}
|
|
48
|
+
// ─── 记录写入互斥(按 issueNumber 串行) ───
|
|
49
|
+
const recordMutex = new KeyedMutex();
|
|
50
|
+
async function readBody(issueNumber) {
|
|
51
|
+
const { stdout } = await gh(`issue view ${issueNumber} --json body --jq .body`);
|
|
52
|
+
return stdout;
|
|
53
|
+
}
|
|
54
|
+
async function writeBody(issueNumber, body) {
|
|
55
|
+
const escaped = escapeShellArg(body);
|
|
56
|
+
await gh(`issue edit ${issueNumber} --body '${escaped}'`);
|
|
57
|
+
}
|
|
58
|
+
/** 创建 Draft Parent Issue(Flow Record),打 cabbage:flow 标签 */
|
|
59
|
+
export async function createFlowRecord(input) {
|
|
60
|
+
try {
|
|
61
|
+
const escapedBody = escapeShellArg(input.body);
|
|
62
|
+
const { stdout } = await gh(`issue create --title '${input.slug}' --body '${escapedBody}' --label '${FLOW_LABEL}' --draft --json number --jq .number`);
|
|
63
|
+
const number = Number(stdout.trim());
|
|
64
|
+
if (!Number.isInteger(number)) {
|
|
65
|
+
throw new Error(`invalid issue number: ${stdout}`);
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, ref: { parentIssueNumber: number, slug: input.slug } };
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return { ok: false, error: String(err) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function readFlowRecord(parentIssueNumber) {
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await gh(`issue view ${parentIssueNumber} --json body --jq .body`);
|
|
76
|
+
return { ok: true, body: stdout };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return { ok: false, code: "NOT_FOUND", error: String(err) };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 乐观锁更新 Flow Record body:mutex 串行 + previousBody 比对。
|
|
84
|
+
* 比对不一致(外部并发修改)→ 重读最新 body 重试 1 次(retried: true)。
|
|
85
|
+
*/
|
|
86
|
+
export async function updateFlowRecord(parentIssueNumber, previousBody, buildNewBody) {
|
|
87
|
+
return recordMutex.runExclusive(parentIssueNumber, async () => {
|
|
88
|
+
try {
|
|
89
|
+
const current = await readBody(parentIssueNumber);
|
|
90
|
+
if (current === previousBody) {
|
|
91
|
+
await writeBody(parentIssueNumber, buildNewBody(current));
|
|
92
|
+
return { ok: true, retried: false };
|
|
93
|
+
}
|
|
94
|
+
// 冲突:重读最新 body 重试 1 次
|
|
95
|
+
const latest = await readBody(parentIssueNumber);
|
|
96
|
+
await writeBody(parentIssueNumber, buildNewBody(latest));
|
|
97
|
+
return { ok: true, retried: true };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return { ok: false, error: String(err) };
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** 创建关联 Parent 的 Sub Issue(Task Record) */
|
|
105
|
+
export async function createTaskRecord(input) {
|
|
106
|
+
try {
|
|
107
|
+
const escapedBody = escapeShellArg(input.body);
|
|
108
|
+
const { stdout } = await gh(`issue create --title '${input.title}' --body '${escapedBody}' --parent ${input.parentIssueNumber} --json number --jq .number`);
|
|
109
|
+
const number = Number(stdout.trim());
|
|
110
|
+
if (!Number.isInteger(number)) {
|
|
111
|
+
throw new Error(`invalid issue number: ${stdout}`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
ref: { parentIssueNumber: input.parentIssueNumber, taskId: input.title, issueNumber: number },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return { ok: false, error: String(err) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** 读取 Task Record 的单个受控 evidence comment;不存在返回 null */
|
|
123
|
+
export async function readTddEvidenceComment(issueNumber) {
|
|
124
|
+
try {
|
|
125
|
+
const { stdout } = await gh(`issue view ${issueNumber} --json comments --jq '[.comments[] | select(.body | contains("${EVIDENCE_MARKER_START}")) | {id: (.id|tonumber), body}] | first'`);
|
|
126
|
+
const trimmed = stdout.trim();
|
|
127
|
+
if (trimmed === "" || trimmed === "null") {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const parsed = JSON.parse(trimmed);
|
|
131
|
+
const extracted = extractEvidenceBlock(parsed.body);
|
|
132
|
+
if (!extracted) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return { id: parsed.id, body: parsed.body, revision: extracted.revision };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 追加 TDD evidence 到 Task Record 的单个受控 comment:
|
|
143
|
+
* 无受控 comment → 新建(revision 1);有 → marker 区间内 append,revision+1(PATCH 更新)。
|
|
144
|
+
* mutex 按 issueNumber 串行;block 已存在则幂等跳过(追加不重复)。
|
|
145
|
+
*/
|
|
146
|
+
export async function appendTddEvidence(issueNumber, block) {
|
|
147
|
+
return recordMutex.runExclusive(issueNumber, async () => {
|
|
148
|
+
try {
|
|
149
|
+
const existing = await readTddEvidenceComment(issueNumber);
|
|
150
|
+
if (!existing) {
|
|
151
|
+
const body = buildEvidenceCommentBody(block, 1);
|
|
152
|
+
await gh(`issue comment ${issueNumber} --body '${escapeShellArg(body)}'`);
|
|
153
|
+
return { ok: true, revision: 1 };
|
|
154
|
+
}
|
|
155
|
+
// 幂等:block 已在 marker 区间内,不重复追加
|
|
156
|
+
const extracted = extractEvidenceBlock(existing.body);
|
|
157
|
+
if (extracted && extracted.content.includes(block)) {
|
|
158
|
+
return { ok: true, revision: existing.revision };
|
|
159
|
+
}
|
|
160
|
+
const newBody = appendBlockToEvidenceBody(existing.body, block, existing.revision);
|
|
161
|
+
const repo = await gh("repo view --json nameWithOwner --jq .nameWithOwner");
|
|
162
|
+
await gh(`api repos/${repo.stdout.trim()}/issues/comments/${existing.id} -X PATCH -f body='${escapeShellArg(newBody)}'`);
|
|
163
|
+
return { ok: true, revision: existing.revision + 1 };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return { ok: false, revision: 0, error: String(err) };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** 在 END marker 前插入新 block,并将 START 行 revision 递增 */
|
|
171
|
+
function appendBlockToEvidenceBody(body, block, revision) {
|
|
172
|
+
const endIdx = body.indexOf(EVIDENCE_MARKER_END);
|
|
173
|
+
const prefix = body.slice(0, endIdx);
|
|
174
|
+
const suffix = body.slice(endIdx);
|
|
175
|
+
return `${prefix}${block}\n${suffix}`.replace(/(revision:)\d+/, `$1${revision + 1}`);
|
|
176
|
+
}
|
|
177
|
+
// ─── labels ───
|
|
178
|
+
/** 设置 Flow Record 的阶段标签:移除其它 cabbage:stage:*,添加当前阶段 */
|
|
179
|
+
export async function setStageLabel(parentIssueNumber, stage) {
|
|
180
|
+
try {
|
|
181
|
+
const { stdout } = await gh(`issue view ${parentIssueNumber} --json labels --jq '[.labels[].name] | join(" ") | tostring'`);
|
|
182
|
+
const currentLabels = stdout.trim() === "" ? [] : stdout.trim().split(" ");
|
|
183
|
+
const target = `${STAGE_LABEL_PREFIX}${stage}`;
|
|
184
|
+
const stale = currentLabels.filter(l => l.startsWith(STAGE_LABEL_PREFIX) && l !== target);
|
|
185
|
+
const args = [];
|
|
186
|
+
for (const label of stale) {
|
|
187
|
+
args.push(`--remove-label '${label}'`);
|
|
188
|
+
}
|
|
189
|
+
args.push(`--add-label '${target}'`);
|
|
190
|
+
await gh(`issue edit ${parentIssueNumber} ${args.join(" ")}`);
|
|
191
|
+
return { ok: true };
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return { ok: false, error: String(err) };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/kernel/records.ts"],"names":[],"mappings":"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"}
|