@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
package/README.md CHANGED
@@ -26,6 +26,8 @@ npm install -g @pzy560117/codex-harness
26
26
  harness
27
27
  ```
28
28
 
29
+ 执行 `harness init` 时如果发现 `%USERPROFILE%\\.codex\\packages\\codex-harness\\` 的用户级 package cache 缺失或版本落后,会先自动刷新这层共享缓存,再继续项目初始化。
30
+
29
31
  ## Quick Start
30
32
 
31
33
  ### 1. 初始化当前项目
@@ -37,6 +39,7 @@ harness init
37
39
  ```
38
40
 
39
41
  如果当前目录还不是 Git 仓库,CLI 会调用安装器并按默认策略初始化项目所需的 Harness runtime。
42
+ 如果用户级 package cache 版本落后,CLI 会先自动补齐 `install-user.ps1` 对应的共享缓存。
40
43
 
41
44
  ### 2. 运行 doctor
42
45
 
@@ -103,7 +106,7 @@ harness init --plan
103
106
  ### 钉住远程版本初始化
104
107
 
105
108
  ```powershell
106
- npx @pzy560117/codex-harness init --version 0.1.6
109
+ npx @pzy560117/codex-harness init --version 0.1.8
107
110
  ```
108
111
 
109
112
  适合 release smoke 或回滚到某个已发布版本。
@@ -9,7 +9,8 @@ import {
9
9
  getDownloadedShaPath,
10
10
  getDownloadedZipPath,
11
11
  getPackageInstallRoot,
12
- getReleaseAssetNames
12
+ getReleaseAssetNames,
13
+ getUserPackageLockPath
13
14
  } from "../release/cache-layout.js";
14
15
  import { downloadToFile, fileExists } from "../release/download-release.js";
15
16
  import { buildReleaseAssetUrl } from "../release/release-config.js";
@@ -27,6 +28,57 @@ function getCliPackageRoot() {
27
28
  return path.resolve(import.meta.dirname, "..", "..");
28
29
  }
29
30
 
