@pzy560117/codex-harness 0.1.6 → 0.1.8

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.
Files changed (21) hide show
  1. package/README.md +4 -1
  2. package/lib/commands/init.js +61 -1
  3. package/lib/release/package-source-layout.js +20 -8
  4. package/package-source/AGENTS.md +7 -0
  5. package/package-source/PACKAGE.md +3 -3
  6. package/package-source/README.md +13 -46
  7. package/package-source/docs/codex-harness-engineering/templates/bootstrap-codex-harness.ps1 +57 -48
  8. package/package-source/docs/codex-harness-engineering/templates/docs/new-project-usage.md +2 -0
  9. package/package-source/docs/codex-harness-engineering/templates/docs/task-session-strategy.md +4 -0
  10. package/package-source/docs/codex-harness-engineering/templates/hooks/hook-stop-verify.ps1 +76 -10
  11. package/package-source/docs/codex-harness-engineering/templates/package-assets/docs/codex-harness-engineering/examples/ticket-filter-demo/task.json +2 -2
  12. package/package-source/docs/codex-harness-engineering/templates/runtime/AGENTS.md +4 -0
  13. package/package-source/docs/codex-harness-engineering/templates/runtime/doctor.ps1 +89 -24
  14. package/package-source/docs/codex-harness-engineering/templates/runtime/project-task-template.json +81 -66
  15. package/package-source/docs/codex-harness-engineering/templates/runtime/smoke-task.json +1 -1
  16. package/package-source/docs/codex-harness-engineering/templates/runtime/task.json +1 -1
  17. package/package-source/docs/codex-harness-engineering/templates/runtime/verify.ps1 +29 -12
  18. package/package-source/docs/codex-harness-engineering/templates/tools/harness/task-structure-lint.ps1 +399 -0
  19. package/package-source/install-manifest.json +1 -1
  20. package/package-source/tools/install/bootstrap-codex-harness.ps1 +23 -10
  21. package/package.json +1 -1
@@ -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
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "0.1",
3
3
  "status": "draft",
4
4
  "package": "codex-harness",
5
- "version": "0.1.5",
5
+ "version": "0.1.8",
6
6
  "installModes": [
7
7
  {
8
8
  "name": "user",
@@ -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
- if ([string]::IsNullOrWhiteSpace($taskDocument.runtime.driver)) {
234
- $taskDocument.runtime.driver = "powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\codex-loop.ps1"
235
- $taskDocument | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $taskPath -Encoding UTF8
236
- return [PSCustomObject]@{
237
- Path = $taskPath
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" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pzy560117/codex-harness",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Codex Harness installer and project runtime CLI",
5
5
  "type": "module",
6
6
  "bin": {