kld-sdd 2.6.11 → 2.6.13
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 +20 -0
- package/kld-sdd-guide.html +429 -159
- package/lib/hook-gate-core.js +41 -5
- package/lib/init.js +101 -95
- package/package.json +5 -2
- package/skywalk-sdd/index.cjs +2668 -415
- package/skywalk-sdd/metrics-v3.cjs +1153 -0
- package/skywalk-sdd/ontology/archive-package.cjs +114 -3
- package/skywalk-sdd/ontology/resolve-spec-root.cjs +20 -80
- package/skywalk-sdd/ontology/working-artifacts.cjs +2 -1
- package/templates/hooks/claude/hooks/sdd-apply-test-gate.cjs +39 -9
- package/templates/hooks/claude/hooks/sdd-post-tool.cjs +87 -8
- package/templates/hooks/claude/hooks/sdd-prompt.cjs +25 -5
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +39 -9
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +87 -9
- package/templates/hooks/codebuddy/hooks/sdd-prompt.cjs +24 -3
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +17 -16
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +22 -22
- package/templates/skills/kld-sdd/opsx-apply/implementer-prompt.md +12 -12
- package/templates/skills/kld-sdd/opsx-apply/reference.md +43 -42
- package/templates/skills/kld-sdd/opsx-apply/worktree-setup.md +3 -3
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +58 -20
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +6 -3
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +30 -22
- package/templates/skills/kld-sdd/opsx-check/checklist.md +7 -6
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +5 -5
- package/templates/skills/kld-sdd/opsx-design/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-design/reference.md +2 -2
- package/templates/skills/kld-sdd/opsx-explore/SKILL.md +5 -5
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +20 -12
- package/templates/skills/kld-sdd/opsx-propose/reference.md +31 -5
- package/templates/skills/kld-sdd/opsx-rules/SKILL.md +3 -3
- package/templates/skills/kld-sdd/opsx-rules/reference.md +3 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +6 -6
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-spec/reference.md +2 -2
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +14 -14
- package/templates/skills/kld-sdd/opsx-task/checklist.md +3 -3
- package/templates/skills/kld-sdd/opsx-task/reference.md +5 -5
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +11 -9
- package/templates/skills/kld-sdd/{opsx-tdd-anti-patterns → tdd-anti-patterns}/SKILL.md +4 -4
- package/templates/skills/kld-sdd/{opsx-tdd-anti-patterns → tdd-anti-patterns}/reference.md +4 -4
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/SKILL.md +13 -13
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/checklist.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/reference.md +12 -12
- package/templates/skills/kld-sdd/{opsx-tdd-metrics → tdd-metrics}/SKILL.md +3 -3
- package/templates/skills/kld-sdd/{opsx-tdd-metrics → tdd-metrics}/checklist.md +2 -2
- package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/SKILL.md +2 -2
- package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/parameterized-testing.md +1 -1
- package/templates/skills/kld-sdd/{opsx-tdd-review → tdd-review}/SKILL.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-review → tdd-review}/checklist.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/SKILL.md +3 -3
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/non-tdd-modules.md +1 -1
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/refactor-checklist.md +1 -1
- package/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +19 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/test-skeleton-telemetry.md +0 -19
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/cause-effect-clarity.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/clean-test-data.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/existing-test-awareness.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/given-when-then.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/good-test-qualities.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/mock-boundary.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/naming-conventions.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/no-logic-in-tests.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/one-test-one-scenario.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/prefer-public-apis.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/test-behaviors-not-methods.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/argument-matching.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/controller-test-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/domain-service-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/java-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/json-serialization.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/logging-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/post-generation/compilation-verification.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/post-generation/execution-verification.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/python/py-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/typescript/ts-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/controller-strategy.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/dag-generation-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/des-step-annotation.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/exception-path-coverage.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/green-scope-declaration.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/green-yagni-fence.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/multi-validation-split.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/task-type-definitions.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/tdd-strategy-selection.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/test-execution-gate.md +0 -0
|
@@ -74,6 +74,26 @@ function isRealTestDetails(testResults) {
|
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function isPassingTestDetails(testResults) {
|
|
78
|
+
if (!isRealTestDetails(testResults)) return false;
|
|
79
|
+
const failed = Number(testResults.failed);
|
|
80
|
+
const exitCode = Number(testResults.exit_code);
|
|
81
|
+
return (!Number.isFinite(failed) || failed === 0)
|
|
82
|
+
&& (!Number.isFinite(exitCode) || exitCode === 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isStrictTestEvidence(event, changeName) {
|
|
86
|
+
const testResults = event?.details?.test_results;
|
|
87
|
+
if (event?.type !== 'test_result' || event.change !== changeName) return false;
|
|
88
|
+
if (!event.run_id || !event.source || testResults?.evidence_tier !== 'strict') return false;
|
|
89
|
+
if (!['green', 'refactor', 'regression'].includes(testResults.tdd_phase)) return false;
|
|
90
|
+
if (!Number.isInteger(testResults.exit_code) || testResults.counts_known !== true) return false;
|
|
91
|
+
return event.result === 'success'
|
|
92
|
+
&& testResults.exit_code === 0
|
|
93
|
+
&& Number(testResults.failed) === 0
|
|
94
|
+
&& isPassingTestDetails(testResults);
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
function loadChangeEvents(projectRoot, changeName) {
|
|
78
98
|
const safe = safeChangeName(changeName);
|
|
79
99
|
const dir = path.join(projectRoot, 'skywalk-sdd', 'events', safe);
|
|
@@ -100,33 +120,36 @@ function loadChangeEvents(projectRoot, changeName) {
|
|
|
100
120
|
function hasRealTestExecution(projectRoot, changeName, capability, sinceTimestamp) {
|
|
101
121
|
const since = sinceTimestamp ? new Date(sinceTimestamp).getTime() : 0;
|
|
102
122
|
const events = loadChangeEvents(projectRoot, changeName);
|
|
123
|
+
let legacyEvidence = null;
|
|
103
124
|
|
|
104
125
|
for (const event of events) {
|
|
105
126
|
const ts = new Date(event.timestamp || 0).getTime();
|
|
106
127
|
if (since && ts < since) continue;
|
|
107
128
|
|
|
108
|
-
if (event.type === 'stage_end' && event.command === 'test' &&
|
|
109
|
-
|
|
110
|
-
return { kind: 'opsx-test', event };
|
|
129
|
+
if (event.type === 'stage_end' && event.command === 'test' && event.result === 'success') {
|
|
130
|
+
legacyEvidence ||= { kind: 'legacy-opsx-test', event };
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
if (event.type === 'test_result') {
|
|
114
134
|
const tr = event.details && event.details.test_results;
|
|
115
|
-
if (
|
|
116
|
-
return { kind: '
|
|
135
|
+
if (isStrictTestEvidence(event, changeName)) {
|
|
136
|
+
return { kind: 'strict-test-result', event };
|
|
137
|
+
}
|
|
138
|
+
if (event.result === 'success' && isPassingTestDetails(tr)) {
|
|
139
|
+
legacyEvidence ||= { kind: 'legacy-test-result', event };
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
if (event.type === 'task_update' && (event.command === 'apply' || !event.command)) {
|
|
121
144
|
const tr = event.details && event.details.test_results;
|
|
122
|
-
if (
|
|
145
|
+
if (event.result !== 'failure' && isPassingTestDetails(tr)) {
|
|
123
146
|
if (!capability || !event.capability || event.capability === capability) {
|
|
124
|
-
|
|
147
|
+
legacyEvidence ||= { kind: 'legacy-task-update', event };
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
151
|
}
|
|
129
|
-
return
|
|
152
|
+
return legacyEvidence;
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
function isApplyCompletionBash(command) {
|
|
@@ -210,9 +233,10 @@ function shouldGate(projectRoot, applyEvent) {
|
|
|
210
233
|
applyEvent.capability,
|
|
211
234
|
applyEvent.timestamp
|
|
212
235
|
);
|
|
213
|
-
if (evidence) {
|
|
236
|
+
if (evidence?.kind === 'strict-test-result') {
|
|
214
237
|
return null;
|
|
215
238
|
}
|
|
239
|
+
if (evidence) return { strategy, changeName: applyEvent.change, capability: applyEvent.capability, legacyEvidence: evidence };
|
|
216
240
|
return { strategy, changeName: applyEvent.change, capability: applyEvent.capability };
|
|
217
241
|
}
|
|
218
242
|
|
|
@@ -374,6 +398,11 @@ if (require.main === module) {
|
|
|
374
398
|
if (!gate) {
|
|
375
399
|
process.exit(0);
|
|
376
400
|
}
|
|
401
|
+
if (gate.legacyEvidence) {
|
|
402
|
+
recordWarning(projectRoot, { ...activeApply, test_strategy: gate.strategy }, 'legacy_test_evidence',
|
|
403
|
+
'仅检测到旧版测试证据;本次兼容放行,请补录包含 run-id、exit code、TDD phase 和严格身份字段的 test_result');
|
|
404
|
+
process.exit(0);
|
|
405
|
+
}
|
|
377
406
|
|
|
378
407
|
const reason = buildReason(gate.strategy, gate.changeName, gate.capability);
|
|
379
408
|
recordWarning(projectRoot, { ...activeApply, test_strategy: gate.strategy }, 'apply_test_missing', 'Apply test gate blocked: no unit test evidence');
|
|
@@ -391,5 +420,6 @@ module.exports = {
|
|
|
391
420
|
detectMissingTddRed,
|
|
392
421
|
hasExistingWarning,
|
|
393
422
|
isRealTestDetails,
|
|
423
|
+
isStrictTestEvidence,
|
|
394
424
|
requiresUnitTests,
|
|
395
425
|
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
6
7
|
const { execFileSync } = require('child_process');
|
|
7
|
-
const core = require('./hook-gate-core.cjs');
|
|
8
8
|
|
|
9
9
|
function latestActiveStage(projectRoot) {
|
|
10
10
|
const stateDir = path.join(projectRoot, 'skywalk-sdd', 'state');
|
|
@@ -26,19 +26,54 @@ function latestActiveStage(projectRoot) {
|
|
|
26
26
|
function inferResult(input) {
|
|
27
27
|
const response = input.tool_response || input.toolResponse || {};
|
|
28
28
|
if (typeof response.exit_code === 'number') {
|
|
29
|
-
return { result: response.exit_code === 0 ? 'success' : 'failure', exit_code_known: true };
|
|
29
|
+
return { result: response.exit_code === 0 ? 'success' : 'failure', exit_code_known: true, exit_code: response.exit_code };
|
|
30
30
|
}
|
|
31
31
|
if (typeof response.exitCode === 'number') {
|
|
32
|
-
return { result: response.exitCode === 0 ? 'success' : 'failure', exit_code_known: true };
|
|
32
|
+
return { result: response.exitCode === 0 ? 'success' : 'failure', exit_code_known: true, exit_code: response.exitCode };
|
|
33
33
|
}
|
|
34
34
|
if (typeof response.success === 'boolean') {
|
|
35
|
-
return { result: response.success ? 'success' : 'failure', exit_code_known: true };
|
|
35
|
+
return { result: response.success ? 'success' : 'failure', exit_code_known: true, exit_code: response.success ? 0 : 1 };
|
|
36
36
|
}
|
|
37
37
|
const out = String(response.stdout || response.stderr || '');
|
|
38
38
|
if (/BUILD FAILED|error TS\d+|ERR_/i.test(out)) {
|
|
39
|
-
return { result: 'failure', exit_code_known: false };
|
|
39
|
+
return { result: 'failure', exit_code_known: false, exit_code: null };
|
|
40
40
|
}
|
|
41
|
-
return { result: 'partial', exit_code_known: false };
|
|
41
|
+
return { result: 'partial', exit_code_known: false, exit_code: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseTestCounts(output) {
|
|
45
|
+
const text = String(output || '');
|
|
46
|
+
const tapPass = text.match(/#\s*pass(?:ed)?\s+(\d+)/i);
|
|
47
|
+
const tapFail = text.match(/#\s*fail(?:ed)?\s+(\d+)/i);
|
|
48
|
+
const tapSkip = text.match(/#\s*skip(?:ped)?\s+(\d+)/i);
|
|
49
|
+
if (tapPass || tapFail) {
|
|
50
|
+
return {
|
|
51
|
+
counts_known: true,
|
|
52
|
+
passed: Number(tapPass?.[1] || 0),
|
|
53
|
+
failed: Number(tapFail?.[1] || 0),
|
|
54
|
+
skipped: Number(tapSkip?.[1] || 0),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const summary = text.match(/Tests?\s+.*?(\d+)\s+passed(?:.*?(\d+)\s+failed)?(?:.*?(\d+)\s+skipped)?/i);
|
|
58
|
+
if (summary) {
|
|
59
|
+
return {
|
|
60
|
+
counts_known: true,
|
|
61
|
+
passed: Number(summary[1] || 0),
|
|
62
|
+
failed: Number(summary[2] || 0),
|
|
63
|
+
skipped: Number(summary[3] || 0),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { counts_known: false };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stableRunId(input, activeStage, command) {
|
|
70
|
+
const explicit = input.tool_use_id || input.toolUseId || input.tool_call_id || input.toolCallId;
|
|
71
|
+
if (explicit) return String(explicit);
|
|
72
|
+
const response = input.tool_response || input.toolResponse || {};
|
|
73
|
+
return `hook-${crypto.createHash('sha256')
|
|
74
|
+
.update(JSON.stringify([activeStage.event_id || '', activeStage.session_id || '', command, response]))
|
|
75
|
+
.digest('hex')
|
|
76
|
+
.slice(0, 20)}`;
|
|
42
77
|
}
|
|
43
78
|
|
|
44
79
|
function inferRecord(command) {
|
|
@@ -69,6 +104,34 @@ function recordTelemetry(projectRoot, activeStage, record, result, exitCodeKnown
|
|
|
69
104
|
const toolInput = (input && (input.tool_input || input.toolInput)) || {};
|
|
70
105
|
const toolResponse = (input && (input.tool_response || input.toolResponse)) || {};
|
|
71
106
|
const details = { [record.detailsKey]: { ...record.details } };
|
|
107
|
+
let strictTestEvent = false;
|
|
108
|
+
let runId = null;
|
|
109
|
+
if (record.type === 'test_result') {
|
|
110
|
+
const output = String(toolResponse.stdout || '') + '\n' + String(toolResponse.stderr || '');
|
|
111
|
+
const counts = parseTestCounts(output);
|
|
112
|
+
const exitCode = typeof toolResponse.exit_code === 'number'
|
|
113
|
+
? toolResponse.exit_code
|
|
114
|
+
: (typeof toolResponse.exitCode === 'number'
|
|
115
|
+
? toolResponse.exitCode
|
|
116
|
+
: (typeof toolResponse.success === 'boolean' ? (toolResponse.success ? 0 : 1) : null));
|
|
117
|
+
const tddPhase = toolInput.tdd_phase || input.tdd_phase || 'regression';
|
|
118
|
+
details.test_results = {
|
|
119
|
+
command: toolInput.command || record.details.command,
|
|
120
|
+
tdd_phase: tddPhase,
|
|
121
|
+
exit_code: exitCode,
|
|
122
|
+
counts_known: counts.counts_known,
|
|
123
|
+
...(counts.counts_known ? {
|
|
124
|
+
passed: counts.passed,
|
|
125
|
+
failed: counts.failed,
|
|
126
|
+
skipped: counts.skipped,
|
|
127
|
+
} : {}),
|
|
128
|
+
duration_ms: Number.isFinite(toolResponse.duration_ms) ? toolResponse.duration_ms : 0,
|
|
129
|
+
failure_type: result === 'failure' ? (exitCode == null ? 'infrastructure' : 'assertion') : 'none',
|
|
130
|
+
expected_failure: tddPhase === 'red' && result === 'failure' && exitCode != null,
|
|
131
|
+
};
|
|
132
|
+
strictTestEvent = exitCode != null && Boolean(activeStage.task_id);
|
|
133
|
+
runId = stableRunId(input || {}, activeStage, details.test_results.command);
|
|
134
|
+
}
|
|
72
135
|
if (record.type === 'build_result') {
|
|
73
136
|
details.build_results.success = result === 'success';
|
|
74
137
|
details.build_results.error_count = result === 'success' ? 0 : null;
|
|
@@ -82,7 +145,7 @@ function recordTelemetry(projectRoot, activeStage, record, result, exitCodeKnown
|
|
|
82
145
|
logCli,
|
|
83
146
|
'record',
|
|
84
147
|
`--type=${record.type}`,
|
|
85
|
-
`--command=${
|
|
148
|
+
`--command=${activeStage.command || activeStage.stage || 'unknown'}`,
|
|
86
149
|
`--project=${projectRoot}`,
|
|
87
150
|
`--change=${activeStage.change || 'general'}`,
|
|
88
151
|
`--agent=${activeStage.agent_type || 'codebuddy'}`,
|
|
@@ -91,14 +154,22 @@ function recordTelemetry(projectRoot, activeStage, record, result, exitCodeKnown
|
|
|
91
154
|
`--summary=CodeBuddy hook captured ${record.type}`,
|
|
92
155
|
`--details-json=${JSON.stringify(details)}`,
|
|
93
156
|
];
|
|
157
|
+
if (strictTestEvent) {
|
|
158
|
+
args.push(
|
|
159
|
+
'--strict',
|
|
160
|
+
`--run-id=${runId}`,
|
|
161
|
+
`--session-id=${activeStage.session_id || `stage-${activeStage.event_id}`}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
94
164
|
if (activeStage.capability) args.push(`--capability=${activeStage.capability}`);
|
|
95
165
|
if (activeStage.task_id) args.push(`--task-id=${activeStage.task_id}`);
|
|
96
|
-
if (activeStage.session_id) args.push(`--session-id=${activeStage.session_id}`);
|
|
166
|
+
if (!strictTestEvent && activeStage.session_id) args.push(`--session-id=${activeStage.session_id}`);
|
|
97
167
|
const run = runner || ((a) => execFileSync('node', a, { cwd: projectRoot, stdio: 'ignore' }));
|
|
98
168
|
run(args);
|
|
99
169
|
}
|
|
100
170
|
|
|
101
171
|
if (require.main === module) {
|
|
172
|
+
const core = require('./hook-gate-core.cjs');
|
|
102
173
|
const parsed = core.parseHookInput(core.readStdin(), { strict: false });
|
|
103
174
|
const input = core.normalizeHookInput(parsed.input || {});
|
|
104
175
|
const toolInput = input.tool_input || {};
|
|
@@ -120,4 +191,11 @@ if (require.main === module) {
|
|
|
120
191
|
}
|
|
121
192
|
}
|
|
122
193
|
|
|
123
|
-
module.exports = {
|
|
194
|
+
module.exports = {
|
|
195
|
+
inferResult,
|
|
196
|
+
inferRecord,
|
|
197
|
+
parseTestCounts,
|
|
198
|
+
stableRunId,
|
|
199
|
+
recordTelemetry,
|
|
200
|
+
latestActiveStage,
|
|
201
|
+
};
|
|
@@ -13,8 +13,27 @@ if (!/\/opsx[:|-]/.test(prompt)) {
|
|
|
13
13
|
process.exit(0);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function resolveSkywalkDir(dir) {
|
|
17
|
+
const direct = path.join(dir, 'skywalk-sdd');
|
|
18
|
+
if (fs.existsSync(path.join(direct, 'log.cjs')) || fs.existsSync(path.join(direct, 'log.js'))) {
|
|
19
|
+
return direct;
|
|
20
|
+
}
|
|
21
|
+
const hintFile = path.join(dir, '.sdd-spec-root');
|
|
22
|
+
if (fs.existsSync(hintFile)) {
|
|
23
|
+
const rel = String(fs.readFileSync(hintFile, 'utf8') || '').trim().split(/\r?\n/)[0].trim();
|
|
24
|
+
if (rel) {
|
|
25
|
+
const nested = path.join(dir, rel, 'skywalk-sdd');
|
|
26
|
+
if (fs.existsSync(path.join(nested, 'log.cjs')) || fs.existsSync(path.join(nested, 'log.js'))) {
|
|
27
|
+
return nested;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
function readActiveStages(projectRoot) {
|
|
17
|
-
const
|
|
35
|
+
const skywalk = resolveSkywalkDir(projectRoot);
|
|
36
|
+
const stateDir = skywalk ? path.join(skywalk, 'state') : path.join(projectRoot, 'skywalk-sdd', 'state');
|
|
18
37
|
if (!fs.existsSync(stateDir)) return [];
|
|
19
38
|
return fs.readdirSync(stateDir)
|
|
20
39
|
.filter((file) => file.endsWith('.json'))
|
|
@@ -35,8 +54,10 @@ const activeStages = readActiveStages(projectRoot)
|
|
|
35
54
|
|
|
36
55
|
const lines = [
|
|
37
56
|
'SDD Telemetry reminder:',
|
|
38
|
-
'-
|
|
39
|
-
'- Run skywalk-sdd/log.cjs
|
|
57
|
+
'- Resolve the exact change key first; every standard OPSX start/end command must include --change=<change-key>.',
|
|
58
|
+
'- Run node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" start before the OPSX stage work begins.',
|
|
59
|
+
'- Run node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" end with the same change, stage, session and event identity before stopping.',
|
|
60
|
+
'- change=general is reserved for explicitly allowed explore events and must later use scope_link for historical attribution.',
|
|
40
61
|
'- Hooks are only an enhancement; OPSX skill instructions remain authoritative.',
|
|
41
62
|
];
|
|
42
63
|
|
|
@@ -27,18 +27,19 @@ allowed-tools:
|
|
|
27
27
|
> - ⛔ **隔离红线**:绝对禁止加载同级其他 Capability 的文档
|
|
28
28
|
|
|
29
29
|
> **🖥️ 跨平台执行规则**
|
|
30
|
-
> - **写代码**:cwd 保持在 Git 根(含 `src/`);**文档 / telemetry**:`--project` 指向 SDD 包裹包(`node skywalk-sdd/ontology/cli.cjs spec-root` 可查看)。
|
|
31
|
-
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包)。
|
|
30
|
+
> - **写代码**:cwd 保持在 Git 根(含 `src/`);**文档 / telemetry**:`--project` 指向 SDD 包裹包(`node "$(cat .sdd-spec-root)/skywalk-sdd/ontology/cli.cjs" spec-root` 可查看)。
|
|
31
|
+
> - `openspec` 命令:优先 `node "$(cat .sdd-spec-root)/skywalk-sdd/openspec-shim.cjs" list`(自动 cd 到包裹包)。
|
|
32
32
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
33
33
|
> - ${SHELL_GUIDANCE}
|
|
34
34
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
35
35
|
> **📊 Telemetry(必做,不得跳过)**
|
|
36
36
|
> - 阶段开始 / 阶段结束 / `task_update` / `ai_adoption_review` / `worktree_finish` 的**完整命令模板**见 `./reference.md`「📊 Telemetry 命令模板」。
|
|
37
37
|
> - 不得跳过任何 telemetry 记录;`task_update` 的 `--task-id=<TASK-ID>` 必须替换为实际任务 ID,否则 E4 指标无法计算;TDD 测试骨架任务须在 `--details-json` 带 `"task_kind":"test-skeleton"`(P3,详见 `./reference.md`)。
|
|
38
|
-
> - 较大的 `--details-json` 负载可先写入文件,再通过 `--details-file=skywalk-sdd/state/<变更名称>-<type>.json` 传递(例如 `task_update` 对应 `<变更名称>-task-update.json`)。
|
|
38
|
+
> - 较大的 `--details-json` 负载可先写入文件,再通过 `--details-file="$(cat .sdd-spec-root)/skywalk-sdd/state/<变更名称>-<type>.json"` 传递(例如 `task_update` 对应 `<变更名称>-task-update.json`)。
|
|
39
39
|
> - `task_update` 记录成功后会自动调用 `check-task` 更新 `tasks.md` 中的 checkbox;录入成功后,agent 可以通过 `check-task` 命令验证该 checkbox 已更新。
|
|
40
40
|
> - **⚠️ P1-2 ai_adoption_review 必填 ai_diff**:记录 AI 产出快照时,`--details-json` 必须含 `ai_diff.files_changed`(即使=0)与 `ai_diff.files`(产出文件路径数组,非空),**不得只发 `assertions`**。`vcs_mode=readonly` 时 `ai_diff.added_lines` 不得为 null——用只读 `git diff --numstat HEAD` 取值(apply Git 只读策略允许);`vcs_mode=no-git` 时可填 null。工具侧已加记录时硬校验:`files` 空数组或 `readonly` 下 `added_lines=null` 将 **拒绝记录**(与 conformance_review 校验对称)。缺失 `ai_diff` 会导致 report 变更文件数为 null(工具侧已加 `assertions[].files` 兜底,但 `ai_diff` 是主数据源)。完整模板见 `./reference.md`「§5.1」。
|
|
41
|
-
> -
|
|
41
|
+
> - **严格证据链**:一次真实测试执行只写一条严格 `test_result`;任务完成事件只通过 `test_event_id` 引用成功的 green/refactor/regression 测试,并明确 `tdd_required=true/false`,不复制测试计数。测试不适用时必须写明原因。完整 schema 见 `./reference.md`。
|
|
42
|
+
> - **process_note 必记**:用户决策、范围变化、API/模型/测试异常及恢复动作必须写严格 `process_note`;范围、模式、测试策略选择统一用 `kind=user_decision` 并标注 `decision_type`。
|
|
42
43
|
|
|
43
44
|
> **🔒 Git 策略(只读增强,不改变开发流)**
|
|
44
45
|
> - Git 只作为可选度量数据源,不是 apply 前置条件。
|
|
@@ -205,29 +206,29 @@ g. **继续下一个层级** — 重新检查 DAG,找出依赖已满足的下
|
|
|
205
206
|
3. **GREEN**:读取 RED 失败原因 → 执行 GREEN Scope 声明 → 逐条确认 YAGNI 围栏 → 写最少代码让测试通过 → 禁止捆绑未测试代码
|
|
206
207
|
4. **GREEN Scope 门禁**:Verify GREEN 之后,检查生产代码无越界逻辑分支(属于后续 RED 的行为 → 删除)
|
|
207
208
|
5. **REFACTOR**:在测试全绿状态下重构 → 运行全部测试确认仍绿
|
|
208
|
-
6. **可选:TDD 审查子代理**:GREEN 任务完成后,可派发独立审查子代理执行 `
|
|
209
|
+
6. **可选:TDD 审查子代理**:GREEN 任务完成后,可派发独立审查子代理执行 `tdd-review/SKILL.md` §7(子代理审查提示模板),检测"测试通过但没测到关键点"。implementer 自审查 ≠ 独立审查。
|
|
209
210
|
7. 再进入下一个任务
|
|
210
211
|
|
|
211
|
-
> 完整执行步骤见
|
|
212
|
-
> 合理化预防表见
|
|
213
|
-
> REFACTOR 检查点见
|
|
214
|
-
> 异常路径覆盖门禁见
|
|
212
|
+
> 完整执行步骤见 tdd-core/reference.md §1-§3
|
|
213
|
+
> 合理化预防表见 tdd-core/SKILL.md §7
|
|
214
|
+
> REFACTOR 检查点见 tdd-rules/rules/refactor-checklist.md
|
|
215
|
+
> 异常路径覆盖门禁见 tdd-rules/rules/exception-path-coverage.md
|
|
215
216
|
> TDD 节奏校验(执行后校验)见 `./checklist.md` §5e.1
|
|
216
217
|
|
|
217
218
|
**【S2.1 RED 测试质量标准】**(test-strategy=tdd 时强制):
|
|
218
219
|
|
|
219
220
|
⛔ RED 测试质量门禁见 `./checklist.md`「§5e.2 RED 测试质量门禁」,核心包括:
|
|
220
|
-
- Mock 边界(引用
|
|
221
|
-
- 测试命名与结构(引用
|
|
222
|
-
- 断言深度(引用
|
|
223
|
-
- RED 阶段反模式检查(引用
|
|
221
|
+
- Mock 边界(引用 tdd-quality/SKILL.md §2)
|
|
222
|
+
- 测试命名与结构(引用 tdd-quality/SKILL.md §3-§4)
|
|
223
|
+
- 断言深度(引用 tdd-anti-patterns/SKILL.md §4 反模式 14)
|
|
224
|
+
- RED 阶段反模式检查(引用 tdd-anti-patterns/SKILL.md §3)
|
|
224
225
|
|
|
225
226
|
⛔ BEFORE 编写 RED 测试,必须读取:
|
|
226
|
-
-
|
|
227
|
+
- tdd-anti-patterns/SKILL.md §3(RED 阶段 3 种反模式 + 门禁函数)
|
|
227
228
|
读取后在报告中确认:"已读取 RED 阶段反模式检查表"。
|
|
228
229
|
|
|
229
|
-
> 完整 Mock 边界矩阵见
|
|
230
|
-
> 完整 15 种反模式见
|
|
230
|
+
> 完整 Mock 边界矩阵见 tdd-quality/SKILL.md §2
|
|
231
|
+
> 完整 15 种反模式见 tdd-anti-patterns/SKILL.md §3-§4
|
|
231
232
|
|
|
232
233
|
### 5f. 【S3 apply 结束前 checkbox 全量自检】
|
|
233
234
|
|
|
@@ -14,7 +14,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
14
14
|
### §1.2 Check 门禁(apply 前必做)
|
|
15
15
|
|
|
16
16
|
- [ ] 读取 telemetry 事件数据,检查当前 change 是否已有完成的 check 阶段
|
|
17
|
-
- `node skywalk-sdd/log.cjs doctor --project=. --change=<change-name>` 或搜 `skywalk-sdd/data/events/<change-name>/` 的 `stage_end` + `command=check` + `result=success`
|
|
17
|
+
- `node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" doctor --project=. --change=<change-name>` 或搜 `skywalk-sdd/data/events/<change-name>/` 的 `stage_end` + `command=check` + `result=success`
|
|
18
18
|
- [ ] check 已完成 ✅ → 继续 apply 流程
|
|
19
19
|
- [ ] check 未完成 ❌ → **强制拒绝** apply,引导用户先执行 `/opsx-check <change-name>`(不得跳过)
|
|
20
20
|
- [ ] telemetry 数据缺失(新项目无历史) → 提示首次执行先运行 `/opsx-check`,询问是否现在执行
|
|
@@ -34,7 +34,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
34
34
|
|
|
35
35
|
### §5e 测试执行门禁(按 `proposal.md` 的 `test-strategy`)
|
|
36
36
|
|
|
37
|
-
> 测试执行门禁策略见
|
|
37
|
+
> 测试执行门禁策略见 tdd-rules/rules/test-execution-gate.md
|
|
38
38
|
|
|
39
39
|
- [ ] `tdd` → ⛔ 强制执行(RED 确认失败、GREEN 确认通过、REFACTOR 全部测试仍绿)
|
|
40
40
|
- [ ] `impl-first` → ⚠️ 警告模式
|
|
@@ -42,10 +42,10 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
42
42
|
|
|
43
43
|
### §5e.1 TDD 执行合规自检(仅 test-strategy=tdd 时)
|
|
44
44
|
|
|
45
|
-
⛔ 每完成一个 RED/GREEN/REFACTOR 任务后,必须执行 `
|
|
45
|
+
⛔ 每完成一个 RED/GREEN/REFACTOR 任务后,必须执行 `tdd-core/checklist.md` §A(11 项)逐项勾选。
|
|
46
46
|
|
|
47
|
-
> 不在此内联复制,以
|
|
48
|
-
> 额外补充:REFACTOR 任务还需执行 `
|
|
47
|
+
> 不在此内联复制,以 tdd-core/checklist.md §A 为唯一真相源。
|
|
48
|
+
> 额外补充:REFACTOR 任务还需执行 `tdd-rules/rules/refactor-checklist.md`(7 项重构检查点)。
|
|
49
49
|
|
|
50
50
|
⛔ **TDD 节奏校验**(RED→GREEN 严格串行的执行后校验):
|
|
51
51
|
- [ ] 连续的 RED-N `task_update` 与 GREEN-N `task_update` 之间有可验证的执行间隔(建议 >60 秒),若时间戳差距过小视为批量执行信号
|
|
@@ -54,50 +54,50 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
54
54
|
|
|
55
55
|
### §5e.2 RED 测试质量门禁(仅 test-strategy=tdd 时,RED 任务完成后强制检查)
|
|
56
56
|
|
|
57
|
-
⛔ 核心原则(引用
|
|
57
|
+
⛔ 核心原则(引用 tdd-quality/SKILL.md §2):Mock 边界,不 Mock 行为
|
|
58
58
|
- [ ] 未 mock 被测行为本身
|
|
59
59
|
- [ ] Mock 仅用于系统边界依赖(Mapper/HTTP)
|
|
60
60
|
- [ ] RED 测试 Given 是真实输入
|
|
61
61
|
|
|
62
|
-
⛔ 测试命名与结构(引用
|
|
62
|
+
⛔ 测试命名与结构(引用 tdd-quality/SKILL.md §3-§4):
|
|
63
63
|
- [ ] 测试方法名符合 `{method}_{state}_{outcome}` 格式
|
|
64
64
|
- [ ] 测试包含 `// Given` / `// When` / `// Then` 注释结构
|
|
65
65
|
- [ ] 一测一场景(测试方法名不含 "and")
|
|
66
66
|
|
|
67
|
-
⛔ 断言深度(引用
|
|
67
|
+
⛔ 断言深度(引用 tdd-anti-patterns/SKILL.md §4 反模式 14):
|
|
68
68
|
- [ ] 每个测试至少有一个具体值断言(assertEquals),而非仅 assertNotNull
|
|
69
69
|
- [ ] 异常测试使用 `assertThrows(BusinessException.class, ...)` 并验证错误码(而非 RuntimeException.class)
|
|
70
70
|
|
|
71
71
|
⛔ BEFORE 标记 RED 任务完成,必须读取:
|
|
72
|
-
-
|
|
72
|
+
- tdd-anti-patterns/SKILL.md §3(RED 阶段反模式检查)
|
|
73
73
|
读取后确认:"已检查 RED 阶段反模式"。
|
|
74
74
|
|
|
75
|
-
> 完整质量门禁见
|
|
76
|
-
> 完整 15 种反模式见
|
|
75
|
+
> 完整质量门禁见 tdd-quality/SKILL.md §2-§6
|
|
76
|
+
> 完整 15 种反模式见 tdd-anti-patterns/SKILL.md §3-§4
|
|
77
77
|
|
|
78
78
|
### 任务状态实时更新
|
|
79
79
|
|
|
80
|
-
- [ ]
|
|
81
|
-
- [ ]
|
|
82
|
-
- [ ]
|
|
80
|
+
- [ ] 每个主任务只保留一条规范状态行:`- **状态**: [ ] 未完成` → `- **状态**: [x] 已完成`
|
|
81
|
+
- [ ] 自动同步仅更新任务块内的 `- **状态**:` 行,不改标题复选框、场景列表或验收清单
|
|
82
|
+
- [ ] “验收证据”与主任务进度分开统计;必须由真实测试、人工验收或评审证据确认,不自动勾选
|
|
83
83
|
- [ ] 显示进度:`✅ [TASK-ID] 已完成 [N/M]`
|
|
84
|
-
- [ ]
|
|
85
|
-
- [ ] ⛔ **task_update
|
|
84
|
+
- [ ] 记录严格任务事件:`task_update` 必须含 `--task-id`、`--run-id`,完成时引用成功的 green/refactor/regression `test_event_id` 并明确 `tdd_required=true/false`;测试不适用时写明 `verification_not_applicable_reason`
|
|
85
|
+
- [ ] ⛔ **task_update 后必须验证状态已更新**:执行 `node "$(cat .sdd-spec-root)/skywalk-sdd/index.cjs" check-task --project=. --change=<变更名称> --task-id=<TASK-ID>`,确认对应规范状态行已变为 `[x]`
|
|
86
86
|
|
|
87
87
|
### §5f.1 apply 结束前 checkbox 全量同步校验
|
|
88
88
|
|
|
89
89
|
> ⛔ 在 `stage_end` telemetry 记录前必须执行此校验,防止任务状态滞后到 archive 阶段。
|
|
90
90
|
|
|
91
|
-
- [ ] 对比 telemetry `task_update`
|
|
92
|
-
- [ ]
|
|
93
|
-
- [ ]
|
|
91
|
+
- [ ] 对比 telemetry `task_update` 已完成任务数与 `primary_tasks.completed`,不一致则修正任务状态或事件
|
|
92
|
+
- [ ] `acceptance_evidence` 单独报告已确认/待确认数量,不将其混入任务完成率
|
|
93
|
+
- [ ] 手动验证清单(如有)仅在真实完成后勾选
|
|
94
94
|
- [ ] 文档更新项(如有)已完成或显式标注推迟
|
|
95
95
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
98
|
## §6.0 单元测试真实执行自检(`test-strategy` 非 `none`)
|
|
99
99
|
|
|
100
|
-
> 单元测试真实执行自检见
|
|
100
|
+
> 单元测试真实执行自检见 tdd-core/reference.md §9
|
|
101
101
|
|
|
102
102
|
- [ ] 真实运行单元测试命令并留 telemetry 证据
|
|
103
103
|
- [ ] `tdd`:无测试证据不得结束 apply / 不得 finish worktree
|
|
@@ -112,7 +112,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
112
112
|
- [ ] 主仓库无与 merge 冲突的未跟踪文件(见 §1.5 Step 0.5)
|
|
113
113
|
- [ ] `test-strategy` 为 `tdd`/`impl-first` 时,§6.0 测试证据已存在
|
|
114
114
|
- [ ] 在**主仓库根目录**执行收尾脚本(禁止在 `.worktrees/...` 内执行)
|
|
115
|
-
- [ ] 执行:`node skywalk-sdd/apply-worktree-finish.cjs --change=<变更名称> --capability=<capability-name>`
|
|
115
|
+
- [ ] 执行:`node "$(cat .sdd-spec-root)/skywalk-sdd/apply-worktree-finish.cjs" --change=<变更名称> --capability=<capability-name>`
|
|
116
116
|
- [ ] 默认 merge 目标 = §1.5 `--record-base` 写入的 `integration_base`(**不是**默认 master)
|
|
117
117
|
- [ ] 若 §1.5 判定未建 worktree(串行、强依赖、沙箱回退)→ 跳过本节
|
|
118
118
|
|
|
@@ -126,7 +126,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
126
126
|
- [ ] ⛔ **DAG 依赖拦截**:执行任务前必须检查依赖,前置未完成必须拦截
|
|
127
127
|
- [ ] ⛔ **编译检查门禁**:每完成一个任务后必须运行编译检查,编译失败禁止标记已完成
|
|
128
128
|
- [ ] ⛔ **测试执行门禁**:根据 `test-strategy` 决定(tdd=强制, impl-first=强制补跑, none=跳过);须真实执行并留 telemetry,`sdd-apply-test-gate` 校验非占位数据
|
|
129
|
-
- [ ] ⛔ **RED 测试质量门禁**:见 §5e.2(引用
|
|
129
|
+
- [ ] ⛔ **RED 测试质量门禁**:见 §5e.2(引用 tdd-quality + tdd-anti-patterns,不在此内联复制)
|
|
130
130
|
- [ ] ⛔ **必须实时更新任务状态**:每完成一个任务立即改 tasks.md,两种格式(`- [ ]`→`- [x]` 与 `**状态**: [ ]`→`[x]`)同步
|
|
131
131
|
- [ ] ⛔ **apply 结束前 checkbox 全量同步校验**:见 §5f.1,`stage_end` 前对比 telemetry `task_update` 记录数与 tasks.md `[x]` 数量
|
|
132
132
|
- [ ] ⛔ **task_update 后必须验证 checkbox 已更新**:执行 `check-task` 确认 tasks.md 对应行已变更;未更新则手动修改
|
|
@@ -14,26 +14,26 @@ NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
|
|
|
14
14
|
2. 编写测试代码:Given-When-Then 结构 + 真实断言
|
|
15
15
|
3. 运行测试,确认失败(失败原因必须是功能未实现)
|
|
16
16
|
4. 如果测试通过:说明测试无效或功能已存在,重新编写
|
|
17
|
-
5. ⛔ 检查异常路径覆盖:每个 orElseThrow/边界检查须有对应测试方法(见 `
|
|
17
|
+
5. ⛔ 检查异常路径覆盖:每个 orElseThrow/边界检查须有对应测试方法(见 `tdd-rules/rules/exception-path-coverage.md`)
|
|
18
18
|
|
|
19
19
|
⛔ BEFORE 编写测试代码,必须读取对应语言的规则文件:
|
|
20
|
-
- Java:
|
|
21
|
-
- TypeScript:
|
|
22
|
-
- Python:
|
|
23
|
-
- 通用:
|
|
20
|
+
- Java:tdd-quality/rules/java/java-test-template.md + argument-matching.md + domain-service-rules.md
|
|
21
|
+
- TypeScript:tdd-quality/rules/typescript/ts-test-template.md
|
|
22
|
+
- Python:tdd-quality/rules/python/py-test-template.md
|
|
23
|
+
- 通用:tdd-quality/rules/general/naming-conventions.md + given-when-then.md + no-logic-in-tests.md
|
|
24
24
|
读取后在报告中列出已读取的规则文件路径。
|
|
25
25
|
|
|
26
26
|
### 执行 实现-GREEN 任务
|
|
27
27
|
1. 读取对应 RED 任务的失败原因
|
|
28
|
-
2. 【Scope 声明】按 `
|
|
28
|
+
2. 【Scope 声明】按 `tdd-rules/rules/green-scope-declaration.md` 执行 Scope 声明步骤
|
|
29
29
|
3. 编写最少代码——仅实现 Scope 声明中标记为"属于当前 RED"的步骤
|
|
30
30
|
4. 【Scope 自检】检查生产代码中是否有未被任何当前 RED 断言覆盖的逻辑路径?有则删除
|
|
31
31
|
5. 不提前实现没有测试要求的功能(YAGNI)
|
|
32
32
|
6. 禁止捆绑未测试的代码(Controller/Filter/Config)
|
|
33
33
|
|
|
34
|
-
> 完整执行步骤见
|
|
35
|
-
> 完整质量标准见
|
|
36
|
-
> Scope 声明规则见
|
|
34
|
+
> 完整执行步骤见 tdd-core/reference.md §1-§3
|
|
35
|
+
> 完整质量标准见 tdd-quality/SKILL.md §2
|
|
36
|
+
> Scope 声明规则见 tdd-rules/rules/green-scope-declaration.md
|
|
37
37
|
|
|
38
38
|
### 执行 重构-REFACTOR 任务
|
|
39
39
|
1. 在所有测试通过的状态下开始
|
|
@@ -41,9 +41,9 @@ NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
|
|
|
41
41
|
3. 运行**全部测试**:`mvn test`(或项目对应命令)
|
|
42
42
|
4. 确认所有测试仍通过
|
|
43
43
|
5. 如果任何测试失败:回退重构,重新尝试
|
|
44
|
-
6. ⛔ 执行 `
|
|
44
|
+
6. ⛔ 执行 `tdd-rules/rules/refactor-checklist.md`(7 项重构检查点)
|
|
45
45
|
|
|
46
|
-
> 完整 REFACTOR 检查点见
|
|
46
|
+
> 完整 REFACTOR 检查点见 tdd-rules/rules/refactor-checklist.md
|
|
47
47
|
|
|
48
48
|
## 派发格式
|
|
49
49
|
|
|
@@ -94,7 +94,7 @@ Agent (general-purpose):
|
|
|
94
94
|
3. 遵循 overview.md 的全局规范
|
|
95
95
|
4. 保持变更最小化,不超出任务范围
|
|
96
96
|
5. **当任务类型为 测试-RED 时**:编写带真实断言的测试,运行并确认失败,记录失败原因
|
|
97
|
-
6. **当任务类型为 实现-GREEN 时**:读取对应 RED 的失败原因,按 `
|
|
97
|
+
6. **当任务类型为 实现-GREEN 时**:读取对应 RED 的失败原因,按 `tdd-rules/rules/green-scope-declaration.md` 执行 Scope 声明,仅实现标记为"属于"的步骤,写最少代码让测试通过,不提前实现未要求的功能
|
|
98
98
|
7. **当任务类型为 重构-REFACTOR 时**:在测试全绿状态下优化代码,运行全部测试确认仍绿
|
|
99
99
|
8. 自我审查(见下方)
|
|
100
100
|
9. 报告结果
|