@pzy560117/codex-harness 0.1.7 → 0.1.9
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 +1 -1
- package/package-source/AGENTS.md +18 -1
- package/package-source/docs/codex-harness-engineering/templates/bootstrap-codex-harness.ps1 +57 -48
- package/package-source/docs/codex-harness-engineering/templates/config/rules/agents.md +3 -3
- package/package-source/docs/codex-harness-engineering/templates/docs/project-agents-template.md +8 -1
- package/package-source/docs/codex-harness-engineering/templates/docs/task-session-strategy.md +7 -0
- package/package-source/docs/codex-harness-engineering/templates/hooks/hook-stop-verify.ps1 +76 -10
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/examples/ticket-filter-demo/task.json +2 -2
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/project-agents-template.md +51 -1
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/controller-loop.md +88 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/failure-triage.md +71 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/harness-audit.md +54 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/implement-one-task.md +18 -15
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/repair-one-finding.md +1 -2
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/review-one-task.md +45 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/review-stage1-spec.md +111 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/review-stage2-quality.md +82 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/visual-evaluator.md +80 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/worker-role/backend-worker.md +41 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/worker-role/docs-worker.md +28 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/worker-role/frontend-worker.md +46 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/worker-role/harness-writer.md +40 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/templates/prompts/worker-role/test-runner.md +27 -0
- package/package-source/docs/codex-harness-engineering/templates/package-assets/rules/agents.md +6 -3
- package/package-source/docs/codex-harness-engineering/templates/package-assets/skills/auto-commit/SKILL.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/package-assets/skills/speckit-plan/SKILL.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/controller-loop.md +4 -4
- package/package-source/docs/codex-harness-engineering/templates/prompts/failure-triage.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/harness-audit.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/implement-one-task.md +18 -15
- package/package-source/docs/codex-harness-engineering/templates/prompts/repair-one-finding.md +1 -2
- package/package-source/docs/codex-harness-engineering/templates/prompts/review-one-task.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/review-stage1-spec.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/review-stage2-quality.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/visual-evaluator.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/prompts/worker-role/backend-worker.md +2 -2
- package/package-source/docs/codex-harness-engineering/templates/prompts/worker-role/docs-worker.md +2 -2
- package/package-source/docs/codex-harness-engineering/templates/prompts/worker-role/frontend-worker.md +2 -2
- package/package-source/docs/codex-harness-engineering/templates/prompts/worker-role/harness-writer.md +4 -4
- package/package-source/docs/codex-harness-engineering/templates/prompts/worker-role/test-runner.md +1 -1
- package/package-source/docs/codex-harness-engineering/templates/runtime/AGENTS.md +45 -46
- package/package-source/docs/codex-harness-engineering/templates/runtime/doctor.ps1 +151 -42
- package/package-source/docs/codex-harness-engineering/templates/runtime/project-task-template.json +81 -66
- package/package-source/docs/codex-harness-engineering/templates/runtime/smoke-task.json +1 -1
- package/package-source/docs/codex-harness-engineering/templates/runtime/task.json +1 -1
- package/package-source/docs/codex-harness-engineering/templates/runtime/verify.ps1 +29 -12
- package/package-source/docs/codex-harness-engineering/templates/tools/harness/task-structure-lint.ps1 +399 -0
- package/package-source/install-manifest.json +1 -1
- package/package-source/tools/install/bootstrap-codex-harness.ps1 +23 -10
- package/package-source/tools/install/install-agent.ps1 +59 -18
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
|
|
11
11
|
$ProjectRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\.."))
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function Invoke-VerificationCommand {
|
|
14
|
+
function Invoke-VerificationCommand {
|
|
15
15
|
param(
|
|
16
16
|
[string]$Command,
|
|
17
17
|
[string]$Root
|
|
@@ -27,8 +27,22 @@ function Invoke-VerificationCommand {
|
|
|
27
27
|
}
|
|
28
28
|
finally {
|
|
29
29
|
Pop-Location
|
|
30
|
-
}
|
|
31
|
-
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Invoke-TaskStructureValidation {
|
|
34
|
+
param([string]$Root)
|
|
35
|
+
|
|
36
|
+
$scriptPath = Join-Path $Root "tools\harness\task-structure-lint.ps1"
|
|
37
|
+
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
|
38
|
+
throw "缺少 task structure lint: tools\\harness\\task-structure-lint.ps1"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $scriptPath -ProjectRoot $Root | Out-Host
|
|
42
|
+
if ($LASTEXITCODE -ne 0) {
|
|
43
|
+
throw "task structure 校验失败。"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
32
46
|
|
|
33
47
|
function Get-CodexHookCommand {
|
|
34
48
|
param(
|
|
@@ -166,15 +180,17 @@ function Test-CodexHookCommandCompatibility {
|
|
|
166
180
|
function Test-PowerShellSyntax {
|
|
167
181
|
param([string]$Root)
|
|
168
182
|
|
|
169
|
-
$paths = @(
|
|
183
|
+
$paths = @(
|
|
170
184
|
"tools\install\bootstrap-codex-harness.ps1",
|
|
171
185
|
"tools\harness\codex-loop.ps1",
|
|
172
186
|
"tools\install\env-check.ps1",
|
|
173
187
|
"tools\install\install-agent.ps1",
|
|
174
188
|
"tools\install\install-agent-here.ps1",
|
|
189
|
+
"tools\harness\task-structure-lint.ps1",
|
|
175
190
|
"tools\harness\verify.ps1",
|
|
176
191
|
"docs\codex-harness-engineering\templates\bootstrap-codex-harness.ps1",
|
|
177
|
-
"docs\codex-harness-engineering\templates\hooks\hook-stop-verify.ps1"
|
|
192
|
+
"docs\codex-harness-engineering\templates\hooks\hook-stop-verify.ps1",
|
|
193
|
+
"docs\codex-harness-engineering\templates\tools\harness\task-structure-lint.ps1"
|
|
178
194
|
)
|
|
179
195
|
|
|
180
196
|
foreach ($relativePath in $paths) {
|
|
@@ -273,13 +289,14 @@ function Invoke-AgentPackageFreshnessCheck {
|
|
|
273
289
|
|
|
274
290
|
$resolvedProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path
|
|
275
291
|
|
|
276
|
-
foreach ($command in $Commands) {
|
|
277
|
-
Invoke-VerificationCommand -Command $command -Root $resolvedProjectRoot
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
Test-
|
|
282
|
-
Test-
|
|
292
|
+
foreach ($command in $Commands) {
|
|
293
|
+
Invoke-VerificationCommand -Command $command -Root $resolvedProjectRoot
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
Invoke-TaskStructureValidation -Root $resolvedProjectRoot
|
|
297
|
+
Test-CodexHookCommandCompatibility -Root $resolvedProjectRoot -AdditionalPaths $HookConfigPaths
|
|
298
|
+
Test-PowerShellSyntax -Root $resolvedProjectRoot
|
|
299
|
+
Test-NoStaleFeedbackMcpReferences -Root $resolvedProjectRoot
|
|
283
300
|
Invoke-AgentPackageFreshnessCheck -Root $resolvedProjectRoot
|
|
284
301
|
|
|
285
302
|
Write-Output "Verification passed."
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$ProjectRoot = "",
|
|
3
|
+
[string]$TaskFile = "task.json",
|
|
4
|
+
[switch]$JsonOutput
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
Set-StrictMode -Version Latest
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
|
|
10
|
+
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
|
11
|
+
$candidateRoots = @(
|
|
12
|
+
[System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..")),
|
|
13
|
+
[System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..\..\..\.."))
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
foreach ($candidateRoot in $candidateRoots | Select-Object -Unique) {
|
|
17
|
+
if (Test-Path -LiteralPath (Join-Path $candidateRoot "task.json")) {
|
|
18
|
+
$ProjectRoot = $candidateRoot
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
|
24
|
+
$ProjectRoot = $candidateRoots[0]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$script:Findings = New-Object System.Collections.Generic.List[object]
|
|
29
|
+
|
|
30
|
+
function Get-ObjectPropertyValue {
|
|
31
|
+
param(
|
|
32
|
+
[object]$InputObject,
|
|
33
|
+
[string]$Name,
|
|
34
|
+
[object]$Default = $null
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if ($null -eq $InputObject) {
|
|
38
|
+
return $Default
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$property = $InputObject.PSObject.Properties[$Name]
|
|
42
|
+
if ($null -eq $property) {
|
|
43
|
+
return $Default
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return $property.Value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ConvertTo-StringArray {
|
|
50
|
+
param([object]$Value)
|
|
51
|
+
|
|
52
|
+
if ($null -eq $Value) {
|
|
53
|
+
return @()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ($Value -is [string]) {
|
|
57
|
+
if ([string]::IsNullOrWhiteSpace($Value)) {
|
|
58
|
+
return @()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return @([string]$Value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$items = @()
|
|
65
|
+
foreach ($item in $Value) {
|
|
66
|
+
if ($null -ne $item -and -not [string]::IsNullOrWhiteSpace([string]$item)) {
|
|
67
|
+
$items += [string]$item
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return $items
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Add-Finding {
|
|
75
|
+
param(
|
|
76
|
+
[string]$Severity,
|
|
77
|
+
[string]$Code,
|
|
78
|
+
[string]$Message,
|
|
79
|
+
[string]$TaskId = "",
|
|
80
|
+
[string]$Field = ""
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
$script:Findings.Add([pscustomobject]@{
|
|
84
|
+
severity = $Severity
|
|
85
|
+
code = $Code
|
|
86
|
+
message = $Message
|
|
87
|
+
task_id = $TaskId
|
|
88
|
+
field = $Field
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function Read-JsonFileOrNull {
|
|
93
|
+
param([string]$Path)
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
return (Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json)
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return $null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Get-TaskList {
|
|
104
|
+
param([object]$TaskDocument)
|
|
105
|
+
|
|
106
|
+
$tasks = Get-ObjectPropertyValue -InputObject $TaskDocument -Name "tasks" -Default $null
|
|
107
|
+
if ($null -eq $tasks -or ($tasks -is [string]) -or (-not ($tasks -is [System.Collections.IEnumerable]))) {
|
|
108
|
+
return @()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return @($tasks)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Test-FileExists {
|
|
115
|
+
param(
|
|
116
|
+
[string]$Root,
|
|
117
|
+
[string]$RelativePath
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return (Test-Path -LiteralPath (Join-Path $Root $RelativePath))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Test-IsFormalTaskQueue {
|
|
124
|
+
param([object[]]$Tasks)
|
|
125
|
+
|
|
126
|
+
foreach ($task in @($Tasks)) {
|
|
127
|
+
$taskKind = [string](Get-ObjectPropertyValue -InputObject $task -Name "task_kind" -Default "")
|
|
128
|
+
if (@("feature_research", "feature_spec", "feature_design", "feature_plan", "feature_impl", "release", "archive") -contains $taskKind) {
|
|
129
|
+
return $true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return $false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function Test-ApprovedDecompositionExemption {
|
|
137
|
+
param(
|
|
138
|
+
[object]$Task,
|
|
139
|
+
[string]$Scope
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
$exemption = Get-ObjectPropertyValue -InputObject $Task -Name "decomposition_exemption" -Default $null
|
|
143
|
+
if ($null -eq $exemption) {
|
|
144
|
+
return $false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
$approved = [bool](Get-ObjectPropertyValue -InputObject $exemption -Name "user_approved" -Default $false)
|
|
148
|
+
if (-not $approved) {
|
|
149
|
+
return $false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$scopes = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $exemption -Name "scopes" -Default @()))
|
|
153
|
+
if ($scopes.Count -eq 0) {
|
|
154
|
+
return $true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return $scopes -contains $Scope
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function Get-OwnedSurfaceKinds {
|
|
161
|
+
param([string[]]$OwnedPaths)
|
|
162
|
+
|
|
163
|
+
$hasBackend = $false
|
|
164
|
+
$hasFrontend = $false
|
|
165
|
+
|
|
166
|
+
foreach ($rawPath in @($OwnedPaths)) {
|
|
167
|
+
$path = $rawPath.Replace('/', '\').ToLowerInvariant()
|
|
168
|
+
if ($path -match '(^|\\)(apps\\api|backend|server|services?)($|\\)') {
|
|
169
|
+
$hasBackend = $true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if ($path -match '(^|\\)(apps\\admin|apps\\web|apps\\mobile|frontend|web|mobile|ui)($|\\)') {
|
|
173
|
+
$hasFrontend = $true
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return [pscustomobject]@{
|
|
178
|
+
backend = $hasBackend
|
|
179
|
+
frontend = $hasFrontend
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function Get-DomainMatches {
|
|
184
|
+
param([object]$Task)
|
|
185
|
+
|
|
186
|
+
$description = [string](Get-ObjectPropertyValue -InputObject $Task -Name "description" -Default "")
|
|
187
|
+
$steps = ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "steps" -Default @())
|
|
188
|
+
$requirementIds = ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $Task -Name "requirement_ids" -Default @())
|
|
189
|
+
$haystack = (($description + " " + ($steps -join " ") + " " + ($requirementIds -join " ")).ToLowerInvariant())
|
|
190
|
+
|
|
191
|
+
$domainMatchesList = @()
|
|
192
|
+
$patterns = @(
|
|
193
|
+
@{ name = "order"; pattern = '(订单|order)' },
|
|
194
|
+
@{ name = "payment"; pattern = '(支付|payment)' },
|
|
195
|
+
@{ name = "inventory"; pattern = '(库存|inventory)' },
|
|
196
|
+
@{ name = "rbac"; pattern = '(rbac|角色权限|权限角色|role[ -]?based access)' }
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
foreach ($entry in $patterns) {
|
|
200
|
+
if ($haystack -match $entry["pattern"]) {
|
|
201
|
+
$domainMatchesList += [string]$entry["name"]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return @($domainMatchesList | Select-Object -Unique)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function Test-RequiredSourceCaseIds {
|
|
209
|
+
param(
|
|
210
|
+
[object]$ParentObject,
|
|
211
|
+
[string]$PropertyName,
|
|
212
|
+
[string]$TaskId,
|
|
213
|
+
[string]$FieldBase
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
$target = Get-ObjectPropertyValue -InputObject $ParentObject -Name $PropertyName -Default $null
|
|
217
|
+
if ($null -eq $target) {
|
|
218
|
+
Add-Finding -Severity "error" -Code "missing_field" -Message "任务缺少 $FieldBase.$PropertyName。" -TaskId $TaskId -Field "$FieldBase.$PropertyName"
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
$sourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $target -Name "source_case_ids" -Default @()))
|
|
223
|
+
if ($sourceCaseIds.Count -eq 0) {
|
|
224
|
+
Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "任务缺少 $FieldBase.$PropertyName.source_case_ids。" -TaskId $TaskId -Field "$FieldBase.$PropertyName.source_case_ids"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function Test-FormalTaskQueueStructure {
|
|
229
|
+
param(
|
|
230
|
+
[string]$Root,
|
|
231
|
+
[object[]]$Tasks
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
$taskIdSet = @{}
|
|
235
|
+
foreach ($task in @($Tasks)) {
|
|
236
|
+
$taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
|
|
237
|
+
if (-not [string]::IsNullOrWhiteSpace($taskId)) {
|
|
238
|
+
$taskIdSet[$taskId] = $true
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
foreach ($requiredTaskId in @("ANALYSIS-001", "TESTCASE-001", "PLAN-001")) {
|
|
243
|
+
if (-not $taskIdSet.ContainsKey($requiredTaskId)) {
|
|
244
|
+
Add-Finding -Severity "error" -Code "missing_phase_task" -Message "正式任务队列缺少必需 phase:$requiredTaskId。" -Field "tasks"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
$featureImplTasks = @($Tasks | Where-Object { [string](Get-ObjectPropertyValue -InputObject $_ -Name "task_kind" -Default "") -eq "feature_impl" })
|
|
249
|
+
$releaseTasks = @($Tasks | Where-Object { [string](Get-ObjectPropertyValue -InputObject $_ -Name "task_kind" -Default "") -eq "release" })
|
|
250
|
+
|
|
251
|
+
if (($featureImplTasks.Count + $releaseTasks.Count) -gt 0) {
|
|
252
|
+
$requiredTestingArtifacts = @(
|
|
253
|
+
"docs\testing\ACCEPTANCE_CRITERIA.md",
|
|
254
|
+
"docs\testing\NATURAL_LANGUAGE_TEST_CASES.md",
|
|
255
|
+
"docs\testing\TRACEABILITY_MATRIX.md",
|
|
256
|
+
"docs\testing\TEST_DATA_MATRIX.md",
|
|
257
|
+
"docs\testing\REGRESSION_PLAN.md",
|
|
258
|
+
"docs\testing\verify-matrix.md"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
foreach ($relativePath in $requiredTestingArtifacts) {
|
|
262
|
+
if (-not (Test-FileExists -Root $Root -RelativePath $relativePath)) {
|
|
263
|
+
Add-Finding -Severity "error" -Code "missing_testing_artifact" -Message "正式任务队列缺少测试真相源:$relativePath。" -Field $relativePath
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
foreach ($task in $featureImplTasks) {
|
|
269
|
+
$taskId = [string](Get-ObjectPropertyValue -InputObject $task -Name "id" -Default "")
|
|
270
|
+
$qaContract = Get-ObjectPropertyValue -InputObject $task -Name "qa_contract" -Default $null
|
|
271
|
+
if ($null -eq $qaContract) {
|
|
272
|
+
Add-Finding -Severity "error" -Code "missing_qa_contract" -Message "feature_impl 缺少 qa_contract。" -TaskId $taskId -Field "qa_contract"
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
$requiredLayers = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $qaContract -Name "required_layers" -Default @()))
|
|
276
|
+
foreach ($layer in @("unit_or_component", "contract_or_api", "story_full_chain", "affected_regression")) {
|
|
277
|
+
if ($requiredLayers -notcontains $layer) {
|
|
278
|
+
Add-Finding -Severity "error" -Code "missing_required_layer" -Message "feature_impl 缺少 qa_contract.required_layers[$layer]。" -TaskId $taskId -Field "qa_contract.required_layers"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
$developmentValidation = Get-ObjectPropertyValue -InputObject $qaContract -Name "development_validation" -Default $null
|
|
283
|
+
if ($null -eq $developmentValidation) {
|
|
284
|
+
Add-Finding -Severity "error" -Code "missing_development_validation" -Message "feature_impl 缺少 qa_contract.development_validation。" -TaskId $taskId -Field "qa_contract.development_validation"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
$acceptanceValidation = Get-ObjectPropertyValue -InputObject $qaContract -Name "acceptance_validation" -Default $null
|
|
288
|
+
if ($null -eq $acceptanceValidation) {
|
|
289
|
+
Add-Finding -Severity "error" -Code "missing_acceptance_validation" -Message "feature_impl 缺少 qa_contract.acceptance_validation。" -TaskId $taskId -Field "qa_contract.acceptance_validation"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
$tddContract = Get-ObjectPropertyValue -InputObject $qaContract -Name "tdd_contract" -Default $null
|
|
293
|
+
if ($null -eq $tddContract) {
|
|
294
|
+
Add-Finding -Severity "error" -Code "missing_tdd_contract" -Message "feature_impl 缺少 qa_contract.tdd_contract。" -TaskId $taskId -Field "qa_contract.tdd_contract"
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
$red = Get-ObjectPropertyValue -InputObject $tddContract -Name "red" -Default $null
|
|
298
|
+
if ($null -eq $red) {
|
|
299
|
+
Add-Finding -Severity "error" -Code "missing_tdd_red" -Message "feature_impl 缺少 qa_contract.tdd_contract.red。" -TaskId $taskId -Field "qa_contract.tdd_contract.red"
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
$sourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $red -Name "source_case_ids" -Default @()))
|
|
303
|
+
if ($sourceCaseIds.Count -eq 0) {
|
|
304
|
+
Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "feature_impl 缺少 qa_contract.tdd_contract.red.source_case_ids。" -TaskId $taskId -Field "qa_contract.tdd_contract.red.source_case_ids"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
foreach ($requiredField in @("green", "refactor_guard")) {
|
|
309
|
+
if ($null -eq (Get-ObjectPropertyValue -InputObject $tddContract -Name $requiredField -Default $null)) {
|
|
310
|
+
Add-Finding -Severity "error" -Code "missing_tdd_field" -Message "feature_impl 缺少 qa_contract.tdd_contract.$requiredField。" -TaskId $taskId -Field "qa_contract.tdd_contract.$requiredField"
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
Test-RequiredSourceCaseIds -ParentObject $qaContract -PropertyName "story_full_chain" -TaskId $taskId -FieldBase "qa_contract"
|
|
316
|
+
if ($null -ne $acceptanceValidation) {
|
|
317
|
+
$acceptanceSourceCaseIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $acceptanceValidation -Name "source_case_ids" -Default @()))
|
|
318
|
+
if ($acceptanceSourceCaseIds.Count -eq 0) {
|
|
319
|
+
Add-Finding -Severity "error" -Code "empty_source_case_ids" -Message "feature_impl 缺少 qa_contract.acceptance_validation.source_case_ids。" -TaskId $taskId -Field "qa_contract.acceptance_validation.source_case_ids"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
$requirementIds = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $task -Name "requirement_ids" -Default @()))
|
|
325
|
+
if ($requirementIds.Count -gt 3 -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "requirement_span")) {
|
|
326
|
+
Add-Finding -Severity "error" -Code "oversized_requirement_span" -Message "单个 feature_impl 默认不得覆盖超过 3 条主 requirement_ids。" -TaskId $taskId -Field "requirement_ids"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
$ownedPaths = @(ConvertTo-StringArray -Value (Get-ObjectPropertyValue -InputObject $task -Name "owned_paths" -Default @()))
|
|
330
|
+
$surfaceKinds = Get-OwnedSurfaceKinds -OwnedPaths $ownedPaths
|
|
331
|
+
if ($surfaceKinds.backend -and $surfaceKinds.frontend -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "mixed_surface")) {
|
|
332
|
+
Add-Finding -Severity "error" -Code "mixed_frontend_backend_story" -Message "feature_impl 同时覆盖前端与后端路径;请拆分为独立 story 或改为明确的 release 收口。" -TaskId $taskId -Field "owned_paths"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
$domains = @(Get-DomainMatches -Task $task)
|
|
336
|
+
if ($domains.Count -gt 1 -and -not (Test-ApprovedDecompositionExemption -Task $task -Scope "domain_bundle")) {
|
|
337
|
+
Add-Finding -Severity "error" -Code "combined_high_risk_domains" -Message ("feature_impl 同时覆盖高风险主域:{0}。订单、支付、库存、RBAC 必须拆独立任务。" -f ($domains -join ", ")) -TaskId $taskId -Field "description"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
$resolvedProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path
|
|
343
|
+
$taskPath = Join-Path $resolvedProjectRoot $TaskFile
|
|
344
|
+
|
|
345
|
+
if (-not (Test-Path -LiteralPath $taskPath)) {
|
|
346
|
+
Add-Finding -Severity "error" -Code "missing_task_file" -Message "缺少 task.json:$TaskFile。" -Field $TaskFile
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
$taskDocument = Read-JsonFileOrNull -Path $taskPath
|
|
350
|
+
if ($null -eq $taskDocument) {
|
|
351
|
+
Add-Finding -Severity "error" -Code "invalid_task_json" -Message "task.json 不是合法 JSON。" -Field $TaskFile
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
$tasks = Get-TaskList -TaskDocument $taskDocument
|
|
355
|
+
if ($tasks.Count -eq 0) {
|
|
356
|
+
Add-Finding -Severity "error" -Code "empty_task_queue" -Message "task.json 不包含可校验任务。" -Field "tasks"
|
|
357
|
+
}
|
|
358
|
+
elseif (Test-IsFormalTaskQueue -Tasks $tasks) {
|
|
359
|
+
Test-FormalTaskQueueStructure -Root $resolvedProjectRoot -Tasks $tasks
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
$errors = @($script:Findings | Where-Object { $_.severity -eq "error" })
|
|
365
|
+
$warnings = @($script:Findings | Where-Object { $_.severity -eq "warning" })
|
|
366
|
+
$status = if ($errors.Count -gt 0) { "fail" } elseif ($warnings.Count -gt 0) { "warn" } else { "pass" }
|
|
367
|
+
$result = @{
|
|
368
|
+
status = $status
|
|
369
|
+
error_count = $errors.Count
|
|
370
|
+
warning_count = $warnings.Count
|
|
371
|
+
findings = @($script:Findings.ToArray())
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if ($JsonOutput) {
|
|
375
|
+
$result | ConvertTo-Json -Depth 8
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
if ($status -eq "fail") {
|
|
379
|
+
Write-Output "Task structure validation failed."
|
|
380
|
+
}
|
|
381
|
+
elseif ($status -eq "warn") {
|
|
382
|
+
Write-Output "Task structure validation passed with warnings."
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
Write-Output "Task structure validation passed."
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
foreach ($finding in $script:Findings.ToArray()) {
|
|
389
|
+
$taskLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.task_id)) { "" } else { " task=$($finding.task_id)" }
|
|
390
|
+
$fieldLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.field)) { "" } else { " field=$($finding.field)" }
|
|
391
|
+
Write-Output "- [$($finding.severity)] $($finding.code)$taskLabel$fieldLabel :: $($finding.message)"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if ($status -eq "fail") {
|
|
396
|
+
exit 1
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
exit 0
|
|
@@ -225,16 +225,28 @@ function Ensure-TaskFile {
|
|
|
225
225
|
return Copy-ManagedFile -SourceRoot $SourceRoot -SourceRelativePath "runtime\task.json" -DestinationRoot $DestinationRoot -DestinationRelativePath "task.json" -Overwrite:$true
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
$taskDocument = $taskContent | ConvertFrom-Json
|
|
229
|
-
if ($null -eq $taskDocument.runtime) {
|
|
230
|
-
$taskDocument | Add-Member -NotePropertyName "runtime" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
228
|
+
$taskDocument = $taskContent | ConvertFrom-Json
|
|
229
|
+
if ($null -eq $taskDocument.runtime) {
|
|
230
|
+
$taskDocument | Add-Member -NotePropertyName "runtime" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
$canonicalDriver = "powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\codex-loop.ps1"
|
|
234
|
+
$driverPattern = 'tools[\\/]+harness[\\/]+tools[\\/]+harness[\\/]+codex-loop\.ps1'
|
|
235
|
+
|
|
236
|
+
if ([string]::IsNullOrWhiteSpace($taskDocument.runtime.driver)) {
|
|
237
|
+
$taskDocument.runtime.driver = $canonicalDriver
|
|
238
|
+
$taskDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $taskPath -Encoding UTF8
|
|
239
|
+
return [PSCustomObject]@{
|
|
240
|
+
Path = $taskPath
|
|
241
|
+
Action = "updated"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if ([string]$taskDocument.runtime.driver -match $driverPattern) {
|
|
246
|
+
$taskDocument.runtime.driver = $canonicalDriver
|
|
247
|
+
$taskDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $taskPath -Encoding UTF8
|
|
248
|
+
return [PSCustomObject]@{
|
|
249
|
+
Path = $taskPath
|
|
238
250
|
Action = "updated"
|
|
239
251
|
}
|
|
240
252
|
}
|
|
@@ -601,6 +613,7 @@ $rootFiles = @(
|
|
|
601
613
|
@{ Source = "prompts\worker-role\harness-writer.md"; Destination = ".codex\prompts\worker-role\harness-writer.md" },
|
|
602
614
|
@{ Source = "runtime\smoke-task.json"; Destination = "tools\harness\templates\smoke-task.json" },
|
|
603
615
|
@{ Source = "runtime\project-task-template.json"; Destination = "tools\harness\templates\project-task-template.json" },
|
|
616
|
+
@{ Source = "tools\harness\task-structure-lint.ps1"; Destination = "tools\harness\task-structure-lint.ps1" },
|
|
604
617
|
@{ Source = "runtime\verify.ps1"; Destination = "tools\harness\verify.ps1" },
|
|
605
618
|
@{ Source = "tools\harness\docs-lint.ps1"; Destination = "tools\harness\docs-lint.ps1" },
|
|
606
619
|
@{ Source = "tools\harness\data-lint.ps1"; Destination = "tools\harness\data-lint.ps1" },
|
|
@@ -2022,7 +2022,7 @@ function Copy-ManifestEntries {
|
|
|
2022
2022
|
}
|
|
2023
2023
|
}
|
|
2024
2024
|
|
|
2025
|
-
function Overlay-DirectoryIfPresent {
|
|
2025
|
+
function Overlay-DirectoryIfPresent {
|
|
2026
2026
|
param(
|
|
2027
2027
|
[string]$SourceDirectory,
|
|
2028
2028
|
[string]$DestinationDirectory
|
|
@@ -2056,8 +2056,8 @@ function Overlay-DirectoryIfPresent {
|
|
|
2056
2056
|
}
|
|
2057
2057
|
}
|
|
2058
2058
|
|
|
2059
|
-
function Ensure-ReadmeForSmoke {
|
|
2060
|
-
param([string]$Root)
|
|
2059
|
+
function Ensure-ReadmeForSmoke {
|
|
2060
|
+
param([string]$Root)
|
|
2061
2061
|
|
|
2062
2062
|
$readmePath = Join-Path $Root "README.md"
|
|
2063
2063
|
if (Test-Path -LiteralPath $readmePath) {
|
|
@@ -2070,11 +2070,47 @@ function Ensure-ReadmeForSmoke {
|
|
|
2070
2070
|
"This README was generated by install-agent.ps1 to support the initial smoke task.",
|
|
2071
2071
|
"Replace it with project-specific content after harness setup is verified."
|
|
2072
2072
|
) | Set-Content -LiteralPath $readmePath -Encoding UTF8
|
|
2073
|
-
|
|
2074
|
-
return $readmePath
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
function
|
|
2073
|
+
|
|
2074
|
+
return $readmePath
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
function Assert-InstalledProjectScopeSurface {
|
|
2078
|
+
param(
|
|
2079
|
+
[object]$ManifestInfo,
|
|
2080
|
+
[string]$ProjectRoot
|
|
2081
|
+
)
|
|
2082
|
+
|
|
2083
|
+
$projectEntries = @(
|
|
2084
|
+
$ManifestInfo.Entries |
|
|
2085
|
+
Where-Object { [string]$_.Scope -eq "project" }
|
|
2086
|
+
)
|
|
2087
|
+
|
|
2088
|
+
$missingPaths = @()
|
|
2089
|
+
foreach ($entry in $projectEntries) {
|
|
2090
|
+
$targetPath = [string]$entry.TargetPath
|
|
2091
|
+
if ([string]::IsNullOrWhiteSpace($targetPath)) {
|
|
2092
|
+
continue
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
if ($entry.SourceKind -eq "directory") {
|
|
2096
|
+
if (-not (Test-Path -LiteralPath $targetPath -PathType Container)) {
|
|
2097
|
+
$missingPaths += $entry.Destination
|
|
2098
|
+
}
|
|
2099
|
+
continue
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) {
|
|
2103
|
+
$missingPaths += $entry.Destination
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
if ($missingPaths.Count -gt 0) {
|
|
2108
|
+
$details = ($missingPaths | Sort-Object -Unique | ForEach-Object { "- $_" }) -join "`n"
|
|
2109
|
+
throw "project scope 安装后缺少以下真相源或运行文件:`n$details"
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
function Copy-Package {
|
|
2078
2114
|
param(
|
|
2079
2115
|
[string]$SourceRoot,
|
|
2080
2116
|
[string]$DestinationRoot,
|
|
@@ -2399,18 +2435,23 @@ try {
|
|
|
2399
2435
|
Write-Step "已写入 package lock: $lockPath"
|
|
2400
2436
|
}
|
|
2401
2437
|
|
|
2402
|
-
if ($Mode -eq "project" -or $Mode -eq "vendor") {
|
|
2403
|
-
$configPackageRoot = if ($Mode -eq "vendor") { $targetAgentsRoot } elseif ($null -ne $scopeUserPackage) { [string]$scopeUserPackage.PackageRoot } else { $effectiveSourceRoot }
|
|
2404
|
-
$configPath = Write-ProjectHarnessConfig `
|
|
2405
|
-
-ManifestInfo $installManifestInfo `
|
|
2438
|
+
if ($Mode -eq "project" -or $Mode -eq "vendor") {
|
|
2439
|
+
$configPackageRoot = if ($Mode -eq "vendor") { $targetAgentsRoot } elseif ($null -ne $scopeUserPackage) { [string]$scopeUserPackage.PackageRoot } else { $effectiveSourceRoot }
|
|
2440
|
+
$configPath = Write-ProjectHarnessConfig `
|
|
2441
|
+
-ManifestInfo $installManifestInfo `
|
|
2406
2442
|
-ProjectRoot $resolvedProjectRoot `
|
|
2407
2443
|
-InstallScope $Mode `
|
|
2408
2444
|
-VendorInstalled:($Mode -eq "vendor") `
|
|
2409
|
-
-PackageRoot $configPackageRoot
|
|
2410
|
-
Write-Step "已写入 harness config: $configPath"
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
$
|
|
2445
|
+
-PackageRoot $configPackageRoot
|
|
2446
|
+
Write-Step "已写入 harness config: $configPath"
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
if ($Mode -eq "project") {
|
|
2450
|
+
Assert-InstalledProjectScopeSurface -ManifestInfo $installManifestInfo -ProjectRoot $resolvedProjectRoot
|
|
2451
|
+
Write-Step "project scope 真相源与运行文件已按 manifest 落地"
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
$generatedReadme = $null
|
|
2414
2455
|
if ($InitSmoke) {
|
|
2415
2456
|
$generatedReadme = Ensure-ReadmeForSmoke -Root $resolvedProjectRoot
|
|
2416
2457
|
if ($null -ne $generatedReadme) {
|
|
@@ -2506,7 +2547,7 @@ try {
|
|
|
2506
2547
|
Write-Output ("- powershell -NoProfile -ExecutionPolicy Bypass -File `"{0}`"" -f (Join-Path $resolvedProjectRoot 'tools\harness\codex-loop.ps1'))
|
|
2507
2548
|
}
|
|
2508
2549
|
}
|
|
2509
|
-
finally {
|
|
2550
|
+
finally {
|
|
2510
2551
|
if (($null -ne $stagedSourceRoot) -and (Test-Path -LiteralPath $stagedSourceRoot)) {
|
|
2511
2552
|
Remove-Item -LiteralPath $stagedSourceRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
2512
2553
|
}
|