@chenmk/superflow 0.4.0 → 0.5.1
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/INSTALL.en.md +58 -12
- package/INSTALL.md +62 -33
- package/README.md +181 -54
- package/README.zh-CN.md +158 -51
- package/assets/brand/managed-review-loop.png +0 -0
- package/assets/manifest.json +10 -5
- package/assets/scripts/superflow-coding-ready.mjs +135 -0
- package/assets/scripts/superflow-dependency-update-hook.sh +81 -8
- package/assets/scripts/superflow-document-audit.mjs +240 -0
- package/assets/scripts/superflow-environment-preflight.mjs +398 -0
- package/assets/scripts/superflow-hook-guard.sh +40 -1
- package/assets/scripts/superflow-managed-command-guard.py +309 -0
- package/assets/scripts/superflow-managed-executor-preflight.mjs +421 -0
- package/assets/scripts/superflow-managed-owner-verification.sh +117 -0
- package/assets/scripts/superflow-managed-work-check.mjs +287 -26
- package/assets/scripts/superflow-managed-work-guard.sh +6 -92
- package/assets/skills/superflow-clarify/SKILL.md +14 -0
- package/assets/skills/superflow-design/SKILL.md +38 -13
- package/assets/skills/superflow-docs/SKILL.md +93 -2
- package/assets/skills/superflow-pipeline/SKILL.md +167 -10
- package/assets/skills/superflow-pipeline/references/batch-prompt-template.md +17 -9
- package/assets/skills/superflow-pipeline/references/document-readiness.md +157 -0
- package/assets/skills/superflow-pipeline/references/document-templates.md +9 -5
- package/assets/skills/superflow-pipeline/references/feature-gated-workflow.md +14 -0
- package/assets/skills/superflow-pipeline/references/implementation-prompt-template.md +20 -11
- package/assets/skills/superflow-pipeline/references/managed-work.md +123 -15
- package/assets/skills/superflow-pipeline/references/orchestration.md +2 -2
- package/assets/skills/superflow-pipeline/references/p0-baseline-template.md +3 -2
- package/assets/skills/superflow-pipeline/references/quality-gate.md +7 -5
- package/assets/skills/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills/superflow-requirement-review/SKILL.md +178 -0
- package/assets/skills/superflow-requirement-review/agents/openai.yaml +4 -0
- package/assets/skills-en/superflow-clarify/SKILL.md +17 -0
- package/assets/skills-en/superflow-design/SKILL.md +31 -12
- package/assets/skills-en/superflow-docs/SKILL.md +99 -0
- package/assets/skills-en/superflow-pipeline/SKILL.md +163 -9
- package/assets/skills-en/superflow-pipeline/references/batch-prompt-template.md +4 -0
- package/assets/skills-en/superflow-pipeline/references/document-readiness.md +125 -0
- package/assets/skills-en/superflow-pipeline/references/document-templates.md +10 -0
- package/assets/skills-en/superflow-pipeline/references/feature-gated-workflow.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/implementation-prompt-template.md +7 -0
- package/assets/skills-en/superflow-pipeline/references/managed-work.md +160 -10
- package/assets/skills-en/superflow-pipeline/references/quality-gate.md +12 -0
- package/assets/skills-en/superflow-pipeline/references/reviewer-checklist.md +7 -0
- package/assets/skills-en/superflow-pipeline/scripts/superflow-guard.sh +205 -9
- package/assets/skills-en/superflow-pipeline/scripts/superflow-handoff.sh +7 -4
- package/assets/skills-en/superflow-requirement-review/SKILL.md +99 -0
- package/assets/skills-en/superflow-requirement-review/agents/openai.yaml +4 -0
- package/dist/app/cli.js +64 -9
- package/dist/app/cli.js.map +1 -1
- package/dist/app/commands/change-guard.js +19 -2
- package/dist/app/commands/change-guard.js.map +1 -1
- package/dist/app/commands/check.js +107 -45
- package/dist/app/commands/check.js.map +1 -1
- package/dist/app/commands/docs.js +7 -3
- package/dist/app/commands/docs.js.map +1 -1
- package/dist/app/commands/doctor.js +174 -198
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.js +64 -0
- package/dist/app/commands/eval.js.map +1 -0
- package/dist/app/commands/implement.js +7 -3
- package/dist/app/commands/implement.js.map +1 -1
- package/dist/app/commands/init.js +12 -24
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/mcp.js +167 -0
- package/dist/app/commands/mcp.js.map +1 -0
- package/dist/app/commands/pipeline.js +652 -18
- package/dist/app/commands/pipeline.js.map +1 -1
- package/dist/app/commands/requirement-review.js +6 -0
- package/dist/app/commands/requirement-review.js.map +1 -0
- package/dist/app/commands/status.js +66 -3
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.js +3 -3
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.js +83 -9
- package/dist/app/commands/update.js.map +1 -1
- package/dist/domains/agent.js +3 -11
- package/dist/domains/agent.js.map +1 -1
- package/dist/domains/config/cli-help.js +54 -12
- package/dist/domains/config/cli-help.js.map +1 -1
- package/dist/domains/config/i18n.js +6 -8
- package/dist/domains/config/i18n.js.map +1 -1
- package/dist/domains/config/manifest.js +0 -3
- package/dist/domains/config/manifest.js.map +1 -1
- package/dist/domains/deps.js +68 -7
- package/dist/domains/deps.js.map +1 -1
- package/dist/domains/managed-work/completion-policy.js +174 -0
- package/dist/domains/managed-work/completion-policy.js.map +1 -0
- package/dist/domains/managed-work/context-manifest.js +208 -0
- package/dist/domains/managed-work/context-manifest.js.map +1 -0
- package/dist/domains/managed-work/contract.js +135 -4
- package/dist/domains/managed-work/contract.js.map +1 -1
- package/dist/domains/managed-work/control-signals.js +34 -0
- package/dist/domains/managed-work/control-signals.js.map +1 -0
- package/dist/domains/managed-work/control.js +628 -0
- package/dist/domains/managed-work/control.js.map +1 -0
- package/dist/domains/managed-work/evaluation.js +497 -0
- package/dist/domains/managed-work/evaluation.js.map +1 -0
- package/dist/domains/managed-work/evidence-path.js +14 -0
- package/dist/domains/managed-work/evidence-path.js.map +1 -0
- package/dist/domains/managed-work/execution-contract.js +167 -0
- package/dist/domains/managed-work/execution-contract.js.map +1 -0
- package/dist/domains/managed-work/executor-policy.js +65 -0
- package/dist/domains/managed-work/executor-policy.js.map +1 -0
- package/dist/domains/managed-work/failure.js +65 -0
- package/dist/domains/managed-work/failure.js.map +1 -0
- package/dist/domains/managed-work/host-review.js +77 -0
- package/dist/domains/managed-work/host-review.js.map +1 -0
- package/dist/domains/managed-work/human-messages.js +31 -0
- package/dist/domains/managed-work/human-messages.js.map +1 -0
- package/dist/domains/managed-work/input.js +29 -3
- package/dist/domains/managed-work/input.js.map +1 -1
- package/dist/domains/managed-work/journal.js +77 -3
- package/dist/domains/managed-work/journal.js.map +1 -1
- package/dist/domains/managed-work/lock.js +67 -1
- package/dist/domains/managed-work/lock.js.map +1 -1
- package/dist/domains/managed-work/notifications.js +0 -15
- package/dist/domains/managed-work/notifications.js.map +1 -1
- package/dist/domains/managed-work/pair-admission.js +108 -0
- package/dist/domains/managed-work/pair-admission.js.map +1 -0
- package/dist/domains/managed-work/prompts.js +257 -22
- package/dist/domains/managed-work/prompts.js.map +1 -1
- package/dist/domains/managed-work/redaction.js +90 -0
- package/dist/domains/managed-work/redaction.js.map +1 -0
- package/dist/domains/managed-work/review-convergence.js +41 -0
- package/dist/domains/managed-work/review-convergence.js.map +1 -0
- package/dist/domains/managed-work/review-facts.js +181 -0
- package/dist/domains/managed-work/review-facts.js.map +1 -0
- package/dist/domains/managed-work/rule-selection.js +84 -0
- package/dist/domains/managed-work/rule-selection.js.map +1 -0
- package/dist/domains/managed-work/runner.js +1967 -175
- package/dist/domains/managed-work/runner.js.map +1 -1
- package/dist/domains/managed-work/schemas.js +72 -2
- package/dist/domains/managed-work/schemas.js.map +1 -1
- package/dist/domains/managed-work/service.js +82 -13
- package/dist/domains/managed-work/service.js.map +1 -1
- package/dist/domains/managed-work/state.js +195 -5
- package/dist/domains/managed-work/state.js.map +1 -1
- package/dist/domains/managed-work/storage.js +66 -24
- package/dist/domains/managed-work/storage.js.map +1 -1
- package/dist/domains/managed-work/types.js +12 -9
- package/dist/domains/managed-work/types.js.map +1 -1
- package/dist/domains/managed-work/verification-categories.js +39 -0
- package/dist/domains/managed-work/verification-categories.js.map +1 -0
- package/dist/domains/managed-work/verification-policy.js +116 -0
- package/dist/domains/managed-work/verification-policy.js.map +1 -0
- package/dist/domains/managed-work/wait.js +21 -3
- package/dist/domains/managed-work/wait.js.map +1 -1
- package/dist/domains/managed-work/workspace-binding.js +133 -0
- package/dist/domains/managed-work/workspace-binding.js.map +1 -0
- package/dist/domains/sdd-readiness.js +44 -0
- package/dist/domains/sdd-readiness.js.map +1 -0
- package/dist/domains/skill/deploy.js +0 -6
- package/dist/domains/skill/deploy.js.map +1 -1
- package/dist/domains/skill/scripts.js +0 -8
- package/dist/domains/skill/scripts.js.map +1 -1
- package/dist/domains/state.js +1 -2
- package/dist/domains/state.js.map +1 -1
- package/dist/mcp/runtime-identity.js +21 -0
- package/dist/mcp/runtime-identity.js.map +1 -0
- package/dist/mcp/server.js +369 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/platform/agent-process.js +499 -38
- package/dist/platform/agent-process.js.map +1 -1
- package/dist/platform/executable.js +28 -0
- package/dist/platform/executable.js.map +1 -0
- package/dist/platform/paths.js +1 -17
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/process-tree.js +44 -0
- package/dist/platform/process-tree.js.map +1 -0
- package/dist/platform/process.js +1 -1
- package/dist/platform/process.js.map +1 -1
- package/package.json +17 -7
- package/scripts/check-design-principles.mjs +107 -0
- package/scripts/check-managed-work-design.mjs +507 -0
- package/scripts/clean-dist.js +9 -0
- package/scripts/ensure-bin-modes.js +11 -0
- package/scripts/preinstall.js +57 -0
- package/dist/cli/index.js +0 -193
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/archive.js +0 -6
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/clarify.js +0 -6
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/design.js +0 -28
- package/dist/commands/design.js.map +0 -1
- package/dist/commands/docs.js +0 -6
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js +0 -494
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/implement.js +0 -6
- package/dist/commands/implement.js.map +0 -1
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/pipeline.js +0 -6
- package/dist/commands/pipeline.js.map +0 -1
- package/dist/commands/scan.js +0 -63
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/status.js +0 -173
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/uninstall.js +0 -214
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -193
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/verify.js +0 -6
- package/dist/commands/verify.js.map +0 -1
- package/dist/core/assets.js +0 -27
- package/dist/core/assets.js.map +0 -1
- package/dist/core/cli-help.js +0 -98
- package/dist/core/cli-help.js.map +0 -1
- package/dist/core/context.js +0 -123
- package/dist/core/context.js.map +0 -1
- package/dist/core/dependencies.js +0 -146
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/detect.js +0 -64
- package/dist/core/detect.js.map +0 -1
- package/dist/core/i18n.js +0 -105
- package/dist/core/i18n.js.map +0 -1
- package/dist/core/integrity.js +0 -46
- package/dist/core/integrity.js.map +0 -1
- package/dist/core/manifest.js +0 -21
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/prompts.js +0 -20
- package/dist/core/prompts.js.map +0 -1
- package/dist/core/registry.js +0 -134
- package/dist/core/registry.js.map +0 -1
- package/dist/core/rules.js +0 -17
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scripts.js +0 -48
- package/dist/core/scripts.js.map +0 -1
- package/dist/core/skill-check.js +0 -32
- package/dist/core/skill-check.js.map +0 -1
- package/dist/core/skills.js +0 -62
- package/dist/core/skills.js.map +0 -1
- package/dist/core/state.js +0 -54
- package/dist/core/state.js.map +0 -1
- package/dist/utils/path.js +0 -11
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/shell.js +0 -29
- package/dist/utils/shell.js.map +0 -1
|
@@ -18,7 +18,14 @@ const task = JSON.parse(fs.readFileSync(taskFile, 'utf8'));
|
|
|
18
18
|
verifyContract(task);
|
|
19
19
|
const runsDir = path.join(taskDir, 'runs');
|
|
20
20
|
const runs = fs.existsSync(runsDir)
|
|
21
|
-
? fs.readdirSync(runsDir).filter((entry) =>
|
|
21
|
+
? fs.readdirSync(runsDir).filter((entry) => {
|
|
22
|
+
const candidate = path.join(runsDir, entry);
|
|
23
|
+
return (
|
|
24
|
+
entry.startsWith('run-') &&
|
|
25
|
+
fs.statSync(candidate).isDirectory() &&
|
|
26
|
+
fs.existsSync(path.join(candidate, 'run-state.json'))
|
|
27
|
+
);
|
|
28
|
+
})
|
|
22
29
|
: [];
|
|
23
30
|
if (runs.length !== 1) fail(`首版要求一个活动 run,实际 ${runs.length}`);
|
|
24
31
|
|
|
@@ -31,39 +38,68 @@ for (const file of [stateFile, journalFile, reportFile]) {
|
|
|
31
38
|
}
|
|
32
39
|
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
assertMax('
|
|
36
|
-
assertMax(
|
|
37
|
-
|
|
41
|
+
if (task.budgets?.unlimitedAgentInvocations !== true) {
|
|
42
|
+
assertMax('reviewRound', state.reviewRound, 10);
|
|
43
|
+
assertMax(
|
|
44
|
+
'executorInvocations',
|
|
45
|
+
state.executorInvocations,
|
|
46
|
+
20 + (state.executorInvocationCredits ?? 0),
|
|
47
|
+
);
|
|
48
|
+
assertMax(
|
|
49
|
+
'totalAgentInvocations',
|
|
50
|
+
state.totalAgentInvocations,
|
|
51
|
+
30 + (state.totalAgentInvocationCredits ?? 0),
|
|
52
|
+
);
|
|
53
|
+
assertMax('reviewInvocations', state.reviewInvocations, 10);
|
|
54
|
+
}
|
|
38
55
|
if (state.contractHash !== task.contractHash) fail('任务合同哈希与运行状态不一致');
|
|
39
56
|
if (state.status !== task.status) fail('任务合同状态与运行状态不一致');
|
|
40
57
|
if (state.executorInvocations > 0 && !state.executorSession?.sessionId) {
|
|
41
58
|
fail('已有执行调用但缺少执行会话编号');
|
|
42
59
|
}
|
|
43
|
-
if (state.reviewInvocations > 0 && !state.supervisorSession?.sessionId) {
|
|
44
|
-
fail('已有正式检查但缺少监督会话编号');
|
|
45
|
-
}
|
|
46
60
|
const events = verifyJournal(journalFile);
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
const deliveryStatuses = new Set([
|
|
63
|
+
'local_delivery_ready',
|
|
64
|
+
'environment_validation_blocked',
|
|
65
|
+
'release_ready',
|
|
66
|
+
]);
|
|
67
|
+
if (deliveryStatuses.has(state.status)) {
|
|
49
68
|
if (!state.lastExecutorResult || !fs.existsSync(state.lastExecutorResult)) {
|
|
50
|
-
fail('
|
|
69
|
+
fail('交付状态缺少最终执行结果');
|
|
51
70
|
}
|
|
52
71
|
if (!state.lastReviewResult || !fs.existsSync(state.lastReviewResult)) {
|
|
53
|
-
fail('
|
|
72
|
+
fail('交付状态缺少最终检查结果');
|
|
54
73
|
}
|
|
55
74
|
assertInsideRun(state.lastExecutorResult, runDir, '最终执行结果');
|
|
56
75
|
assertInsideRun(state.lastReviewResult, runDir, '最终检查结果');
|
|
57
76
|
const executor = JSON.parse(fs.readFileSync(state.lastExecutorResult, 'utf8'));
|
|
58
|
-
verifyExecutorEvidence(task, executor);
|
|
59
77
|
const review = JSON.parse(fs.readFileSync(state.lastReviewResult, 'utf8'));
|
|
78
|
+
const completion = verifyOpenSpecTasksComplete(task, executor);
|
|
79
|
+
verifyDeliveryStatus(state.status, completion);
|
|
80
|
+
if (
|
|
81
|
+
task.source === 'sdd' &&
|
|
82
|
+
!review.verificationCommands?.some(
|
|
83
|
+
(item) =>
|
|
84
|
+
item.exitCode === 0 &&
|
|
85
|
+
/openspec\s+instructions\s+apply/i.test(item.command),
|
|
86
|
+
)
|
|
87
|
+
) {
|
|
88
|
+
fail('SDD 最终评审缺少 openspec instructions apply 命令证据');
|
|
89
|
+
}
|
|
60
90
|
if (review.result !== 'pass' || review.findings?.some((item) => item.blocking)) {
|
|
61
|
-
fail('
|
|
91
|
+
fail('交付状态与最终检查结论不一致');
|
|
62
92
|
}
|
|
93
|
+
verifyExecutorEvidence(task, executor, review, runDir);
|
|
63
94
|
const readyEvents = events.filter((event) => event.eventType === 'run.delivery_ready');
|
|
95
|
+
const reopenedEvents = events.filter(
|
|
96
|
+
(event) => event.eventType === 'run.delivery_reopened',
|
|
97
|
+
);
|
|
64
98
|
const expectedReadyEvents = beforeReadyEvent ? 0 : 1;
|
|
65
|
-
if (readyEvents.length !== expectedReadyEvents) {
|
|
66
|
-
fail(
|
|
99
|
+
if (readyEvents.length !== reopenedEvents.length + expectedReadyEvents) {
|
|
100
|
+
fail(
|
|
101
|
+
`交付就绪事件应比重新打开事件多 ${expectedReadyEvents} 条,实际就绪 ${readyEvents.length}、重新打开 ${reopenedEvents.length}`,
|
|
102
|
+
);
|
|
67
103
|
}
|
|
68
104
|
}
|
|
69
105
|
|
|
@@ -77,18 +113,35 @@ function assertMax(name, value, max) {
|
|
|
77
113
|
|
|
78
114
|
function verifyContract(contract) {
|
|
79
115
|
const budgets = contract.budgets ?? {};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
if (
|
|
117
|
+
budgets.executorPhysicalStopAt !== undefined &&
|
|
118
|
+
(!Number.isInteger(budgets.executorPhysicalStopAt) ||
|
|
119
|
+
budgets.executorPhysicalStopAt <= 0)
|
|
120
|
+
) {
|
|
121
|
+
fail('executorPhysicalStopAt 必须是正整数');
|
|
122
|
+
}
|
|
123
|
+
assertPositiveMax('maxReviewRounds', budgets.maxReviewRounds, 10, true);
|
|
124
|
+
assertPositiveMax('maxExecutorInvocations', budgets.maxExecutorInvocations, 20, true);
|
|
125
|
+
assertPositiveMax('maxTotalAgentInvocations', budgets.maxTotalAgentInvocations, 30, true);
|
|
83
126
|
assertPositiveMax('maxActiveRunHours', budgets.maxActiveRunHours, 24, false);
|
|
84
127
|
assertPositiveMax('maxSingleInvocationHours', budgets.maxSingleInvocationHours, 3, false);
|
|
85
128
|
if (contract.supervisorAgent === contract.executorAgent) {
|
|
86
129
|
fail('监督 Agent 和执行 Agent 不能相同');
|
|
87
130
|
}
|
|
131
|
+
if (contract.supervisorExecution !== 'external_host') {
|
|
132
|
+
fail('监督执行模式只允许 external_host');
|
|
133
|
+
}
|
|
88
134
|
const permissions = contract.permissions ?? {};
|
|
89
135
|
for (const key of ['gitCommit', 'gitPush', 'productionWrites', 'bypassSandbox']) {
|
|
90
136
|
if (permissions[key] !== false) fail(`不可覆盖的权限边界被放宽: ${key}`);
|
|
91
137
|
}
|
|
138
|
+
const disclosure = permissions.externalModelDataDisclosure;
|
|
139
|
+
if (!disclosure?.approved || !disclosure.approvedBy || !disclosure.approvedAt) {
|
|
140
|
+
fail('缺少任务级外部研发 Agent 源码披露授权');
|
|
141
|
+
}
|
|
142
|
+
if (!Array.isArray(disclosure.scope) || disclosure.scope.length === 0) {
|
|
143
|
+
fail('外部研发 Agent 源码披露授权缺少仓库作用域');
|
|
144
|
+
}
|
|
92
145
|
if (contract.taskPrompt) {
|
|
93
146
|
const expectedSnapshot = path.join(
|
|
94
147
|
path.resolve(contract.projectRoot),
|
|
@@ -117,9 +170,16 @@ function verifyContract(contract) {
|
|
|
117
170
|
...(contract.language ? { language: contract.language } : {}),
|
|
118
171
|
objective: contract.objective,
|
|
119
172
|
doneCriteria: contract.doneCriteria,
|
|
173
|
+
...(contract.mandatoryEngineeringRules?.length
|
|
174
|
+
? { mandatoryEngineeringRules: contract.mandatoryEngineeringRules }
|
|
175
|
+
: {}),
|
|
120
176
|
taskPrompt: contract.taskPrompt,
|
|
121
177
|
supervisorAgent: contract.supervisorAgent,
|
|
122
178
|
executorAgent: contract.executorAgent,
|
|
179
|
+
...(contract.supervisorExecution
|
|
180
|
+
? { supervisorExecution: contract.supervisorExecution }
|
|
181
|
+
: {}),
|
|
182
|
+
...(contract.executionMode ? { executionMode: contract.executionMode } : {}),
|
|
123
183
|
permissions: contract.permissions,
|
|
124
184
|
budgets: contract.budgets,
|
|
125
185
|
};
|
|
@@ -133,20 +193,220 @@ function assertPositiveMax(name, value, max, integer) {
|
|
|
133
193
|
}
|
|
134
194
|
}
|
|
135
195
|
|
|
136
|
-
function
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
196
|
+
function verifyOpenSpecTasksComplete(task, executor) {
|
|
197
|
+
if (task.source !== 'sdd' || !task.taskPrompt?.originalPath) {
|
|
198
|
+
return { localPending: [], environmentPending: [], releasePending: [] };
|
|
199
|
+
}
|
|
200
|
+
let current = path.dirname(path.resolve(task.taskPrompt.originalPath));
|
|
201
|
+
const projectRoot = path.resolve(task.projectRoot);
|
|
202
|
+
while (current.startsWith(projectRoot)) {
|
|
203
|
+
const tasksFile = path.join(current, 'tasks.md');
|
|
204
|
+
if (fs.existsSync(tasksFile)) {
|
|
205
|
+
const tasks = fs.readFileSync(tasksFile, 'utf8')
|
|
206
|
+
.split(/\r?\n/)
|
|
207
|
+
.filter((line) => /^\s*- \[[ xX]\]/.test(line))
|
|
208
|
+
.map((line, index) => parseCompletionTask(line, index));
|
|
209
|
+
const localPending = tasks.filter(
|
|
210
|
+
(item) => !item.completed && item.category === 'local_required',
|
|
211
|
+
);
|
|
212
|
+
const environmentPending = tasks.filter(
|
|
213
|
+
(item) => !item.completed && item.category === 'environment_required',
|
|
214
|
+
);
|
|
215
|
+
const releasePending = tasks.filter(
|
|
216
|
+
(item) => !item.completed && item.category === 'release_required',
|
|
217
|
+
);
|
|
218
|
+
if (localPending.length > 0) {
|
|
219
|
+
fail(`OpenSpec tasks.md 仍有 ${localPending.length} 个本地可执行任务未完成`);
|
|
220
|
+
}
|
|
221
|
+
const evidence = new Map(
|
|
222
|
+
(executor.taskEvidence ?? []).map((item) => [item.taskId, item]),
|
|
223
|
+
);
|
|
224
|
+
const successfulCommands = (executor.commands ?? []).filter(
|
|
225
|
+
isAcceptedEvidenceCommand,
|
|
226
|
+
);
|
|
227
|
+
const derivedEvidencePaths = [
|
|
228
|
+
...(executor.evidence ?? []),
|
|
229
|
+
tasksFile,
|
|
230
|
+
path.join(current, 'test-report.md'),
|
|
231
|
+
].filter((item) => taskEvidencePathExists(task, item));
|
|
232
|
+
for (const item of tasks.filter(
|
|
233
|
+
(taskItem) =>
|
|
234
|
+
taskItem.completed && taskItem.category === 'local_required',
|
|
235
|
+
)) {
|
|
236
|
+
const proof = evidence.get(item.taskId);
|
|
237
|
+
if (successfulCommands.length > 0 && derivedEvidencePaths.length > 0) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!proof) {
|
|
241
|
+
fail(`已勾选本地任务 ${item.taskId} 缺少可推导的真实交付证据`);
|
|
242
|
+
}
|
|
243
|
+
if (
|
|
244
|
+
proof.owner !== 'executor' ||
|
|
245
|
+
!proof.evidencePaths?.length ||
|
|
246
|
+
!proof.verificationCommands?.length ||
|
|
247
|
+
!proof.evidencePaths.every((item) => taskEvidencePathExists(task, item)) ||
|
|
248
|
+
!proof.verificationCommands.every((command) =>
|
|
249
|
+
executor.commands?.some(
|
|
250
|
+
(item) =>
|
|
251
|
+
isAcceptedEvidenceCommand(item) &&
|
|
252
|
+
verificationCommandCovered(command, item.command),
|
|
253
|
+
),
|
|
254
|
+
) ||
|
|
255
|
+
!proof.changedFiles.every((file) => executor.changedFiles?.includes(file))
|
|
256
|
+
) {
|
|
257
|
+
fail(`已勾选本地任务 ${item.taskId} 缺少结构化交付证据`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
environmentPending.length + releasePending.length > 0 &&
|
|
262
|
+
(!Array.isArray(executor.releasePrerequisites) ||
|
|
263
|
+
executor.releasePrerequisites.length === 0)
|
|
264
|
+
) {
|
|
265
|
+
fail(
|
|
266
|
+
`OpenSpec tasks.md 有 ${environmentPending.length + releasePending.length} 个环境/发布前置,但执行结果未记录 releasePrerequisites`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return { localPending, environmentPending, releasePending };
|
|
270
|
+
}
|
|
271
|
+
const parent = path.dirname(current);
|
|
272
|
+
if (parent === current) break;
|
|
273
|
+
current = parent;
|
|
274
|
+
}
|
|
275
|
+
fail('SDD 托管任务未找到 OpenSpec tasks.md');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isAcceptedEvidenceCommand(command) {
|
|
279
|
+
if (command.exitCode === 0) return true;
|
|
280
|
+
if (command.exitCode !== 1 && command.exitCode !== 2) return false;
|
|
281
|
+
return (
|
|
282
|
+
/^(?:\s*)(?:grep|rg|pgrep|lsof|ls|ss|netstat|git\s+(?:status|diff)|find)\b/i.test(
|
|
283
|
+
command.command,
|
|
284
|
+
) &&
|
|
285
|
+
/(?:none|empty|no\s+(?:match|orphan|process|change|listener|such\s+file)|not found|does not exist|无匹配|无残留|未发现|没有|不存在|空)/i.test(
|
|
286
|
+
command.result,
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function verificationCommandCovered(expected, actual) {
|
|
292
|
+
if (expected === actual) return true;
|
|
293
|
+
const expectedTests = mavenTestSelectors(expected);
|
|
294
|
+
const actualTests = mavenTestSelectors(actual);
|
|
295
|
+
return (
|
|
296
|
+
expectedTests.length > 0 &&
|
|
297
|
+
actualTests.length > 0 &&
|
|
298
|
+
expectedTests.every((test) => actualTests.includes(test))
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function mavenTestSelectors(command) {
|
|
303
|
+
if (!/(?:^|\s)(?:\.\/)?mvn(?:\s|$)/.test(command)) return [];
|
|
304
|
+
const match = command.match(/-Dtest=([^\s]+)/);
|
|
305
|
+
return match?.[1].split(',').filter(Boolean) ?? [];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function taskEvidencePathExists(task, evidencePath) {
|
|
309
|
+
if (path.isAbsolute(evidencePath)) return fs.existsSync(evidencePath);
|
|
310
|
+
const bases = [
|
|
311
|
+
...(findTaskDirectory(task) ? [findTaskDirectory(task)] : []),
|
|
312
|
+
...(task.taskPrompt?.originalPath
|
|
313
|
+
? [path.dirname(task.taskPrompt.originalPath)]
|
|
314
|
+
: []),
|
|
315
|
+
task.projectRoot,
|
|
316
|
+
...(task.relatedProjectRoots ?? []),
|
|
317
|
+
];
|
|
318
|
+
return bases.some((base) => fs.existsSync(path.resolve(base, evidencePath)));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function findTaskDirectory(task) {
|
|
322
|
+
if (!task.taskPrompt?.originalPath) return null;
|
|
323
|
+
let current = path.dirname(path.resolve(task.taskPrompt.originalPath));
|
|
324
|
+
const roots = [task.projectRoot, ...(task.relatedProjectRoots ?? [])].map(
|
|
325
|
+
(root) => path.resolve(root),
|
|
326
|
+
);
|
|
327
|
+
while (roots.some((root) => current.startsWith(root))) {
|
|
328
|
+
if (fs.existsSync(path.join(current, 'tasks.md'))) return current;
|
|
329
|
+
const parent = path.dirname(current);
|
|
330
|
+
if (parent === current) break;
|
|
331
|
+
current = parent;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function parseCompletionTask(line, index) {
|
|
337
|
+
const completed = /^\s*- \[[xX]\]/.test(line);
|
|
338
|
+
const text = line.replace(/^\s*- \[[ xX]\]\s*/, '').trim();
|
|
339
|
+
const match = text.match(/^([A-Za-z]*\d+(?:\.\d+)*|[A-Z][A-Z0-9_-]+)\b/);
|
|
340
|
+
const category = /\[environment_required\]/i.test(text)
|
|
341
|
+
? 'environment_required'
|
|
342
|
+
: /\[release_required\]/i.test(text)
|
|
343
|
+
? 'release_required'
|
|
344
|
+
: 'local_required';
|
|
345
|
+
return { taskId: match?.[1] ?? `task-${index + 1}`, completed, category };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function verifyDeliveryStatus(status, completion) {
|
|
349
|
+
if (status === 'environment_validation_blocked' && completion.environmentPending.length === 0) {
|
|
350
|
+
fail('environment_validation_blocked 状态没有待完成环境验收');
|
|
351
|
+
}
|
|
352
|
+
if (status === 'local_delivery_ready' && completion.environmentPending.length > 0) {
|
|
353
|
+
fail('local_delivery_ready 状态仍有环境验收未完成');
|
|
140
354
|
}
|
|
141
|
-
if (
|
|
142
|
-
|
|
355
|
+
if (
|
|
356
|
+
status === 'release_ready' &&
|
|
357
|
+
completion.environmentPending.length + completion.releasePending.length > 0
|
|
358
|
+
) {
|
|
359
|
+
fail('release_ready 状态仍有环境或发布任务未完成');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function verifyExecutorEvidence(task, result, review, runDir) {
|
|
364
|
+
if (!['ready_for_review', 'blocked'].includes(result.status)) {
|
|
365
|
+
fail('最终执行结果既不可检查也不是诚实阻塞状态');
|
|
366
|
+
}
|
|
367
|
+
if (!Array.isArray(result.commands)) fail('最终执行结果缺少命令证据');
|
|
368
|
+
if (result.status === 'blocked' && review.result !== 'pass') {
|
|
369
|
+
fail('执行者阻塞结果未经监督者判定为非阻断发布前置');
|
|
370
|
+
}
|
|
371
|
+
if (result.status === 'ready_for_review' && result.blockers?.length > 0) {
|
|
372
|
+
fail('可检查执行结果仍含阻塞项');
|
|
143
373
|
}
|
|
144
374
|
if (task.profile === 'engineering' || task.profile === 'sdd') {
|
|
145
|
-
const
|
|
146
|
-
|
|
375
|
+
const successful = loadExecutorCommands(runDir)
|
|
376
|
+
.filter((item) => item.exitCode === 0);
|
|
377
|
+
const categories = new Set(
|
|
378
|
+
successful.flatMap((item) => verificationCategories(item.command)),
|
|
379
|
+
);
|
|
380
|
+
const required = requiredVerificationCategories(projectRoot);
|
|
381
|
+
if (categories.size < required) fail('工程任务缺少至少两类成功验证证据');
|
|
147
382
|
}
|
|
148
383
|
}
|
|
149
384
|
|
|
385
|
+
function requiredVerificationCategories(root) {
|
|
386
|
+
const packageFile = path.join(root, 'package.json');
|
|
387
|
+
if (!fs.existsSync(packageFile)) return 2;
|
|
388
|
+
try {
|
|
389
|
+
const scripts = JSON.parse(fs.readFileSync(packageFile, 'utf8')).scripts ?? {};
|
|
390
|
+
const hasTest = typeof scripts.test === 'string';
|
|
391
|
+
const hasRunnableTarget = ['build', 'start', 'dev', 'serve']
|
|
392
|
+
.some((name) => typeof scripts[name] === 'string');
|
|
393
|
+
return hasTest && !hasRunnableTarget ? 1 : 2;
|
|
394
|
+
} catch {
|
|
395
|
+
return 2;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function loadExecutorCommands(runDir) {
|
|
400
|
+
return fs.readdirSync(runDir)
|
|
401
|
+
.filter((entry) => /^executor-result-\d+\.json$/.test(entry))
|
|
402
|
+
.flatMap((entry) => {
|
|
403
|
+
const result = JSON.parse(
|
|
404
|
+
fs.readFileSync(path.join(runDir, entry), 'utf8'),
|
|
405
|
+
);
|
|
406
|
+
return Array.isArray(result.commands) ? result.commands : [];
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
150
410
|
function verificationCategories(command = '') {
|
|
151
411
|
const value = command.toLowerCase();
|
|
152
412
|
const categories = [];
|
|
@@ -155,6 +415,7 @@ function verificationCategories(command = '') {
|
|
|
155
415
|
|| /mvn(?:\s+[^\s]+)*\s+test/.test(value)
|
|
156
416
|
|| /gradle\w*\s+test/.test(value)) categories.push('test');
|
|
157
417
|
if (/(?:npm|pnpm|yarn)\s+(?:run\s+)?build/.test(value)
|
|
418
|
+
|| /(?:^|[\s/])(?:vite|vue-cli-service)\s+build(?:\s|$)/.test(value)
|
|
158
419
|
|| /(^|\s)(tsc|make|cmake|compile)(\s|$)/.test(value)
|
|
159
420
|
|| /mvn(?:\s+[^\s]+)*\s+(?:compile|package|verify)/.test(value)
|
|
160
421
|
|| /gradle\w*\s+(?:build|assemble)/.test(value)
|
|
@@ -6,98 +6,12 @@ set -u
|
|
|
6
6
|
ROLE="${SUPERFLOW_MANAGED_ROLE:-}"
|
|
7
7
|
[ -n "$ROLE" ] || exit 0
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
exit 2
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
case "$ROLE" in
|
|
15
|
-
supervisor|executor) ;;
|
|
16
|
-
*) block "检测到非法托管角色,按失败关闭。" ;;
|
|
17
|
-
esac
|
|
18
|
-
|
|
19
|
-
INPUT=""
|
|
20
|
-
if [ ! -t 0 ]; then
|
|
21
|
-
INPUT=$(cat 2>/dev/null || true)
|
|
22
|
-
fi
|
|
23
|
-
[ -n "$INPUT" ] || block "Hook 没有收到工具调用数据,按失败关闭。"
|
|
24
|
-
|
|
25
|
-
VALID_JSON=$(printf '%s' "$INPUT" | python3 -c "
|
|
26
|
-
import json, sys
|
|
27
|
-
try:
|
|
28
|
-
value = json.load(sys.stdin)
|
|
29
|
-
print('yes' if isinstance(value, dict) else 'no')
|
|
30
|
-
except Exception:
|
|
31
|
-
print('no')
|
|
32
|
-
" 2>/dev/null)
|
|
33
|
-
[ "$VALID_JSON" = "yes" ] || block "Hook 输入不是有效 JSON,按失败关闭。"
|
|
34
|
-
|
|
35
|
-
read_field() {
|
|
36
|
-
local field="$1"
|
|
37
|
-
printf '%s' "$INPUT" | python3 -c "
|
|
38
|
-
import json, sys
|
|
39
|
-
try:
|
|
40
|
-
data = json.load(sys.stdin)
|
|
41
|
-
except Exception:
|
|
42
|
-
print('')
|
|
43
|
-
raise SystemExit
|
|
44
|
-
tool_input = data.get('tool_input', {})
|
|
45
|
-
print(tool_input.get('$field') or '')
|
|
46
|
-
" 2>/dev/null
|
|
47
|
-
}
|
|
9
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
10
|
+
GUARD="$SCRIPT_DIR/superflow-managed-command-guard.py"
|
|
48
11
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
data = json.load(sys.stdin)
|
|
53
|
-
except Exception:
|
|
54
|
-
print('')
|
|
55
|
-
raise SystemExit
|
|
56
|
-
print(data.get('tool_name') or data.get('tool') or '')
|
|
57
|
-
" 2>/dev/null)
|
|
58
|
-
FILE_PATH=$(read_field file_path)
|
|
59
|
-
[ -n "$FILE_PATH" ] || FILE_PATH=$(read_field path)
|
|
60
|
-
[ -n "$FILE_PATH" ] || FILE_PATH=$(read_field file)
|
|
61
|
-
COMMAND=$(read_field command)
|
|
62
|
-
[ -n "$COMMAND" ] || COMMAND=$(read_field cmd)
|
|
63
|
-
|
|
64
|
-
normalize() {
|
|
65
|
-
printf '%s' "$1" | sed 's|\\|/|g'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
FILE_PATH=$(normalize "$FILE_PATH")
|
|
69
|
-
COMMAND=$(printf '%s' "$COMMAND" | tr '\n' ' ')
|
|
70
|
-
|
|
71
|
-
if [ "$ROLE" = "supervisor" ]; then
|
|
72
|
-
case "$TOOL_NAME" in
|
|
73
|
-
Edit|Write|MultiEdit|NotebookEdit|apply_patch)
|
|
74
|
-
block "监督角色只允许检查,禁止修改目标文件。"
|
|
75
|
-
;;
|
|
76
|
-
esac
|
|
77
|
-
if [ -n "$COMMAND" ] && printf '%s' "$COMMAND" | grep -Eiq \
|
|
78
|
-
'(^|[[:space:]])(rm|mv|cp|mkdir|touch|chmod|chown)([[:space:]]|$)|git[[:space:]]+(add|commit|push|tag|reset|checkout|restore)|npm[[:space:]]+publish|apply_patch|sed[[:space:]]+-i|(^|[^>])>[>|[:space:]]|tee[[:space:]]'; then
|
|
79
|
-
block "监督角色的 Bash 仅允许只读检查,检测到可能写入的命令。"
|
|
80
|
-
fi
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
if [ "$ROLE" = "executor" ]; then
|
|
84
|
-
case "$TOOL_NAME" in
|
|
85
|
-
Edit|Write|MultiEdit|NotebookEdit|apply_patch)
|
|
86
|
-
[ -n "$FILE_PATH" ] || block "写入工具缺少目标路径,按失败关闭。"
|
|
87
|
-
;;
|
|
88
|
-
esac
|
|
89
|
-
if [ -n "$FILE_PATH" ] && printf '%s' "$FILE_PATH" | grep -Eq \
|
|
90
|
-
'(^|/)\.superflow/tasks/'; then
|
|
91
|
-
block "执行角色禁止修改托管状态、事件、Prompt 和过程报告。"
|
|
92
|
-
fi
|
|
93
|
-
if [ -n "$COMMAND" ] && printf '%s' "$COMMAND" | grep -Eq \
|
|
94
|
-
'\.superflow/tasks/'; then
|
|
95
|
-
block "执行角色的 Bash 禁止直接访问托管运行目录,请使用提示词中提供的只读证据。"
|
|
96
|
-
fi
|
|
97
|
-
if [ -n "$COMMAND" ] && printf '%s' "$COMMAND" | grep -Eiq \
|
|
98
|
-
'git[[:space:]]+(add|commit|push|tag)|npm[[:space:]]+publish|dangerously-(bypass|skip)|kubectl[[:space:]]+(apply|delete)|helm[[:space:]]+(install|upgrade)|DROP[[:space:]]+(DATABASE|TABLE)|TRUNCATE[[:space:]]+TABLE'; then
|
|
99
|
-
block "执行角色禁止自动 Git 提交/推送、发布、部署、绕过沙箱或高破坏数据库操作。"
|
|
100
|
-
fi
|
|
12
|
+
if [ ! -f "$GUARD" ]; then
|
|
13
|
+
echo "[Superflow 托管任务门禁] 缺少命令门禁脚本,按失败关闭:$GUARD" >&2
|
|
14
|
+
exit 2
|
|
101
15
|
fi
|
|
102
16
|
|
|
103
|
-
|
|
17
|
+
exec python3 "$GUARD"
|
|
@@ -23,6 +23,19 @@ any full SDD document or implementation prompt is generated.
|
|
|
23
23
|
- Do not freeze a feature from product text or understand-anything output alone
|
|
24
24
|
when code impact is relevant. Current source, Mapper/XML, database samples,
|
|
25
25
|
API contracts, or real consumer entry points must confirm the design.
|
|
26
|
+
- 对依赖存量系统行为、表关系或调用链的功能,冻结前必须生成
|
|
27
|
+
`source-code-audit.md` 的“源码事实冻结卡”:
|
|
28
|
+
`业务结论 | understand定位 | 数据模型 | 所有写入方 | 真实用户入口 |
|
|
29
|
+
当前调用方 | 遗留冲突 | DB是否必查 | 结论等级 | owner决策`。
|
|
30
|
+
understand-anything 只允许作为定位器;每项结论必须回到源码、Mapper/SQL、
|
|
31
|
+
当前调用方或只读 DB 证据。
|
|
32
|
+
- 证据必须分类为 `current`、`legacy`、`unmounted`、`data-model-only`、
|
|
33
|
+
`owner-confirmed` 或 `blocked`。看到 `List`、`orderIds`、`batchInsert`、
|
|
34
|
+
一对多关系表,而真实 UI/小程序/H5 只提交单笔时,必须写“冲突审计”,禁止
|
|
35
|
+
直接推导为现行批量业务。
|
|
36
|
+
- 向用户提问前,必须在“提问资格门禁”记录源码、Mapper/SQL、前端/小程序/H5
|
|
37
|
+
调用方、sibling repo 检索以及 DB 核查或跳过原因。能从仓库或只读数据查明的
|
|
38
|
+
事实不得转嫁给用户;只有 owner 意图决策才允许提问。
|
|
26
39
|
- When the feature depends on existing code relationships, field ownership,
|
|
27
40
|
table joins, data permissions, status transitions, or upstream/downstream
|
|
28
41
|
calls, verify the relationship before freezing with current evidence:
|
|
@@ -81,6 +94,7 @@ Create or update these files in the active OpenSpec change directory:
|
|
|
81
94
|
- `ui-contract.md`: page fields, controls, buttons, list columns, dropdowns, API/DTO/DB mapping.
|
|
82
95
|
- `gap-analysis.md`: UI/document/source gaps and over-design risks.
|
|
83
96
|
- `spec-freeze-review.md`: freeze card for the current feature.
|
|
97
|
+
- `source-code-audit.md`: 源码事实冻结卡、证据分类、冲突审计和提问资格门禁。
|
|
84
98
|
- Platform impact evidence in one of the above documents: discovery method,
|
|
85
99
|
understand-anything index status when available, analyzed platform scope,
|
|
86
100
|
impacted modules/interfaces/tables, regression risks, source/DB/API
|
|
@@ -21,6 +21,12 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
21
21
|
|
|
22
22
|
## Preconditions
|
|
23
23
|
|
|
24
|
+
- When the change modifies a database schema, index, constraint, seed data, or
|
|
25
|
+
historical data, `release-sql.md` must already contain the complete executable
|
|
26
|
+
release SQL, an exact target `.sql` path, `copy_policy: verbatim`, and a valid
|
|
27
|
+
`sql_sha256`. Source-level HOW design must not defer SQL design to an
|
|
28
|
+
implementation prompt.
|
|
29
|
+
|
|
24
30
|
- `proposal.md`, `api.md`, `design.md`, `tasks.md`, `tests.md`,
|
|
25
31
|
`traceability-matrix.md`, `sdd-quality-gate.md`, and `test-report.md` exist.
|
|
26
32
|
- `.sdd/state.yaml` exists and is in `phase: design`. If still `docs`, run:
|
|
@@ -38,15 +44,27 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
38
44
|
|
|
39
45
|
1. Read `.sdd/handoff/sdd-context.md` plus original `api.md`, `design.md`,
|
|
40
46
|
`tasks.md`, and `tests.md`.
|
|
41
|
-
2. If
|
|
47
|
+
2. If the change has any schema, index, constraint, seed-data, or historical
|
|
48
|
+
migration impact, verify `release-sql.md` before source-level HOW design:
|
|
49
|
+
- front matter sets `database_change: true`, an exact `target_sql_path`,
|
|
50
|
+
`copy_policy: verbatim`, and the normalized SQL payload `sql_sha256`;
|
|
51
|
+
- exactly one `sql` block contains the complete executable release script,
|
|
52
|
+
including prechecks, DDL/DML, and post-verification required by the
|
|
53
|
+
contract;
|
|
54
|
+
- `design.md`, `tasks.md`, and `sdd-quality-gate.md` reference the artifact
|
|
55
|
+
and prohibit implementation agents from editing its SQL;
|
|
56
|
+
- unresolved SQL types, defaults, indexes, migration rules, or target paths
|
|
57
|
+
return to `$superflow-docs`. They must not be deferred to
|
|
58
|
+
`$superflow-implement`.
|
|
59
|
+
3. If design discussion is still evolving, maintain
|
|
42
60
|
`.sdd/handoff/brainstorm-summary.md` with confirmed, candidate, pending, and
|
|
43
61
|
rejected items. Do not create final technical design from memory only.
|
|
44
|
-
|
|
62
|
+
4. Use Superpowers `brainstorming` or equivalent deep design reasoning to
|
|
45
63
|
decide source-level HOW.
|
|
46
|
-
|
|
64
|
+
5. Write:
|
|
47
65
|
`docs/superpowers/specs/YYYY-MM-DD-<change-id>-technical-design.md`
|
|
48
66
|
using `../superflow-pipeline/references/superpower-technical-design-template.md`.
|
|
49
|
-
|
|
67
|
+
6. Complete `复杂度减法评审` before expanding source-level HOW. For every new
|
|
50
68
|
table, field, API, Service/component, abstraction, cache, async/MQ/event
|
|
51
69
|
flow, scheduled job, or compatibility layer, prove existing reuse options,
|
|
52
70
|
necessity, and why the simplest direct implementation is insufficient.
|
|
@@ -55,7 +73,7 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
55
73
|
duplicate DTO/API layers, derivable persistence, premature caches, and
|
|
56
74
|
async compensation without measured need. Record the retained and removed
|
|
57
75
|
counts; unresolved simpler alternatives keep the design blocked.
|
|
58
|
-
|
|
76
|
+
7. For cross-repository, service-to-service, SDK, MQ, scheduler, device,
|
|
59
77
|
callback, third-party, mini-program, gateway, or adapter changes, include
|
|
60
78
|
`Architecture Boundary And Call Direction`. Prove owner module, call
|
|
61
79
|
direction, existing entry/exit points, proposed entry/exit points, evidence
|
|
@@ -63,7 +81,7 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
63
81
|
adapter/protocol translator/device gateway into a business-entry
|
|
64
82
|
orchestrator, stop and return to `$superflow-docs` unless the OpenSpec/SDD
|
|
65
83
|
contract explicitly grants that ownership with approval evidence.
|
|
66
|
-
|
|
84
|
+
8. For third-party platforms/tools, SDKs, MQ/Kafka, callbacks, payment
|
|
67
85
|
gateways, cloud services, or other external integrations, include
|
|
68
86
|
`External Integration Configuration And Deployment Contract`. Inventory
|
|
69
87
|
every endpoint, app/tenant/project ID, Topic, Tag, Consumer Group,
|
|
@@ -75,7 +93,7 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
75
93
|
auto-creation or pre-existing resources must not be treated as proof that
|
|
76
94
|
production will provision them. Missing production provisioning evidence
|
|
77
95
|
blocks design completion.
|
|
78
|
-
|
|
96
|
+
9. For concurrent requests, batch issue/activation/renewal, duplicate callbacks,
|
|
79
97
|
duplicate consumption, or repeated external delivery, include
|
|
80
98
|
`Concurrency And Idempotency Ownership`. Use a stable business idempotency
|
|
81
99
|
key and an application-layer atomic claim in a short transaction; persist
|
|
@@ -85,17 +103,24 @@ Superpowers own source-level HOW without weakening OpenSpec/SDD contracts.
|
|
|
85
103
|
optional fallback with proven natural uniqueness, historical cleanup,
|
|
86
104
|
NULL/soft-delete semantics, and a conflict contract. Check-then-insert,
|
|
87
105
|
process-local locks, and random IDs alone are blocked.
|
|
88
|
-
|
|
106
|
+
When the selected design is version-based optimistic locking, record the
|
|
107
|
+
exact database atomic compare-and-set statement and affected-row decision:
|
|
108
|
+
the update predicate contains both the stable row identity and expected
|
|
109
|
+
version; a zero-row result distinguishes missing data from version conflict.
|
|
110
|
+
The TDD plan references the `tests.md` concurrent stale-version case proving
|
|
111
|
+
one winner, one conflict, and one increment. Do not defer this decision to
|
|
112
|
+
the managed Executor prompt.
|
|
113
|
+
10. For field/status/enum/sync changes, include `Field And Status Reverse
|
|
89
114
|
Impact` and prove writers, readers, filters, derived sync paths, consumers,
|
|
90
115
|
and tests. Direct setter-only design is blocked.
|
|
91
|
-
|
|
116
|
+
11. For third-party, SDK, BEM/parking, payment/refund, financial display,
|
|
92
117
|
source/origin, status sync, or external dictionary fields, include
|
|
93
118
|
`External Enum Binding`. Prove local source fields/enums, external fields,
|
|
94
119
|
external enum values, display/business/financial meaning, source evidence,
|
|
95
120
|
owner confirmation, unresolved handling, and test evidence. A request
|
|
96
121
|
succeeding or a field being non-null is not enough; unconfirmed values are
|
|
97
122
|
blockers and must be returned to `$superflow-docs` or the user.
|
|
98
|
-
|
|
123
|
+
12. For amount, fee, discount, deduction, refund, sharing, payment, invoice,
|
|
99
124
|
balance, electricity fee, service fee, package settlement, proration,
|
|
100
125
|
allocation, reconciliation, or financial display changes,
|
|
101
126
|
include `Money Precision Boundary`. Prove calculation-state fields,
|
|
@@ -114,12 +139,12 @@ Impact` and prove writers, readers, filters, derived sync paths, consumers,
|
|
|
114
139
|
level and policy source, deterministic residual strategy with stable
|
|
115
140
|
tie-breaker, and positive/zero/negative evidence. For FX changes, freeze the
|
|
116
141
|
directional rate metadata and one canonical conversion path.
|
|
117
|
-
|
|
142
|
+
13. Record state:
|
|
118
143
|
```bash
|
|
119
144
|
../superflow-pipeline/scripts/superflow-state.sh set <change-dir> technical_design docs/superpowers/specs/YYYY-MM-DD-<change-id>-technical-design.md
|
|
120
145
|
../superflow-pipeline/scripts/superflow-state.sh set <change-dir> design_doc design.md
|
|
121
146
|
```
|
|
122
|
-
|
|
147
|
+
14. Update `design.md` with `Superpowers Technical Design Handoff`, update
|
|
123
148
|
`sdd-quality-gate.md` with the technical design path and hash, then run
|
|
124
149
|
`../superflow-pipeline/scripts/superflow-handoff.sh <change-dir> --refresh`.
|
|
125
150
|
If the guard reports a stale or missing hash, record the printed hash in
|
|
@@ -127,7 +152,7 @@ Impact` and prove writers, readers, filters, derived sync paths, consumers,
|
|
|
127
152
|
`--refresh` again. Do not set `design_doc` to the Superpowers document;
|
|
128
153
|
`design_doc` is the OpenSpec/SDD contract design, while `technical_design`
|
|
129
154
|
is the Superpowers HOW document.
|
|
130
|
-
|
|
155
|
+
15. Run:
|
|
131
156
|
|
|
132
157
|
```bash
|
|
133
158
|
../superflow-pipeline/scripts/superflow-yaml-validate.sh <change-dir>
|