31
+ async function readManifestVersion(packageRoot) {
32
+ const manifestPath = path.join(packageRoot, "install-manifest.json");
33
+ const manifestDocument = JSON.parse(await fs.readFile(manifestPath, "utf8"));
34
+ const version = typeof manifestDocument.version === "string" ? manifestDocument.version.trim() : "";
35
+ if (version === "") {
36
+ throw new Error(`install-manifest.json 缺少 version: ${manifestPath}`);
37
+ }
38
+
39
+ return version;
40
+ }
41
+
42
+ async function readInstalledUserCacheVersion(userProfile) {
43
+ const lockPath = getUserPackageLockPath(userProfile);
44
+ if (!fileExists(lockPath)) {
45
+ return "";
46
+ }
47
+
48
+ try {
49
+ const lockDocument = JSON.parse(await fs.readFile(lockPath, "utf8"));
50
+ return typeof lockDocument.version === "string" ? lockDocument.version.trim() : "";
51
+ } catch {
52
+ return "";
53
+ }
54
+ }
55
+
56
+ export function shouldRefreshUserPackageCache(expectedVersion, installedVersion, installRootExists) {
57
+ if (!installRootExists) {
58
+ return true;
59
+ }
60
+
61
+ return expectedVersion !== installedVersion;
62
+ }
63
+
64
+ async function ensureUserPackageCache(options) {
65
+ const expectedVersion = await readManifestVersion(options.packageRoot);
66
+ const installedVersion = await readInstalledUserCacheVersion(options.userProfile);
67
+ const expectedInstallRoot = getPackageInstallRoot(options.userProfile, expectedVersion);
68
+
69
+ if (!shouldRefreshUserPackageCache(expectedVersion, installedVersion, fileExists(expectedInstallRoot))) {
70
+ return;
71
+ }
72
+
73
+ const scriptPath = path.join(options.packageRoot, "tools", "install", "install-user.ps1");
74
+ process.stdout.write(`刷新用户级 codex-harness 缓存到 ${expectedVersion}...\n`);
75
+ const args = [];
76
+ if (options.force) {
77
+ args.push("-Force");
78
+ }
79
+ await invokePowerShellScript(scriptPath, args, { cwd: options.packageRoot });
80
+ }
81
+
30
82
  export async function initCommand(options) {
31
83
  const localPackageRoot = getLocalPackageRoot();
32
84
  const embeddedPackageRoot = path.join(getCliPackageRoot(), "package-source");
@@ -80,6 +132,14 @@ export async function initCommand(options) {
80
132
  effectivePackageRoot = packageInstallRoot;
81
133
  }
82
134
 
135
+ if (!options.plan) {
136
+ await ensureUserPackageCache({
137
+ packageRoot: effectivePackageRoot,
138
+ userProfile,
139
+ force: options.force
140
+ });
141
+ }
142
+
83
143
  const scriptPath = path.join(effectivePackageRoot, "tools", "install", "install-agent.ps1");
84
144
  const args = ["-ProjectRoot", projectRoot];
85
145
 
@@ -1,13 +1,25 @@
1
1
  export const RELEASE_SOURCE_ENTRIES = [
2
- "AGENTS.md",
3
- "README.md",
4
- "PACKAGE.md",
5
- "install-manifest.json",
6
- "install-manifest.schema.json",
7
- "tools/install",
8
- "docs/codex-harness-engineering/templates"
2
+ { source: "AGENTS.md", destination: "AGENTS.md" },
3
+ {
4
+ source: "docs/codex-harness-engineering/templates/package-assets/root/README.md",
5
+ destination: "README.md"
6
+ },
7
+ {
8
+ source: "docs/codex-harness-engineering/templates/package-assets/root/PACKAGE.md",
9
+ destination: "PACKAGE.md"
10
+ },
11
+ { source: "install-manifest.json", destination: "install-manifest.json" },
12
+ {
13
+ source: "install-manifest.schema.json",
14
+ destination: "install-manifest.schema.json"
15
+ },
16
+ { source: "tools/install", destination: "tools/install" },
17
+ {
18
+ source: "docs/codex-harness-engineering/templates",
19
+ destination: "docs/codex-harness-engineering/templates"
20
+ }
9
21
  ];
10
22
 
11
23
  export function shouldIncludeReleaseSource(relativePath) {
12
- return RELEASE_SOURCE_ENTRIES.includes(relativePath);
24
+ return RELEASE_SOURCE_ENTRIES.some((entry) => entry.source === relativePath || entry.destination === relativePath);
13
25
  }
@@ -7,6 +7,13 @@
7
7
  - 当前仓库根目录下的活跃文档或脚本,只在它本身就是 canonical,或为了验证模板落地效果、保持当前仓库可运行时再同步修改。
8
8
  - 如果一次改动同时涉及根目录活跃文件和模板文件,优先说明哪一份是 canonical,避免只修当前仓库、不修模板源。
9
9
 
10
+ ## 任务结构硬门禁
11
+
12
+ - 未经用户明确同意,不得把标准 `task.json` 模板 phase 压缩成更少任务;正式任务队列至少保留 `ANALYSIS-001`、`TESTCASE-001`、`PLAN-001`。
13
+ - P0/P1 项目禁止跳过 `TESTCASE-001`;缺少 `docs/testing/NATURAL_LANGUAGE_TEST_CASES.md`、`ACCEPTANCE_CRITERIA.md`、`TRACEABILITY_MATRIX.md`、`TEST_DATA_MATRIX.md`、`REGRESSION_PLAN.md`、`verify-matrix.md` 任一项,都不得生成最终 `task.json`。
14
+ - `feature_impl` 没有 `qa_contract` 不得进入正式任务队列;`qa_contract` 没有完整 `tdd_contract`、`development_validation`、`acceptance_validation` 不得开始实现。
15
+ - 单个 `feature_impl` 默认不得覆盖超过 3 条主需求;订单、支付、库存、RBAC 必须拆独立任务;前端和后端不得长期混在同一个 story,除非这是明确的 `release` 任务。
16
+
10
17
  ## 目录级规则
11
18
 
12
19
  - 当某个目录满足以下任一条件时,应优先考虑新增或更新该目录下的 `AGENTS.md`,而不是继续扩充根入口:
@@ -25,19 +25,19 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agen
25
25
  显式 thin project:
26
26
 
27
27
  ```powershell
28
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agent.ps1 -ProjectRoot . -Scope project
28
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Scope project
29
29
  ```
30
30
 
31
31
  显式 legacy full:
32
32
 
33
33
  ```powershell
34
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agent.ps1 -ProjectRoot . -Mode full -Force
34
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Mode full -Force
35
35
  ```
36
36
 
37
37
  显式 vendor:
38
38
 
39
39
  ```powershell
40
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agent.ps1 -ProjectRoot . -Scope vendor
40
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Scope vendor
41
41
  ```
42
42
 
43
43
  ## Smoke
@@ -4,34 +4,9 @@
4
4
 
5
5
  SpecKit 相关 `.specify/`、`workflows/speckit.*` 和 `speckit-*` skills 仍随 full 包安装,但它们用于需求、计划和任务输入生成;方案确认后应转入 `task.json`,再交给 full driver 执行。
6
6
 
7
- ## 云端 CLI 安装
7
+ ## 安装
8
8
 
9
- 全局安装:
10
-
11
- ```powershell
12
- npm install -g @pzy560117/codex-harness
13
- ```
14
-
15
- 一次性使用:
16
-
17
- ```powershell
18
- npx @pzy560117/codex-harness init
19
- ```
20
-
21
- 项目内常用命令:
22
-
23
- ```powershell
24
- harness init
25
- harness doctor
26
- harness verify
27
- harness run
28
- ```
29
-
30
- `harness init` 优先使用 npm 包内置的 `package-source/` 完成初始化;只有在 npm 包未携带内置 source 时,才回退到 GitHub Release 下载 package source 到 `%USERPROFILE%\.codex\packages\codex-harness\<version>\`。项目级 `.agents/skills/` 与 `.agents/workflows/` 仍只安装到当前项目,不会提升成全局 active surface。
31
-
32
- ## 本地源码安装
33
-
34
- 从包含 `agent/` 包目录的项目父目录运行:
9
+ 从包含 当前仓库包根目录的项目父目录运行:
35
10
 
36
11
  ```powershell
37
12
  powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agent-here.ps1 -InitGitIfNeeded
@@ -40,46 +15,38 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agen
40
15
  从已安装的 `.agents/` 自更新:
41
16
 
42
17
  ```powershell
43
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\install\install-agent.ps1 -ProjectRoot . -Mode full -Force
18
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\install/install-agent.ps1 -ProjectRoot . -Mode full -Force
44
19
  ```
45
20
 
46
21
  ## 安装产物
47
22
 
48
23
  - 项目根 `AGENTS.md`
49
- - `tools/harness/` 下的 `codex-loop.ps1`、`doctor.ps1`、`verify.ps1`
50
- - `tools/install/` 下的 `bootstrap-codex-harness.ps1`、`env-check.ps1`、安装脚本
24
+ - `tools/harness/` 下的 `tools/harness/codex-loop.ps1`、`tools/harness/doctor.ps1`、`tools/harness/verify.ps1`
25
+ - `tools/install/` 下的 `tools/install/bootstrap-codex-harness.ps1`、`tools/install/env-check.ps1`、安装脚本
51
26
  - 项目根 `task.json`、`progress.txt`
52
- - `tools/harness/templates/` 下的 `smoke-task.json`、`project-task-template.json`
27
+ - `tools/harness/templates/` 下的 `smoke-task.json`、`tools/harness/templates/project-task-template.json`
53
28
  - `docs/harness/trace.schema.json`
54
29
  - 项目根 `.codex/`
55
- - 项目根 `.agents/` active surface,包括 skills、workflows 和安装后需要的运行辅助面
56
-
57
- 说明:
58
-
59
- - 默认 `thin project` 安装不会把 `docs/harness/`、`docs/testing/`、`docs/knowledge/` 整包复制到项目根。
60
- - 这些深文档默认位于 `.codex-harness/state/config.json` 里的 `packageRoot` 下;只有显式 `-Mode full` / legacy full 场景才会在项目根保留完整副本。
30
+ - 项目根 `docs/harness/`、`docs/testing/`
31
+ - 项目根 `.agents/` 完整能力包,包括 rules、skills、workflows、`.specify/` 和模板复制源
61
32
 
62
33
  ## 使用顺序
63
34
 
64
35
  1. 读取项目根 `AGENTS.md`。
65
- 2. 如果项目根存在 `docs/harness/new-project-usage.md`、`docs/harness/architecture.md` 和 `docs/testing/*`,直接读取;若是 thin project 且这些文件缺失,则先读 `.codex-harness/state/config.json`,再到其中 `packageRoot` 下读取同路径文件。
36
+ 2. `docs/harness/new-project-usage.md`、`docs/harness/architecture.md` 和 `docs/testing/*` 收敛项目上下文。
66
37
  3. 必要时使用 SpecKit workflow 生成 spec / plan / tasks 输入。
67
38
  4. 将确认后的任务写入 `task.json`。
68
39
  5. 运行:
69
40
 
70
41
  ```powershell
71
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\verify.ps1
72
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\codex-loop.ps1
42
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\harness/verify.ps1
43
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\\harness/codex-loop.ps1
73
44
  ```
74
45
 
75
46
  ## 验证安装包
76
47
 
77
48
  ```powershell
78
- powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\release\build-release.ps1
49
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\harness\test-install-modes.ps1
79
50
  ```
80
51
 
81
- 发布产物会输出到 `dist/release/<version>/`,包含:
82
-
83
- - `codex-harness-<version>-win.zip`
84
- - `codex-harness-<version>-win.sha256`
85
- - `release-manifest.json`
52
+ 该脚本现在只验证 full 安装形状。
@@ -57,7 +57,7 @@ function Resolve-TemplateRoot {
57
57
  }
58
58
 
59
59
  $agentsTemplate = Join-Path $resolved "runtime\AGENTS.md"
60
- $driverTemplate = Join-Path $resolved "runtime\codex-loop.ps1"
60
+ $driverTemplate = Join-Path $resolved "runtime\codex-loop.ps1"
61
61
  if ((Test-Path -LiteralPath $agentsTemplate) -and (Test-Path -LiteralPath $driverTemplate)) {
62
62
  return $resolved
63
63
  }
@@ -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
  }
