Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ opencode DAG 编排的参考模板配置仓库(唯一权威源)。
仓库只保留 7 个领域,每个领域恰好一份 `full` 和一份 `lite`:

| 领域 | 完整参考 | 轻量参考 |
| ---------------- | ------------------------------- | ------------------------------- |
| --------------- | ------------------------------- | ------------------------------- |
| 产品文档与规划 | `product-planning-full.yaml` | `product-planning-lite.yaml` |
| 技术与架构设计 | `technical-design-full.yaml` | `technical-design-lite.yaml` |
| 项目开发交付 | `project-development-full.yaml` | `project-development-lite.yaml` |
Expand All @@ -19,6 +19,16 @@ opencode DAG 编排的参考模板配置仓库(唯一权威源)。
| 漏洞与供应链安全 | `security-audit-full.yaml` | `security-audit-lite.yaml` |
| 性能与资源审计 | `performance-audit-full.yaml` | `performance-audit-lite.yaml` |

## 跨域路线

跨域路线不占领域槽位(7 域 one full + one lite 的定策不变),是跨多个领域的
预拼装拓扑:

| 路线 | 用途 |
| --------------------- | ---------------------------------------------------------------------------- |
| `ultra-flow-route.yaml` | 六积木(探索 → 设计 → 开发 → 验收 → 发布 → 汇总)+ 五道分级检查点(轻量方向判定 ×3、独立取证三件套 ×2)。检查点 verdict 为 `continue`/`replan`:continue 不扰流程,replan 唤醒父会话做 additive 重规划(findings 注入、回环 ≤3)。发布积木可由父对话在重定向时删除 |
| `release-route.yaml` | 发布装配:机制探测 → 版本推导(latest tag + commit 类型)→ 人工门(默认 hold,父会话注入确认后才 publish)→ 发布执行 → 验证 |

## 积木式路线

新路线用 `config.objective + config.blocks` 组合以下原语:
Expand Down
96 changes: 96 additions & 0 deletions release-route.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Cross-domain route (not one of the 7 domain pairs): release assembly after
# the final ordered merge. Default is a human gate — release-execute only runs
# after the parent confirms the release brief. The parent retargets the
# objective to the real plan and repository before start.
title: "Release assembly: mechanism probe → version derivation → human gate → publish → verify"
config:
name: release-route
max_concurrency: 2
max_node_replan_attempts: 3
max_total_nodes: 10
node_defaults:
worker_config:
timeout_ms: 900000
nodes:
- id: release-map
name: "probe release mechanics"
worker_type: explore
depends_on: []
prompt_template:
inline: >-
Probe the release mechanics of this repository: the workflow objective.
Identify the release pathway (release workflow dispatch vs platform
release creation vs package publish), the latest tag, branch
protection or ruleset constraints on releasing, and what the merge
history since the last tag contains. Report facts only.

- id: release-plan
name: "derive version and draft changelog"
worker_type: plan
depends_on: [release-map]
prompt_template:
inline: >-
From the release-map evidence, derive the candidate version (latest
tag plus landed commit types: feat → minor, fix → patch, breaking →
major) and draft the changelog grouped by type. Produce the complete
release brief: mechanism, version, changelog, publish steps, and
rollback path. Flag every assumption.

- id: release-gate
name: "release human gate brief"
worker_type: general
depends_on: [release-plan]
report_to_parent: true
required: true
worker_config:
timeout_ms: 600000
output_schema:
type: object
properties:
verdict:
type: string
enum: [proceed, hold]
brief:
type: string
required: [verdict, brief]
prompt_template:
inline: >-
Release gate. Consolidate the release-plan brief for human
confirmation: mechanism, candidate version, changelog draft, and the
exact publish command sequence. Default verdict hold — publishing is
irreversible and the parent must obtain the human decision; submit
proceed ONLY when the parent has injected an explicit confirmation.

- id: release-execute
name: "publish the release"
worker_type: general
depends_on: [release-gate]
prompt_template:
inline: >-
Execute the confirmed release exactly as briefed: create the tag,
write the changelog entry or release notes, and trigger the detected
release pathway. Record every command and its output; do not
improvise steps beyond the brief.

- id: release-verify
name: "verify the release landed"
worker_type: verify
depends_on: [release-execute]
prompt_template:
inline: >-
Verify the release exists and matches the brief: the tag or release
asset is visible on the platform, the version string is exact, and
any release workflow run completed successfully. Cite the observed
evidence; PASS only on exact match.

- id: release-report
name: "release report"
worker_type: general
depends_on: [release-verify]
prompt_template:
inline: >-
Report the release outcome: mechanism used, published version,
verification evidence, and rollback path. Write the complete report
to a non-empty markdown file under the project
.opencode/workflow-reports/ directory, then submit ONLY that
absolute file path as your output.
1 change: 1 addition & 0 deletions script/validate-route-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const routes = [
"security-audit",
"technical-design",
].flatMap((domain) => [`${domain}-full.yaml`, `${domain}-lite.yaml`])
routes.push("release-route.yaml", "ultra-flow-route.yaml")

let fixture: string

Expand Down
15 changes: 10 additions & 5 deletions script/validate-route-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,23 @@ const domains = [
const expected = domains
.flatMap((domain) => [`${domain}-full.yaml`, `${domain}-lite.yaml`])
.sort()
const routeNames = expected.map((file) => path.basename(file, ".yaml"))
// Cross-domain routes compose several domains into one topology; they are not
// domain pairs, so they bypass the lite/topology checks but still carry
// config with config.name == filename stem.
const crossDomainRoutes = ["release-route.yaml", "ultra-flow-route.yaml"]
const catalog = [...expected, ...crossDomainRoutes].sort()
const routeNames = catalog.map((file) => path.basename(file, ".yaml"))
const files = (await fs.readdir(root))
.filter((file) => /\.ya?ml$/i.test(file))
.sort()

if (JSON.stringify(files) !== JSON.stringify(expected)) {
console.error(`Route catalog must contain exactly:\n${expected.join("\n")}`)
if (JSON.stringify(files) !== JSON.stringify(catalog)) {
console.error(`Route catalog must contain exactly:\n${catalog.join("\n")}`)
console.error(`Found:\n${files.join("\n")}`)
process.exit(1)
}

for (const filename of expected) {
for (const filename of catalog) {
const parsed: unknown = Bun.YAML.parse(
await Bun.file(path.join(root, filename)).text(),
)
Expand All @@ -38,7 +43,7 @@ for (const filename of expected) {
}

console.log(
`Validated ${expected.length} routes across ${domains.length} full/lite domains.`,
`Validated ${catalog.length} routes: ${domains.length} full/lite domains plus ${crossDomainRoutes.length} cross-domain routes.`,
)

function isRecord(value: unknown): value is Record<string, unknown> {
Expand Down
Loading
Loading