@@ -293,11 +305,25 @@ function Resolve-LockManifestSourcePath {
293
305
  return Join-Path $PackageRoot ($normalizedSource.Replace('/', '\'))
294
306
  }
295
307
 
296
- function Get-FileSha256 {
297
- param([string]$Path)
298
-
299
- return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
300
- }
308
+ function Get-FileSha256 {
309
+ param([string]$Path)
310
+
311
+ $hashAlgorithm = [System.Security.Cryptography.SHA256]::Create()
312
+ try {
313
+ $stream = [System.IO.File]::OpenRead($Path)
314
+ try {
315
+ $hashBytes = $hashAlgorithm.ComputeHash($stream)
316
+ }
317
+ finally {
318
+ $stream.Dispose()
319
+ }
320
+ }
321
+ finally {
322
+ $hashAlgorithm.Dispose()
323
+ }
324
+
325
+ return ([System.BitConverter]::ToString($hashBytes)).Replace('-', '').ToLowerInvariant()
326
+ }
301
327
 
302
328
  function Add-LockManagedFile {
303
329
  param(
@@ -561,13 +587,14 @@ $results = @()
561
587
 
562
588
  $rootFiles = @(
563
589
  @{ Source = "runtime\AGENTS.md"; Destination = "AGENTS.md" },
564
- @{ Source = "tools/install/bootstrap-codex-harness.ps1"; Destination = "tools\\install/bootstrap-codex-harness.ps1" },
565
- @{ Source = "runtime\codex-loop.ps1"; Destination = "tools\\harness/codex-loop.ps1" },
566
- @{ Source = "runtime\doctor.ps1"; Destination = "tools\\harness/doctor.ps1" },
590
+ @{ Source = "bootstrap-codex-harness.ps1"; Destination = "tools\install\bootstrap-codex-harness.ps1" },
591
+ @{ Source = "runtime\codex-loop.ps1"; Destination = "tools\harness\codex-loop.ps1" },
592
+ @{ Source = "runtime\doctor.ps1"; Destination = "tools\harness\doctor.ps1" },
567
593
  @{ Source = "config\codex-config.toml"; Destination = ".codex\config.toml" },
568
594
  @{ Source = "config\codex-readme.md"; Destination = ".codex\README.md" },
569
- @{ Source = "hooks\hooks.json"; Destination = ".codex\hooks.json" },
595
+ @{ Source = "hooks\hooks.json"; Destination = ".codex\hooks.json" },
570
596
  @{ Source = "hooks\hook-stop-verify.ps1"; Destination = "tools\harness\hook-stop-verify.ps1" },
597
+ @{ Source = "scripts\harness\harness-governance-check.ps1"; Destination = "scripts\harness\harness-governance-check.ps1" },
571
598
  @{ Source = "scripts\ai-workflow\check-ai-sync-drift.ps1"; Destination = "tools\install\ai-workflow\check-ai-sync-drift.ps1" },
572
599
  @{ Source = "runtime\task-run-profile.json"; Destination = ".codex\task-run-profile.json" },
573
600
  @{ Source = "prompts\implement-one-task.md"; Destination = ".codex\prompts\implement-one-task.md" },
@@ -586,33 +613,16 @@ $rootFiles = @(
586
613
  @{ Source = "prompts\worker-role\harness-writer.md"; Destination = ".codex\prompts\worker-role\harness-writer.md" },
587
614
  @{ Source = "runtime\smoke-task.json"; Destination = "tools\harness\templates\smoke-task.json" },
588
615
  @{ Source = "runtime\project-task-template.json"; Destination = "tools\harness\templates\project-task-template.json" },
589
- @{ Source = "runtime\verify.ps1"; Destination = "tools\\harness/verify.ps1" },
590
- @{ Source = "tools\harness\spec-lint.ps1"; Destination = "tools\\harness/spec-lint.ps1" },
591
- @{ Source = "tools\harness\docs-lint.ps1"; Destination = "tools\\harness/docs-lint.ps1" },
592
- @{ Source = "tools\harness\data-lint.ps1"; Destination = "tools\\harness/data-lint.ps1" },
593
- @{ Source = "tools\harness\integration-lint.ps1"; Destination = "tools\\harness/integration-lint.ps1" },
594
- @{ Source = "tools\harness\mobile-lint.ps1"; Destination = "tools\\harness/mobile-lint.ps1" },
595
- @{ Source = "tools\harness\acceptance-lint.ps1"; Destination = "tools\\harness/acceptance-lint.ps1" },
596
- @{ Source = "tools\harness\context-lint.ps1"; Destination = "tools\\harness/context-lint.ps1" },
597
- @{ Source = "tools\harness\architecture-lint.ps1"; Destination = "tools\\harness/architecture-lint.ps1" },
598
- @{ Source = "tools\harness\directory-lint.ps1"; Destination = "tools\\harness/directory-lint.ps1" },
599
- @{ Source = "tools\harness\component-lint.ps1"; Destination = "tools\\harness/component-lint.ps1" },
600
- @{ Source = "tools\harness\business-lint.ps1"; Destination = "tools\\harness/business-lint.ps1" },
601
- @{ Source = "tools\harness\contract-lint.ps1"; Destination = "tools\\harness/contract-lint.ps1" },
602
- @{ Source = "tools\harness\state-lint.ps1"; Destination = "tools\\harness/state-lint.ps1" },
603
- @{ Source = "tools\harness\ui-lint.ps1"; Destination = "tools\\harness/ui-lint.ps1" },
604
- @{ Source = "tools\harness\backend-lint.ps1"; Destination = "tools\\harness/backend-lint.ps1" },
605
- @{ Source = "tools\harness\security-lint.ps1"; Destination = "tools\\harness/security-lint.ps1" },
606
- @{ Source = "tools\harness\testing-lint.ps1"; Destination = "tools\\harness/testing-lint.ps1" },
607
- @{ Source = "tools\harness\impact-lint.ps1"; Destination = "tools\\harness/impact-lint.ps1" },
608
- @{ Source = "tools\harness\performance-lint.ps1"; Destination = "tools\\harness/performance-lint.ps1" },
609
- @{ Source = "tools\harness\config-lint.ps1"; Destination = "tools\\harness/config-lint.ps1" },
610
- @{ Source = "tools\harness\observability-lint.ps1"; Destination = "tools\\harness/observability-lint.ps1" },
611
- @{ Source = "tools\harness\refactor-lint.ps1"; Destination = "tools\\harness/refactor-lint.ps1" },
612
- @{ Source = "tools\harness\session-lint.ps1"; Destination = "tools\\harness/session-lint.ps1" },
613
- @{ Source = "tools\harness\style-lint.ps1"; Destination = "tools\\harness/style-lint.ps1" },
614
- @{ Source = "config\tools/install/env-check.ps1"; Destination = "tools\\install/env-check.ps1" },
615
- @{ Source = "trace\docs/harness/trace.schema.json"; Destination = "docs\\harness/trace.schema.json" }
616
+ @{ Source = "tools\harness\task-structure-lint.ps1"; Destination = "tools\harness\task-structure-lint.ps1" },
617
+ @{ Source = "runtime\verify.ps1"; Destination = "tools\harness\verify.ps1" },
618
+ @{ Source = "tools\harness\docs-lint.ps1"; Destination = "tools\harness\docs-lint.ps1" },
619
+ @{ Source = "tools\harness\data-lint.ps1"; Destination = "tools\harness\data-lint.ps1" },
620
+ @{ Source = "tools\harness\integration-lint.ps1"; Destination = "tools\harness\integration-lint.ps1" },
621
+ @{ Source = "tools\harness\mobile-lint.ps1"; Destination = "tools\harness\mobile-lint.ps1" },
622
+ @{ Source = "tools\harness\acceptance-lint.ps1"; Destination = "tools\harness\acceptance-lint.ps1" },
623
+ @{ Source = "tools\harness\session-lint.ps1"; Destination = "tools\harness\session-lint.ps1" },
624
+ @{ Source = "config\env-check.ps1"; Destination = "tools\install\env-check.ps1" },
625
+ @{ Source = "trace\trace.schema.json"; Destination = "docs\harness\trace.schema.json" }
616
626
  )
617
627
 
618
628
  foreach ($file in $rootFiles) {
@@ -735,7 +745,6 @@ if ($Profile -eq "legacy" -and $IncludeDocs) {
735
745
  @{ Source = "docs\prompt-knowledge-integration.md"; Destination = "docs/harness/prompt-knowledge-integration.md" },
736
746
  @{ Source = "docs\mcp-knowledge-governance.md"; Destination = "docs/harness/mcp-knowledge-governance.md" },
737
747
  @{ Source = "docs\code-semantics-and-navigation.md"; Destination = "docs/harness/code-semantics-and-navigation.md" },
738
- @{ Source = "docs\code-style-and-naming.md"; Destination = "docs/harness/code-style-and-naming.md" },
739
748
  @{ Source = "docs\rule-governance.md"; Destination = "docs/harness/rule-governance.md" },
740
749
  @{ Source = "docs\regression-rules.md"; Destination = "docs/harness/regression-rules.md" },
741
750
  @{ Source = "docs\team-knowledge-sync.md"; Destination = "docs/harness/team-knowledge-sync.md" },
@@ -27,6 +27,8 @@
27
27
 
28
28
  `full` 行为作为显式 legacy 兼容入口保留。当前包已经提供 package lock、只读升级/卸载计划、安全 `-Upgrade` 和 vendor `-Uninstall`。新分层入口优先使用 wrapper 或等价 `-Scope user|project|vendor` 命令:`tools\\install/install-user.ps1` 等价于 `tools\\install/install-agent.ps1 -Scope user`,`tools\\install/init-project.ps1` 等价于 `tools\\install/install-agent.ps1 -ProjectRoot . -Scope project`。当前不提供 `harness install-user` 子命令。当前不提供 `harness init-project` 子命令。Windows PowerShell 主链路请直接运行这两个 wrapper,避免误以为存在独立 CLI。`-Scope project` 与无参数 `tools\\install/install-agent.ps1 -ProjectRoot .` 等价,都是新项目默认 thin 输出,只生成项目运行必需 runtime、项目级 `.agents/` active surface、`.codex/`、`.codex-harness/state/config.json`、`.codex-harness/locks/package.lock.json`、`.codex-harness/manifests/install-manifest.json`、`docs/ai/`、`docs/architecture/constraints.md`、`docs/spec/01..14` 和 `tools/`,不复制完整 vendor 镜像、`docs/harness/`、`docs/testing/`、`docs/knowledge/` 或 `docs/requirement-prep-kit/`。`-Scope user` 只安装用户级 package source 并写入用户级 lock。
29
29
 
30
+ 如果你是通过全局 npm CLI 执行 `harness init`,当前实现会先检查 `%USERPROFILE%/.codex/packages/codex-harness/` 的 user package cache;发现缺失或版本落后时,会自动执行等价于 `install-user.ps1` 的刷新,再继续项目初始化。只有在 `harness init --plan` 下,这层缓存不会被改写。
31
+
30
32
  ## `docs/ai/` 导航层
31
33
 
32
34
  - `repo-map.md`
@@ -26,12 +26,16 @@
26
26
  - 标准交接入口是 `tools/harness/templates/project-task-template.json`,首个任务固定为 `INIT-001`,用于锁定 spec 输入、任务依赖、验证矩阵、`execution.mode` 和 owned paths。
27
27
  - 如果 `task.json` 为空文件,先初始化为合法 JSON;如果仍是 smoke 模板或示例任务,先替换为当前项目真实任务。
28
28
  - 交互开发模式可以决定新增或更新哪些待办任务,但实际写入必须委派给 `harness-writer` 等匹配 writer。
29
+ - 未经用户明确同意,不得把标准模板 phase 压缩成更少任务;正式任务队列至少保留 `ANALYSIS-001`、`TESTCASE-001`、`PLAN-001`。
29
30
  - 任务进入 driver 前必须冻结交付语义:`scope`、`non_goals`、`entrypoints`、`inputs_outputs`、`failure_policy`、`rollback_strategy`、`state_surface`、`writeback_targets`。不适用字段必须写 `not_applicable`,不能留给实现会话猜。
30
31
  - 进入实现前,`docs/ai/CURRENT_TASK.md` 必须能回答本轮已完成内容、剩余问题、修改文件、测试结果、风险点和下一步,不允许只靠聊天记忆续跑。
32
+ - `ANALYSIS-001`、`TESTCASE-001`、`PLAN-001` 的 testing truth source 产物必须在生成最终 `task.json` 前补齐:`docs/testing/ACCEPTANCE_CRITERIA.md`、`docs/testing/NATURAL_LANGUAGE_TEST_CASES.md`、`docs/testing/TRACEABILITY_MATRIX.md`、`docs/testing/TEST_DATA_MATRIX.md`、`docs/testing/REGRESSION_PLAN.md`、`docs/testing/verify-matrix.md`。缺任一项都不得进入正式任务队列。
31
33
  - 创建或重建任务队列时必须一次性补齐每个任务的完整交接字段:`requirement_ids`、`owned_paths`、`context_files`、`produces_artifacts`、`test_command`、`acceptance`、自然语言测试用例、测试数据、开发验证、最终验收验证、证据路径和 `qa_contract`。不得创建“先跑起来、后面再补测试/证据/整链路”的实现任务。
32
34
  - PRD / 需求文档完成后、进入实现前必须有自然语言测试用例阶段;每条 P0/P1 需求必须按 `docs/testing/NATURAL_LANGUAGE_TEST_CASES.md` 的需求类型覆盖矩阵满足最小用例数,并回溯到 Requirement ID、PRD 来源、Oracle、测试数据、证据路径和 TDD RED 预期失败。
33
35
  - 每个 `feature_impl` 任务必须声明两段验证:`development_validation` 用于编码过程中的 affected tests、单元 / 组件、局部 API、契约、类型检查或 lint;`acceptance_validation` 用于代码写完后从用户故事入口重新跑完整链路。
34
36
  - 每个 `feature_impl.qa_contract.tdd_contract.red.source_case_ids` 必须能回溯到 `docs/testing/NATURAL_LANGUAGE_TEST_CASES.md`;缺少自然语言用例来源时不得进入实现阶段。
37
+ - 每个 `feature_impl.qa_contract.required_layers` 必须至少包含 `unit_or_component`、`contract_or_api`、`story_full_chain`、`affected_regression`;`tdd_contract` 必须同时包含 `red`、`green`、`refactor_guard`,且 `source_case_ids` 不得为空。
38
+ - 单个 `feature_impl` 默认不得覆盖超过 3 条主需求;订单、支付、库存、RBAC 必须拆成独立任务;前端和后端不得长期混在同一个 story,除非这是明确的 `release` 任务。只有用户明确批准时,才允许在任务中增加 `decomposition_exemption` 记录例外原因。
35
39
  - 不适合作为 TDD RED 的自然语言用例必须通过 `story_full_chain.source_case_ids`、`acceptance_validation.source_case_ids`、回归计划或 `verify-matrix` 进入验收链路;不得只保留在 PRD 或测试设计文档中。
36
40
  - 每个 `feature_impl` 的验收链路必须包含入口、动作、Oracle、副作用/无副作用证据、测试数据、失败态证据和 release 影响。单元测试、小范围 smoke、静态检查或 `git diff --check` 只能作为开发验证的一层,不能替代最终验收链路。
37
41
  - 后端、CLI、worker、数据同步和外部集成类 `feature_impl` 必须按 `static`、`unit`、`chain`、`failure`、`writeback` 五层声明验证;没有 UI 可见结果时,链路验证和失败验证不能省略。
@@ -608,6 +608,57 @@ function Invoke-HarnessGovernanceCheck {
608
608
  }
609
609
  }
610
610
 
611
+ function Invoke-TaskStructureValidation {
612
+ param([string]$Root)
613
+
614
+ $scriptPath = Join-Path $Root "tools\harness\task-structure-lint.ps1"
615
+ if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) {
616
+ return [pscustomobject]@{
617
+ status = "fail"
618
+ findings = @([pscustomobject]@{
619
+ severity = "error"
620
+ code = "missing_task_structure_lint"
621
+ message = "缺少 task structure lint: tools\\harness\\task-structure-lint.ps1"
622
+ task_id = ""
623
+ field = "tools/harness/task-structure-lint.ps1"
624
+ })
625
+ }
626
+ }
627
+
628
+ try {
629
+ return (& powershell -NoProfile -ExecutionPolicy Bypass -File $scriptPath -ProjectRoot $Root -JsonOutput | ConvertFrom-Json)
630
+ }
631
+ catch {
632
+ return [pscustomobject]@{
633
+ status = "fail"
634
+ findings = @([pscustomobject]@{
635
+ severity = "error"
636
+ code = "task_structure_lint_failed"
637
+ message = "task structure lint 执行失败: $($_.Exception.Message)"
638
+ task_id = ""
639
+ field = "task.json"
640
+ })
641
+ }
642
+ }
643
+ }
644
+
645
+ function ConvertTo-TaskStructureFindingText {
646
+ param([object]$ValidationResult)
647
+
648
+ $lines = @()
649
+ foreach ($finding in @($ValidationResult.findings)) {
650
+ $taskLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.task_id)) { "" } else { " task=$($finding.task_id)" }
651
+ $fieldLabel = if ([string]::IsNullOrWhiteSpace([string]$finding.field)) { "" } else { " field=$($finding.field)" }
652
+ $lines += "- [$($finding.severity)] $($finding.code)$taskLabel${fieldLabel}: $($finding.message)"
653
+ }
654
+
655
+ if ($lines.Count -eq 0) {
656
+ return "- (none)"
657
+ }
658
+
659
+ return ($lines -join "`n")
660
+ }
661
+
611
662
  function Block-WithState {
612
663
  param(
613
664
  [string]$Root,
@@ -759,16 +810,31 @@ Initialize task.json with a valid harness task queue, then rerun the driver or v
759
810
  "@
760
811
  }
761
812
 
762
- if ($tasks.Count -eq 0) {
763
- Block-WithState -Root $resolvedProjectRoot -EvidenceKey $evidenceKey -ReasonCode "task_queue_empty" -Reason @"
764
- Harness stop gate: task.json contains no tasks.
765
-
766
- Next action:
767
- Initialize a real harness task queue before ending the session.
768
- "@
769
- }
770
-
771
- $pendingCount = Get-PendingTaskCount -Tasks $tasks
813
+ if ($tasks.Count -eq 0) {
814
+ Block-WithState -Root $resolvedProjectRoot -EvidenceKey $evidenceKey -ReasonCode "task_queue_empty" -Reason @"
815
+ Harness stop gate: task.json contains no tasks.
816
+
817
+ Next action:
818
+ Initialize a real harness task queue before ending the session.
819
+ "@
820
+ }
821
+
822
+ $taskStructureResult = Invoke-TaskStructureValidation -Root $resolvedProjectRoot
823
+ if ([string]$taskStructureResult.status -eq "fail") {
824
+ $findingText = ConvertTo-TaskStructureFindingText -ValidationResult $taskStructureResult
825
+ Block-WithState -Root $resolvedProjectRoot -EvidenceKey $evidenceKey -ReasonCode "task_structure_invalid" -Reason @"
826
+ Harness stop gate: task.json 未通过结构门禁。
827
+
828
+ Findings:
829
+ $findingText
830
+
831
+ Next action:
832
+ 补齐缺失 phase、testing truth source、qa_contract、tdd_contract 或拆分过大的 feature_impl 后,再运行:
833
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\harness\verify.ps1
834
+ "@
835
+ }
836
+
837
+ $pendingCount = Get-PendingTaskCount -Tasks $tasks
772
838
  if ($pendingCount -le 0) {
773
839
  Block-OnTraceEvidenceIfNeeded -Root $resolvedProjectRoot -EvidenceKey $evidenceKey -TraceInfo $latestTraceInfo
774
840
  Exit-Allow
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "runtime": {
3
- "driver": "powershell -NoProfile -ExecutionPolicy Bypass -File .\\tools\\harness\\tools/harness/codex-loop.ps1",
3
+ "driver": "powershell -NoProfile -ExecutionPolicy Bypass -File .\\tools\\harness\\codex-loop.ps1",
4
4
  "run_profile": ".codex\\task-run-profile.json"
5
5
  },
6
6
  "tasks": [
@@ -43,7 +43,7 @@
43
43
  "如有需要补齐 demo 资产",
44
44
  "运行 tools/harness/verify.ps1 验证 demo 闭环"
45
45
  ],
46
- "test_command": "powershell -NoProfile -ExecutionPolicy Bypass -File .\\tools\\harness\\tools/harness/verify.ps1",
46
+ "test_command": "powershell -NoProfile -ExecutionPolicy Bypass -File .\\tools\\harness\\verify.ps1",
47
47
  "acceptance": [
48
48
  "demo 目录同时具备 product、design、frontend architecture、dev-plan、contract、component、story、generated client",
49
49
  "tools/harness/verify.ps1 通过",
@@ -30,6 +30,10 @@
30
30
  - 不要删除或改写已有任务描述,除非用户明确要求替换模板示例或重建任务队列。
31
31
  - 修改后必须运行与改动直接对应的验证;文档改动至少运行 `git diff --check`。
32
32
  - 测试范围从需求收敛阶段开始定义;P0/P1 需求进入实现前必须有可追溯验收、测试数据和证据路径。
33
+ - 未经用户明确同意,不得把标准模板 phase 压缩成更少任务;正式任务队列至少保留 `ANALYSIS-001`、`TESTCASE-001`、`PLAN-001` 三段测试左移与实施计划 phase。
34
+ - P0/P1 任务禁止跳过 `TESTCASE-001`;没有自然语言测试用例、测试数据矩阵、回归计划和验证矩阵,不得生成最终 `task.json`。
35
+ - `feature_impl` 没有 `qa_contract` 不得进入正式 `task.json`;`qa_contract` 没有完整 `tdd_contract`、开发验证和最终验收验证,不得开始实现。
36
+ - 单个 `feature_impl` 默认不得覆盖超过 3 条主需求;订单、支付、库存、RBAC 必须拆成独立任务;前端和后端不得长期混在同一个 story,除非这是明确的 `release` 任务。
33
37
  - 外部系统、开源栈、第三方平台或真实环境集成需求,完成声明必须包含真实依赖接入、成功态证据和失败态证据。
34
38
  - 进入实现前必须有当前项目声明的架构约束 truth source,默认路径为 `docs/architecture/constraints.md`,也可由 `task.json.runtime.handoff.truth_sources.architecture` 指向其他路径。
35
39
  - `feature_impl` 任务必须携带可执行的 `architecture_constraints`、`forbidden_implementations` 和对应验证命令;禁止用测试替身、local-only adapter 或领域原型冒充任务声明的交付路径。