coding-agent-harness 1.0.1 → 1.0.4
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/CHANGELOG.md +44 -0
- package/CONTRIBUTING.md +98 -0
- package/README.en-US.md +14 -0
- package/README.md +230 -80
- package/README.zh-CN.md +290 -0
- package/SKILL.md +132 -198
- package/docs-release/README.md +80 -9
- package/docs-release/architecture/overview.md +298 -28
- package/docs-release/architecture/overview.zh-CN.md +292 -0
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/assets/harness-architecture.svg +163 -0
- package/docs-release/assets/harness-workflow.svg +64 -0
- package/docs-release/guides/agent-installation.en-US.md +237 -0
- package/docs-release/guides/agent-installation.md +149 -27
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +113 -0
- package/docs-release/guides/document-audience-and-surfaces.md +113 -0
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +334 -0
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +334 -0
- package/docs-release/guides/legacy-migration-agent-prompt.md +373 -0
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +350 -0
- package/docs-release/guides/migration-playbook.en-US.md +324 -0
- package/docs-release/guides/migration-playbook.md +328 -0
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +254 -0
- package/docs-release/guides/parent-control-repository-pattern.md +254 -0
- package/docs-release/guides/preset-development.md +214 -0
- package/docs-release/guides/repository-operating-models.en-US.md +197 -0
- package/docs-release/guides/repository-operating-models.md +197 -0
- package/docs-release/guides/task-state-machine.en-US.md +207 -0
- package/docs-release/guides/task-state-machine.md +214 -0
- package/docs-release/intl/README.md +15 -0
- package/docs-release/intl/de-DE.md +18 -0
- package/docs-release/intl/en-US.md +18 -0
- package/docs-release/intl/es-ES.md +18 -0
- package/docs-release/intl/fr-FR.md +18 -0
- package/docs-release/intl/ja-JP.md +18 -0
- package/docs-release/intl/ko-KR.md +18 -0
- package/docs-release/intl/zh-CN.md +18 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/brief.md +13 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/lesson_candidates.md +24 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +4 -2
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/{visual_roadmap.md → visual_map.md} +9 -1
- package/package.json +10 -3
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +5 -5
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +53 -10
- package/references/external-source-intake-standard.md +75 -0
- package/references/harness-ledger.md +53 -94
- package/references/legacy-12-phase-bootstrap.md +41 -0
- package/references/lessons-governance.md +100 -88
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +51 -7
- package/references/project-onboarding-audit.md +10 -0
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +12 -1
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/testing-standard.md +50 -0
- package/references/walkthrough-closeout.md +10 -9
- package/scripts/check-harness.mjs +111 -331
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +96 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +327 -0
- package/scripts/harness.mjs +106 -20
- package/scripts/lib/capability-registry.mjs +591 -0
- package/scripts/lib/check-module-parallel.mjs +237 -0
- package/scripts/lib/check-profiles.mjs +418 -0
- package/scripts/lib/check-task-contracts.mjs +47 -0
- package/scripts/lib/core-shared.mjs +196 -0
- package/scripts/lib/dashboard-data.mjs +412 -0
- package/scripts/lib/dashboard-workbench.mjs +257 -0
- package/scripts/lib/dashboard-writer.mjs +107 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +514 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +15 -1318
- package/scripts/lib/lesson-maintenance.mjs +152 -0
- package/scripts/lib/markdown-utils.mjs +158 -0
- package/scripts/lib/migration-planner.mjs +478 -0
- package/scripts/lib/migration-support.mjs +312 -0
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +497 -0
- package/scripts/lib/preset-registry.mjs +627 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-dashboard-renderer.mjs +102 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-completion-consistency.mjs +16 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +649 -0
- package/scripts/lib/task-review-model.mjs +469 -0
- package/scripts/lib/task-scanner.mjs +576 -0
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/{templates/planning/visual_roadmap.md → skills/preset-creator/references/complex-task-skeleton/visual_map.md} +24 -2
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +51 -36
- package/templates/architecture/Architecture-SSoT.md +21 -0
- package/templates/architecture/README.md +49 -0
- package/templates/architecture/critical-flows.md +22 -0
- package/templates/architecture/local-repo-context.md +20 -0
- package/templates/architecture/service-catalog.md +17 -0
- package/templates/architecture/services/service-template.md +31 -0
- package/templates/architecture/system-map.md +22 -0
- package/templates/dashboard/assets/app-src/00-state.js +42 -0
- package/templates/dashboard/assets/app-src/10-router.js +77 -0
- package/templates/dashboard/assets/app-src/20-overview.js +241 -0
- package/templates/dashboard/assets/app-src/30-tasks.js +409 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
- package/templates/dashboard/assets/app-src/40-modules.js +58 -0
- package/templates/dashboard/assets/app-src/45-review.js +347 -0
- package/templates/dashboard/assets/app-src/50-migration.js +183 -0
- package/templates/dashboard/assets/app-src/60-shared.js +61 -0
- package/templates/dashboard/assets/app-src/90-bindings.js +524 -0
- package/templates/dashboard/assets/app.css +3107 -300
- package/templates/dashboard/assets/app.css.manifest.json +9 -0
- package/templates/dashboard/assets/app.js +2068 -306
- package/templates/dashboard/assets/app.manifest.json +12 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +531 -44
- package/templates/dashboard/assets/mermaid-renderer.js +58 -8
- package/templates/development/README.md +52 -0
- package/templates/development/codebase-map.md +11 -0
- package/templates/development/cross-repo-debugging.md +18 -0
- package/templates/development/external-context/service-template.md +33 -0
- package/templates/development/external-source-packs/README.md +24 -0
- package/templates/development/external-source-packs/digest-template.md +28 -0
- package/templates/development/local-setup.md +16 -0
- package/templates/development/stubs-and-mocks.md +11 -0
- package/templates/integrations/README.md +40 -0
- package/templates/integrations/api-contract.md +42 -0
- package/templates/integrations/event-contract.md +46 -0
- package/templates/integrations/third-party/vendor-template.md +42 -0
- package/templates/integrations/webhook-contract.md +41 -0
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/brief.md +32 -0
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +70 -0
- package/templates/planning/long-running-task-contract.md +7 -0
- package/templates/planning/module_brief.md +25 -0
- package/templates/planning/module_session_prompt.md +6 -0
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +59 -0
- package/templates/planning/task_plan.md +40 -15
- package/templates/planning/visual_map.md +50 -0
- package/templates/reference/docs-library-standard.md +31 -0
- package/templates/reference/execution-workflow-standard.md +5 -2
- package/templates/reference/external-source-intake-standard.md +82 -0
- package/templates/reference/harness-ledger-standard.md +1 -0
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +8 -5
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +3 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +73 -70
- package/templates-zh-CN/architecture/Architecture-SSoT.md +21 -0
- package/templates-zh-CN/architecture/README.md +51 -0
- package/templates-zh-CN/architecture/critical-flows.md +24 -0
- package/templates-zh-CN/architecture/local-repo-context.md +20 -0
- package/templates-zh-CN/architecture/service-catalog.md +17 -0
- package/templates-zh-CN/architecture/services/service-template.md +31 -0
- package/templates-zh-CN/architecture/system-map.md +22 -0
- package/templates-zh-CN/development/README.md +54 -0
- package/templates-zh-CN/development/codebase-map.md +11 -0
- package/templates-zh-CN/development/cross-repo-debugging.md +18 -0
- package/templates-zh-CN/development/external-context/service-template.md +33 -0
- package/templates-zh-CN/development/external-source-packs/README.md +24 -0
- package/templates-zh-CN/development/external-source-packs/digest-template.md +28 -0
- package/templates-zh-CN/development/local-setup.md +16 -0
- package/templates-zh-CN/development/stubs-and-mocks.md +11 -0
- package/templates-zh-CN/integrations/README.md +42 -0
- package/templates-zh-CN/integrations/api-contract.md +42 -0
- package/templates-zh-CN/integrations/event-contract.md +46 -0
- package/templates-zh-CN/integrations/third-party/vendor-template.md +42 -0
- package/templates-zh-CN/integrations/webhook-contract.md +41 -0
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/brief.md +32 -0
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +70 -0
- package/templates-zh-CN/planning/long-running-task-contract.md +1 -1
- package/templates-zh-CN/planning/module_brief.md +25 -0
- package/templates-zh-CN/planning/module_plan.md +2 -2
- package/templates-zh-CN/planning/module_session_prompt.md +4 -3
- package/templates-zh-CN/planning/review.md +59 -1
- package/templates-zh-CN/planning/task_plan.md +37 -11
- package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +36 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +10 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
- package/templates-zh-CN/reference/harness-ledger-standard.md +7 -4
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -1
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +6 -5
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
- package/scripts/smoke-dashboard.mjs +0 -70
- package/scripts/test-harness.mjs +0 -483
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/dashboard/assets/app.css +0 -399
- package/templates-zh-CN/dashboard/assets/app.js +0 -435
- package/templates-zh-CN/dashboard/assets/i18n.js +0 -47
- package/templates-zh-CN/dashboard/assets/markdown-reader.js +0 -116
- package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +0 -59
- package/templates-zh-CN/dashboard/index.html +0 -18
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
- 本轮有没有发现 reference / workflow / checker 不够用或有误:[有/无,写一句理由]
|
|
36
36
|
- 有没有反复出现、跨页面/跨模块/跨阶段的共性问题:[有/无,写一句理由]
|
|
37
37
|
- 有没有下次 agent 也可能重复踩的坑:[有/无,写一句理由]
|
|
38
|
-
- Lessons 结果:[checked-created: L-YYYY-MM-DD-NNN / checked-none: 一句话原因]
|
|
39
|
-
- Lessons Detail Doc:[如 checked-created,填 `docs/01-GOVERNANCE/lessons/...md`;否则写"无"]
|
|
38
|
+
- Lessons 结果:[checked-created: L-YYYY-MM-DD-NNN / queued-promotion: LC-YYYYMMDD-NNN / checked-candidate: LC-YYYYMMDD-NNN / checked-none: 一句话原因]
|
|
39
|
+
- Lessons Detail Doc:[如 checked-created,填 `docs/01-GOVERNANCE/lessons/...md`;如 queued/checked-candidate,填 `lesson_candidates.md` 和任务本地 `lessons/LC-...md`;否则写"无"]
|
|
40
40
|
|
|
41
41
|
## 相关文件
|
|
42
42
|
- Task Plan: [路径]
|
|
@@ -106,22 +106,23 @@ docs/10-WALKTHROUGH/Closeout-SSoT.md
|
|
|
106
106
|
|
|
107
107
|
如果任何一条答案是“有”:
|
|
108
108
|
|
|
109
|
-
1.
|
|
109
|
+
1. 查找任务本地 `lesson_candidates.md`、任务本地 `lessons/LC-*.md` 和 `docs/01-GOVERNANCE/lessons/*.md`
|
|
110
110
|
2. 按 `references/lessons-governance.md` 中的规则处理冲突
|
|
111
|
-
3.
|
|
112
|
-
4.
|
|
113
|
-
5. 在 Closeout SSoT 和 Harness Ledger 中记录 `checked-created: L-YYYY-MM-DD-NNN`
|
|
111
|
+
3. 先在任务目录 `lesson_candidates.md` 中登记候选;候选进入 `needs-promotion` 时同步写任务本地 `lessons/LC-*.md` 详情文件并在 `Detail Artifact` 链接
|
|
112
|
+
4. 人工确认后,如需沉淀,使用 maintenance CLI 写入 `docs/01-GOVERNANCE/lessons/` promoted 详情文档
|
|
113
|
+
5. 在 Closeout SSoT 和 Harness Ledger 中记录 `queued-promotion: LC-...` 或 `checked-created: L-YYYY-MM-DD-NNN`
|
|
114
114
|
|
|
115
115
|
如果所有答案都是“没有”,不能静默跳过;在 Closeout SSoT 和 Harness Ledger 中记录
|
|
116
|
+
新任务在 `lesson_candidates.md` 中写 `no-candidate-accepted` 和 No-Candidate Reason;旧任务可记录
|
|
116
117
|
`checked-none: <一句话原因>`。
|
|
117
118
|
|
|
118
119
|
## Harness Ledger 回写
|
|
119
120
|
|
|
120
|
-
写完 Walkthrough、更新
|
|
121
|
-
`docs/
|
|
121
|
+
写完 Walkthrough、更新 Regression SSoT 或其他本轮实际触达的非任务生命周期 SSoT,并完成 Lessons 检查后,Agent 必须更新
|
|
122
|
+
`docs/10-WALKTHROUGH/Closeout-SSoT.md`;任务生命周期总账由 CLI 重新生成 `docs/Harness-Ledger.md`:
|
|
122
123
|
|
|
123
124
|
1. 为本轮任务追加或更新对应 `HL-*` 条目
|
|
124
|
-
2. 记录 Task Plan、
|
|
125
|
+
2. 记录 Task Plan、Regression SSoT、Review Report、Walkthrough、Lessons Check 的结果
|
|
125
126
|
3. 列出本轮触碰的 harness 文档
|
|
126
127
|
4. 如有未完成项,使用 `missing` 或 `skipped-with-reason` 并写明 residual
|
|
127
128
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { checkModuleParallelStructure } from "./lib/check-module-parallel.mjs";
|
|
5
6
|
|
|
6
7
|
const targetRoot = path.resolve(process.argv[2] || process.cwd());
|
|
7
8
|
const requireGlobalModuleSync = process.env.HARNESS_REQUIRE_GLOBAL_MODULE_SYNC === "1";
|
|
@@ -23,7 +24,6 @@ const requiredFiles = [
|
|
|
23
24
|
"docs/10-WALKTHROUGH/Closeout-SSoT.md",
|
|
24
25
|
"docs/05-TEST-QA/Regression-SSoT.md",
|
|
25
26
|
"docs/05-TEST-QA/Cadence-Ledger.md",
|
|
26
|
-
"docs/01-GOVERNANCE/Lessons-SSoT.md",
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
const legacyPlanningFiles = [
|
|
@@ -41,7 +41,6 @@ const agAgentsRefs = [
|
|
|
41
41
|
"adversarial-review-standard.md",
|
|
42
42
|
"review-routing-standard.md",
|
|
43
43
|
"walkthrough-standard.md",
|
|
44
|
-
"Lessons-SSoT.md",
|
|
45
44
|
"harness-ledger-standard.md",
|
|
46
45
|
"Closeout-SSoT.md",
|
|
47
46
|
];
|
|
@@ -63,6 +62,8 @@ const allowedWalkthroughSkip =
|
|
|
63
62
|
/walkthrough skipped-with-reason:\s*(docs-only|no-runtime|superseded|historical-backfill|owner-deferred)/i;
|
|
64
63
|
const lessonsCreatedPattern = /checked-created:\s*(L-\d{4}-\d{2}-\d{2}-\d{3}|L-\d+)/i;
|
|
65
64
|
const lessonsNonePattern = /checked-none:\s*\S+/i;
|
|
65
|
+
const lessonsCandidatePattern = /checked-candidate:\s*(LC-[A-Za-z0-9-]+)/i;
|
|
66
|
+
const lessonsQueuedPromotionPattern = /queued-promotion:\s*(LC-[A-Za-z0-9-]+)/i;
|
|
66
67
|
|
|
67
68
|
const failures = [];
|
|
68
69
|
const warnings = [];
|
|
@@ -107,7 +108,18 @@ function checkRequiredFiles() {
|
|
|
107
108
|
|
|
108
109
|
function checkPlanningStructure() {
|
|
109
110
|
if (exists("docs/09-PLANNING/Module-Registry.md")) {
|
|
110
|
-
checkModuleParallelStructure(
|
|
111
|
+
checkModuleParallelStructure({
|
|
112
|
+
exists,
|
|
113
|
+
fail,
|
|
114
|
+
filePath,
|
|
115
|
+
markdownTable,
|
|
116
|
+
read,
|
|
117
|
+
rel,
|
|
118
|
+
requireFile,
|
|
119
|
+
requireGlobalModuleSync,
|
|
120
|
+
targetRoot,
|
|
121
|
+
warn,
|
|
122
|
+
});
|
|
111
123
|
return;
|
|
112
124
|
}
|
|
113
125
|
for (const legacyFile of legacyPlanningFiles) {
|
|
@@ -115,239 +127,6 @@ function checkPlanningStructure() {
|
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
|
-
function stripMarkdownCode(value) {
|
|
119
|
-
return String(value || "").replace(/`/g, "").trim();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function modulePromptBlock(content, key) {
|
|
123
|
-
const heading = `## Module: ${key}`;
|
|
124
|
-
const start = content.indexOf(heading);
|
|
125
|
-
if (start < 0) return "";
|
|
126
|
-
const rest = content.slice(start + heading.length);
|
|
127
|
-
const next = rest.search(/\n## Module: /);
|
|
128
|
-
return next >= 0 ? rest.slice(0, next) : rest;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function checkModuleParallelStructure() {
|
|
132
|
-
if (!exists("docs/09-PLANNING/Module-Registry.md")) return;
|
|
133
|
-
|
|
134
|
-
requireFile("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
135
|
-
const hasPromptPack = exists("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
136
|
-
for (const templateFile of [
|
|
137
|
-
"docs/09-PLANNING/MODULES/_task-template/task_plan.md",
|
|
138
|
-
"docs/09-PLANNING/MODULES/_task-template/progress.md",
|
|
139
|
-
"docs/09-PLANNING/MODULES/_task-template/findings.md",
|
|
140
|
-
"docs/09-PLANNING/MODULES/_task-template/review.md",
|
|
141
|
-
]) {
|
|
142
|
-
requireFile(templateFile);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const registryContent = read("docs/09-PLANNING/Module-Registry.md");
|
|
146
|
-
for (const term of ["PREFIX", "Current Step", "Status", "Write Scope"]) {
|
|
147
|
-
if (!registryContent.includes(term)) {
|
|
148
|
-
fail(`docs/09-PLANNING/Module-Registry.md missing registry column or section: ${term}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const registryRows = markdownTable(registryContent)
|
|
153
|
-
.filter((cells) => cells.length >= 6)
|
|
154
|
-
.filter((cells) => /^(_shared|[a-z][a-z0-9-]*)$/.test(cells[0] || "") && /^[A-Z]{2,5}$/.test(cells[2] || ""));
|
|
155
|
-
|
|
156
|
-
if (registryRows.length === 0) {
|
|
157
|
-
fail("docs/09-PLANNING/Module-Registry.md has no active module rows");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const promptPack = hasPromptPack ? read("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md") : "";
|
|
161
|
-
if (hasPromptPack && !/Subagent Worker Invariant|worker[\s\S]{0,120}worktree[\s\S]{0,120}commit SHA/i.test(promptPack)) {
|
|
162
|
-
fail("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md missing subagent worker worktree/commit handoff rule");
|
|
163
|
-
}
|
|
164
|
-
for (const cells of registryRows) {
|
|
165
|
-
const [key, , prefix, branch, currentStep, status] = cells;
|
|
166
|
-
requireFile(`docs/09-PLANNING/MODULES/${key}/module_plan.md`);
|
|
167
|
-
if (!/^(planned|in-progress|paused|completed)$/.test(status)) {
|
|
168
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} has invalid status: ${status}`);
|
|
169
|
-
}
|
|
170
|
-
if (currentStep !== `${prefix}-00` && !currentStep.startsWith(`${prefix}-`)) {
|
|
171
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} current step does not match prefix ${prefix}: ${currentStep}`);
|
|
172
|
-
}
|
|
173
|
-
const branchName = stripMarkdownCode(branch);
|
|
174
|
-
if (!branchName.startsWith("codex/")) {
|
|
175
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} branch must use codex/ prefix: ${branch}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const block = modulePromptBlock(promptPack, key);
|
|
179
|
-
if (!block) {
|
|
180
|
-
if (!exists(`docs/09-PLANNING/MODULES/${key}/session_prompt.md`)) {
|
|
181
|
-
fail(`missing module session prompt for ${key}`);
|
|
182
|
-
}
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
for (const term of [
|
|
186
|
-
"Current Step",
|
|
187
|
-
branchName,
|
|
188
|
-
"Preflight:",
|
|
189
|
-
"Before code edits:",
|
|
190
|
-
"Write scope:",
|
|
191
|
-
"Forbidden without coordination:",
|
|
192
|
-
"Shared Coordination:",
|
|
193
|
-
"Verification:",
|
|
194
|
-
"Closeout:",
|
|
195
|
-
"Stop conditions:",
|
|
196
|
-
]) {
|
|
197
|
-
if (!block.includes(term)) {
|
|
198
|
-
fail(`module session prompt for ${key} missing required term: ${term}`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
checkModuleTaskSsotIndex(registryRows);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function checkModuleTaskSsotIndex(registryRows) {
|
|
206
|
-
const registryByModule = new Map(registryRows.map((cells) => [cells[0], cells]));
|
|
207
|
-
const ledgerContent = exists("docs/Harness-Ledger.md") ? read("docs/Harness-Ledger.md") : "";
|
|
208
|
-
const taskPlans = listModuleTaskPlans();
|
|
209
|
-
|
|
210
|
-
for (const taskPlanPath of taskPlans) {
|
|
211
|
-
const parsed = parseModuleTaskPath(taskPlanPath);
|
|
212
|
-
if (!parsed) continue;
|
|
213
|
-
const { moduleKey, taskDir } = parsed;
|
|
214
|
-
const modulePlanPath = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
|
|
215
|
-
if (!exists(modulePlanPath)) continue;
|
|
216
|
-
|
|
217
|
-
const taskPlan = read(taskPlanPath);
|
|
218
|
-
const taskProgress = readTaskProgress(taskPlanPath);
|
|
219
|
-
const taskProgressStatus = readTaskProgressStatus(taskPlanPath);
|
|
220
|
-
const taskIsActive = isActiveModuleTaskStatus(taskProgressStatus);
|
|
221
|
-
const stepId = extractStepId(taskPlan, taskDir);
|
|
222
|
-
if (!stepId) {
|
|
223
|
-
if (taskIsActive) {
|
|
224
|
-
fail(`${taskPlanPath} does not expose a Step ID and task directory does not start with <PREFIX-NN>`);
|
|
225
|
-
}
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const modulePlan = read(modulePlanPath);
|
|
230
|
-
const moduleRelativeTaskPlan = `TASKS/${taskDir}/task_plan.md`;
|
|
231
|
-
if (!modulePlan.includes(stepId) || !modulePlan.includes(moduleRelativeTaskPlan)) {
|
|
232
|
-
fail(`${modulePlanPath} does not index ${stepId} task plan ${moduleRelativeTaskPlan}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!taskIsActive) continue;
|
|
236
|
-
|
|
237
|
-
const registryRow = registryByModule.get(moduleKey);
|
|
238
|
-
const reviewPath = taskPlanPath.replace(/task_plan\.md$/, "review.md");
|
|
239
|
-
const registrySynced = Boolean(registryRow && registryRow[4] === stepId);
|
|
240
|
-
const ledgerSynced = ledgerContent.includes(taskPlanPath) && (!exists(reviewPath) || ledgerContent.includes(reviewPath));
|
|
241
|
-
if (registrySynced && ledgerSynced) continue;
|
|
242
|
-
|
|
243
|
-
if (requireGlobalModuleSync) {
|
|
244
|
-
if (!registryRow) {
|
|
245
|
-
fail(`docs/09-PLANNING/Module-Registry.md does not include active module ${moduleKey} for ${taskPlanPath}`);
|
|
246
|
-
} else if (registryRow[4] !== stepId) {
|
|
247
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${moduleKey} current step is ${registryRow[4]}, but active task is ${stepId}`);
|
|
248
|
-
}
|
|
249
|
-
if (!ledgerContent.includes(taskPlanPath)) {
|
|
250
|
-
fail(`docs/Harness-Ledger.md does not index active module task plan ${taskPlanPath}`);
|
|
251
|
-
}
|
|
252
|
-
if (exists(reviewPath) && !ledgerContent.includes(reviewPath)) {
|
|
253
|
-
fail(`docs/Harness-Ledger.md does not index active module review ${reviewPath}`);
|
|
254
|
-
}
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (hasPendingCoordinatorHandoff(taskPlan, taskProgress)) {
|
|
259
|
-
warn(`${taskPlanPath} has pending coordinator handoff; run coordinator pass before final integration or set HARNESS_REQUIRE_GLOBAL_MODULE_SYNC=1 for strict gate`);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
fail(`${taskPlanPath} is active but is neither globally synced nor marked with Coordinator Handoff: pending-coordinator-pass`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function listModuleTaskPlans() {
|
|
267
|
-
const modulesRoot = filePath("docs/09-PLANNING/MODULES");
|
|
268
|
-
if (!fs.existsSync(modulesRoot)) return [];
|
|
269
|
-
const results = [];
|
|
270
|
-
function walk(dir) {
|
|
271
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
272
|
-
const full = path.join(dir, entry);
|
|
273
|
-
const relativePath = rel(path.relative(targetRoot, full));
|
|
274
|
-
const stat = fs.statSync(full);
|
|
275
|
-
if (stat.isDirectory()) {
|
|
276
|
-
if (relativePath.includes("/_archive/") || relativePath.endsWith("/_task-template")) continue;
|
|
277
|
-
walk(full);
|
|
278
|
-
} else if (/\/TASKS\/[^/]+\/task_plan\.md$/.test(relativePath)) {
|
|
279
|
-
results.push(relativePath);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
walk(modulesRoot);
|
|
284
|
-
return results;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function parseModuleTaskPath(taskPlanPath) {
|
|
288
|
-
const match = taskPlanPath.match(/^docs\/09-PLANNING\/MODULES\/([^/]+)\/TASKS\/([^/]+)\/task_plan\.md$/);
|
|
289
|
-
if (!match) return null;
|
|
290
|
-
return { moduleKey: match[1], taskDir: match[2] };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function extractStepId(taskPlanContent, taskDir) {
|
|
294
|
-
const fromPlan = taskPlanContent.match(/^- Step ID:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
295
|
-
if (fromPlan) return fromPlan[1];
|
|
296
|
-
const fromModuleSection = taskPlanContent.match(/^- Step:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
297
|
-
if (fromModuleSection) return fromModuleSection[1];
|
|
298
|
-
const fromDir = taskDir.match(/^([A-Z]{2,5}-\d{2})-/);
|
|
299
|
-
return fromDir ? fromDir[1] : "";
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function readTaskProgress(taskPlanPath) {
|
|
303
|
-
const progressPath = taskPlanPath.replace(/task_plan\.md$/, "progress.md");
|
|
304
|
-
return exists(progressPath) ? read(progressPath) : "";
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function readTaskProgressStatus(taskPlanPath) {
|
|
308
|
-
const progress = readTaskProgress(taskPlanPath);
|
|
309
|
-
if (!progress) return "";
|
|
310
|
-
const match = progress.match(/^##\s*(?:Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
311
|
-
return match ? normalizeModuleTaskStatus(stripMarkdownCode(match[1])) : "";
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function normalizeModuleTaskStatus(status) {
|
|
315
|
-
const value = String(status || "").trim().toLowerCase();
|
|
316
|
-
const aliases = new Map([
|
|
317
|
-
["未开始", "not-started"],
|
|
318
|
-
["未启动", "not-started"],
|
|
319
|
-
["进行中", "in-progress"],
|
|
320
|
-
["开发中", "in-progress"],
|
|
321
|
-
["规划审查", "planning-review"],
|
|
322
|
-
["已完成", "completed"],
|
|
323
|
-
["完成", "completed"],
|
|
324
|
-
["已关闭", "closed"],
|
|
325
|
-
["关闭", "closed"],
|
|
326
|
-
["已阻塞", "blocked"],
|
|
327
|
-
["阻塞", "blocked"],
|
|
328
|
-
]);
|
|
329
|
-
return aliases.get(value) || value;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function isActiveModuleTaskStatus(status) {
|
|
333
|
-
if (!status) return false;
|
|
334
|
-
return !new Set([
|
|
335
|
-
"not-started",
|
|
336
|
-
"blocked-not-started",
|
|
337
|
-
"complete",
|
|
338
|
-
"completed",
|
|
339
|
-
"closed",
|
|
340
|
-
"closed-with-residual",
|
|
341
|
-
"closed-local-only",
|
|
342
|
-
"superseded",
|
|
343
|
-
]).has(status);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function hasPendingCoordinatorHandoff(taskPlanContent, progressContent) {
|
|
347
|
-
const combined = `${taskPlanContent}\n${progressContent}`;
|
|
348
|
-
return /Coordinator Handoff/i.test(combined) && /pending-coordinator-pass/i.test(combined);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
130
|
function checkAgentsIndex() {
|
|
352
131
|
if (!exists("AGENTS.md")) return;
|
|
353
132
|
const content = read("AGENTS.md");
|
|
@@ -387,15 +166,15 @@ function checkGovernanceContent() {
|
|
|
387
166
|
if (!exists(governancePath)) return;
|
|
388
167
|
const content = read(governancePath);
|
|
389
168
|
const requiredTerms = [
|
|
390
|
-
"Repo Platform Profile",
|
|
391
|
-
"Branch Model",
|
|
392
|
-
"PR Policy",
|
|
393
|
-
"Required Checks",
|
|
394
|
-
"Branch Protection",
|
|
395
|
-
"Worktree Concurrency",
|
|
169
|
+
["Repo Platform Profile", "仓库平台画像"],
|
|
170
|
+
["Branch Model", "分支模型"],
|
|
171
|
+
["PR Policy", "PR 规则"],
|
|
172
|
+
["Required Checks", "必要检查"],
|
|
173
|
+
["Branch Protection", "分支保护"],
|
|
174
|
+
["Worktree Concurrency", "Worktree 并发"],
|
|
396
175
|
];
|
|
397
|
-
for (const
|
|
398
|
-
if (!content
|
|
176
|
+
for (const terms of requiredTerms) {
|
|
177
|
+
if (!contentIncludesAny(content, terms)) fail(`${governancePath} missing section: ${terms[0]}`);
|
|
399
178
|
}
|
|
400
179
|
if (!statusWords.some((status) => content.includes(status))) {
|
|
401
180
|
fail(`${governancePath} does not use evidence status model`);
|
|
@@ -408,13 +187,13 @@ function checkCiCdContent() {
|
|
|
408
187
|
if (!exists(ciPath)) return;
|
|
409
188
|
const content = read(ciPath);
|
|
410
189
|
const requiredTerms = [
|
|
411
|
-
"CI Profile",
|
|
412
|
-
"Workflow",
|
|
413
|
-
"Required Checks",
|
|
414
|
-
"Evidence Status",
|
|
190
|
+
["CI Profile", "CI 画像"],
|
|
191
|
+
["Workflow", "工作流"],
|
|
192
|
+
["Required Checks", "必要检查"],
|
|
193
|
+
["Evidence Status", "证据状态"],
|
|
415
194
|
];
|
|
416
|
-
for (const
|
|
417
|
-
if (!content
|
|
195
|
+
for (const terms of requiredTerms) {
|
|
196
|
+
if (!contentIncludesAny(content, terms)) fail(`${ciPath} missing section: ${terms[0]}`);
|
|
418
197
|
}
|
|
419
198
|
if (!statusWords.some((status) => content.includes(status))) {
|
|
420
199
|
fail(`${ciPath} does not use evidence status model`);
|
|
@@ -428,14 +207,14 @@ function checkDeliveryOperatingModelContent() {
|
|
|
428
207
|
const content = read(deliveryPath);
|
|
429
208
|
const normalized = content.toLowerCase();
|
|
430
209
|
const requiredTerms = [
|
|
431
|
-
"operating model profile",
|
|
432
|
-
"work decomposition rule",
|
|
433
|
-
"agent visibility",
|
|
434
|
-
"integration owner",
|
|
435
|
-
"delivery ssot",
|
|
210
|
+
["operating model profile", "运行模型"],
|
|
211
|
+
["work decomposition rule", "工作拆分规则"],
|
|
212
|
+
["agent visibility", "agent 可见性"],
|
|
213
|
+
["integration owner", "集成 owner"],
|
|
214
|
+
["delivery ssot", "交付 ssot"],
|
|
436
215
|
];
|
|
437
|
-
for (const
|
|
438
|
-
if (!normalized.
|
|
216
|
+
for (const terms of requiredTerms) {
|
|
217
|
+
if (!contentIncludesAny(normalized, terms.map((term) => term.toLowerCase()))) fail(`${deliveryPath} missing section: ${terms[0]}`);
|
|
439
218
|
}
|
|
440
219
|
if (!/solo-orchestrator|team-feature-lead|split-repo-contract|program-multi-repo|waterfall-stage-gate|kanban-continuous/.test(content)) {
|
|
441
220
|
fail(`${deliveryPath} does not define a recognized operating model`);
|
|
@@ -480,7 +259,7 @@ function checkReviewTemplate() {
|
|
|
480
259
|
const reviewPath = "docs/09-PLANNING/TASKS/_task-template/review.md";
|
|
481
260
|
if (!exists(reviewPath)) return;
|
|
482
261
|
const content = read(reviewPath);
|
|
483
|
-
if (!content
|
|
262
|
+
if (!contentIncludesAny(content, ["Confidence Challenge", "信心挑战"])) {
|
|
484
263
|
fail(`${reviewPath} missing Confidence Challenge`);
|
|
485
264
|
}
|
|
486
265
|
if (/\|\s*R-001\s*\|\s*P[01]\s*\|.*\|\s*open\s*\|/i.test(content)) {
|
|
@@ -515,8 +294,12 @@ function findHeaderIndex(rows, pattern) {
|
|
|
515
294
|
return rows.findIndex((cells) => cells.some((cell) => pattern.test(cell)));
|
|
516
295
|
}
|
|
517
296
|
|
|
518
|
-
function
|
|
519
|
-
return
|
|
297
|
+
function contentIncludesAny(content, terms) {
|
|
298
|
+
return terms.some((term) => (term instanceof RegExp ? term.test(content) : content.includes(term)));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function columnIndexAny(header, patterns) {
|
|
302
|
+
return header.findIndex((cell) => patterns.some((pattern) => pattern.test(cell)));
|
|
520
303
|
}
|
|
521
304
|
|
|
522
305
|
function checkDuplicateIds(rows, sourcePath) {
|
|
@@ -535,9 +318,9 @@ function checkCloseoutSsot() {
|
|
|
535
318
|
if (!exists(closeoutPath)) return;
|
|
536
319
|
|
|
537
320
|
const closeoutContent = read(closeoutPath);
|
|
538
|
-
for (const
|
|
539
|
-
if (!closeoutContent
|
|
540
|
-
fail(`${closeoutPath} missing required closeout column or section: ${
|
|
321
|
+
for (const terms of [["Walkthrough"], ["Lessons Check", "Lessons 检查"], ["Closeout Status", "收口状态"]]) {
|
|
322
|
+
if (!contentIncludesAny(closeoutContent, terms)) {
|
|
323
|
+
fail(`${closeoutPath} missing required closeout column or section: ${terms[0]}`);
|
|
541
324
|
}
|
|
542
325
|
}
|
|
543
326
|
checkNoGenericPlaceholders(closeoutPath);
|
|
@@ -545,7 +328,7 @@ function checkCloseoutSsot() {
|
|
|
545
328
|
const closeoutTable = markdownTable(closeoutContent);
|
|
546
329
|
const closeoutHeaderIndex = findHeaderIndex(closeoutTable, /^Harness ID$/i);
|
|
547
330
|
const closeoutHeader = closeoutHeaderIndex >= 0 ? closeoutTable[closeoutHeaderIndex] : [];
|
|
548
|
-
const lessonsColumn =
|
|
331
|
+
const lessonsColumn = columnIndexAny(closeoutHeader, [/^Lessons Check$/i, /^Lessons 检查$/i, /^Lessons 检查\s*\/\s*Lessons Check$/i]);
|
|
549
332
|
if (lessonsColumn < 0) {
|
|
550
333
|
fail(`${closeoutPath} missing Lessons Check column`);
|
|
551
334
|
}
|
|
@@ -553,10 +336,11 @@ function checkCloseoutSsot() {
|
|
|
553
336
|
if (!exists("docs/Harness-Ledger.md")) return;
|
|
554
337
|
const ledgerContent = read("docs/Harness-Ledger.md");
|
|
555
338
|
const lessonIds = collectLessonIds();
|
|
339
|
+
const lessonCandidateIds = collectLessonCandidateIds();
|
|
556
340
|
const ledgerTable = markdownTable(ledgerContent);
|
|
557
341
|
const ledgerHeaderIndex = findHeaderIndex(ledgerTable, /^ID$/i);
|
|
558
342
|
const ledgerHeader = ledgerHeaderIndex >= 0 ? ledgerTable[ledgerHeaderIndex] : [];
|
|
559
|
-
const ledgerLessonsColumn =
|
|
343
|
+
const ledgerLessonsColumn = columnIndexAny(ledgerHeader, [/^Lessons Check$/i, /^Lessons 检查$/i, /^Lessons 检查\s*\/\s*Lessons Check$/i]);
|
|
560
344
|
if (ledgerLessonsColumn < 0) {
|
|
561
345
|
fail("docs/Harness-Ledger.md missing Lessons Check column");
|
|
562
346
|
}
|
|
@@ -587,86 +371,82 @@ function checkCloseoutSsot() {
|
|
|
587
371
|
if (lessonsColumn >= 0) {
|
|
588
372
|
const lessonsCheck = closeout[lessonsColumn] || "";
|
|
589
373
|
const createdMatch = lessonsCheck.match(lessonsCreatedPattern);
|
|
590
|
-
|
|
591
|
-
|
|
374
|
+
const candidateMatch = lessonsCheck.match(lessonsCandidatePattern) || lessonsCheck.match(lessonsQueuedPromotionPattern);
|
|
375
|
+
if (!createdMatch && !candidateMatch && !lessonsNonePattern.test(lessonsCheck)) {
|
|
376
|
+
fail(`${closeoutPath} row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
|
|
592
377
|
} else if (createdMatch && !lessonIds.has(createdMatch[1])) {
|
|
593
|
-
fail(`${closeoutPath} row ${id} references missing
|
|
378
|
+
fail(`${closeoutPath} row ${id} references missing lesson detail doc id: ${createdMatch[1]}`);
|
|
379
|
+
} else if (candidateMatch && !lessonCandidateIds.has(candidateMatch[1])) {
|
|
380
|
+
fail(`${closeoutPath} row ${id} references missing lesson candidate id: ${candidateMatch[1]}`);
|
|
594
381
|
}
|
|
595
382
|
}
|
|
596
383
|
|
|
597
384
|
if (ledgerLessonsColumn >= 0) {
|
|
598
385
|
const ledgerLessonsCheck = cells[ledgerLessonsColumn] || "";
|
|
599
386
|
const ledgerCreatedMatch = ledgerLessonsCheck.match(lessonsCreatedPattern);
|
|
600
|
-
|
|
601
|
-
|
|
387
|
+
const ledgerCandidateMatch = ledgerLessonsCheck.match(lessonsCandidatePattern) || ledgerLessonsCheck.match(lessonsQueuedPromotionPattern);
|
|
388
|
+
if (!ledgerCreatedMatch && !ledgerCandidateMatch && !lessonsNonePattern.test(ledgerLessonsCheck)) {
|
|
389
|
+
fail(`docs/Harness-Ledger.md row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
|
|
602
390
|
} else if (ledgerCreatedMatch && !lessonIds.has(ledgerCreatedMatch[1])) {
|
|
603
|
-
fail(`docs/Harness-Ledger.md row ${id} references missing
|
|
391
|
+
fail(`docs/Harness-Ledger.md row ${id} references missing lesson detail doc id: ${ledgerCreatedMatch[1]}`);
|
|
392
|
+
} else if (ledgerCandidateMatch && !lessonCandidateIds.has(ledgerCandidateMatch[1])) {
|
|
393
|
+
fail(`docs/Harness-Ledger.md row ${id} references missing lesson candidate id: ${ledgerCandidateMatch[1]}`);
|
|
604
394
|
}
|
|
605
395
|
}
|
|
606
396
|
}
|
|
607
397
|
}
|
|
608
398
|
|
|
609
399
|
function collectLessonIds() {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
400
|
+
const root = filePath("docs/01-GOVERNANCE/lessons");
|
|
401
|
+
const ids = new Set();
|
|
402
|
+
if (!fs.existsSync(root)) return ids;
|
|
403
|
+
for (const entry of fs.readdirSync(root)) {
|
|
404
|
+
if (!entry.endsWith(".md")) continue;
|
|
405
|
+
const full = path.join(root, entry);
|
|
406
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
407
|
+
const content = fs.readFileSync(full, "utf8");
|
|
408
|
+
const pathMatch = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
|
|
409
|
+
const titleMatch = content.match(/#\s*(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
|
|
410
|
+
const id = titleMatch?.[1] || pathMatch?.[1];
|
|
411
|
+
if (id) ids.add(id);
|
|
412
|
+
}
|
|
413
|
+
return ids;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function collectLessonCandidateIds() {
|
|
417
|
+
const root = filePath("docs/09-PLANNING");
|
|
418
|
+
const ids = new Set();
|
|
419
|
+
if (!fs.existsSync(root)) return ids;
|
|
420
|
+
function visit(dir) {
|
|
421
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
422
|
+
const full = path.join(dir, entry);
|
|
423
|
+
const stat = fs.statSync(full);
|
|
424
|
+
if (stat.isDirectory()) {
|
|
425
|
+
if (entry === "_archive") continue;
|
|
426
|
+
visit(full);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (entry !== "lesson_candidates.md") continue;
|
|
430
|
+
const content = fs.readFileSync(full, "utf8");
|
|
431
|
+
for (const match of content.matchAll(/\|\s*(LC-[A-Za-z0-9-]+)\s*\|/g)) {
|
|
432
|
+
ids.add(match[1]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
visit(root);
|
|
437
|
+
return ids;
|
|
622
438
|
}
|
|
623
439
|
|
|
624
|
-
function
|
|
625
|
-
const
|
|
626
|
-
if (!
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const headerIndex = findHeaderIndex(table, /^ID$/i);
|
|
635
|
-
const header = headerIndex >= 0 ? table[headerIndex] : [];
|
|
636
|
-
const detailColumn = columnIndex(header, /^(Detail Doc|Detail)$/i);
|
|
637
|
-
const idColumn = columnIndex(header, /^ID$/i);
|
|
638
|
-
const statusColumn = columnIndex(header, /^Status$/i);
|
|
639
|
-
if (idColumn < 0) fail(`${lessonsPath} missing ID column`);
|
|
640
|
-
if (detailColumn < 0) fail(`${lessonsPath} missing Detail Doc column`);
|
|
641
|
-
if (statusColumn < 0) fail(`${lessonsPath} missing Status column`);
|
|
642
|
-
if (idColumn < 0 || detailColumn < 0 || statusColumn < 0) return;
|
|
643
|
-
|
|
644
|
-
const lessonRows = table.filter((cells) => /^L-\d{4}(-\d{2}-\d{2})?-\d+/i.test(cells[idColumn] || ""));
|
|
645
|
-
for (const cells of lessonRows) {
|
|
646
|
-
const id = cells[idColumn] || "";
|
|
647
|
-
const status = cells[statusColumn] || "";
|
|
648
|
-
const detail = cells[detailColumn] || "";
|
|
649
|
-
if (!/pending|approved|merged|rejected|superseded|🟡|🟢|✅|❌|🔀/i.test(status)) {
|
|
650
|
-
fail(`${lessonsPath} row ${id} has unrecognized status: ${status}`);
|
|
651
|
-
}
|
|
652
|
-
const detailMatch = detail.match(/docs\/01-GOVERNANCE\/lessons\/[^|\s`]+\.md/);
|
|
653
|
-
if (!detailMatch) {
|
|
654
|
-
fail(`${lessonsPath} row ${id} Detail Doc must point to docs/01-GOVERNANCE/lessons/*.md`);
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
const detailPath = detailMatch[0];
|
|
658
|
-
if (!exists(detailPath)) {
|
|
659
|
-
fail(`${lessonsPath} row ${id} Detail Doc missing file: ${detailPath}`);
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
const detailContent = read(detailPath);
|
|
663
|
-
if (!detailContent.includes(id)) {
|
|
664
|
-
fail(`${detailPath} does not include lesson id ${id}`);
|
|
665
|
-
}
|
|
666
|
-
for (const requiredTerm of ["背景", "冲突声明"]) {
|
|
667
|
-
if (!detailContent.includes(requiredTerm)) {
|
|
668
|
-
fail(`${detailPath} missing required lesson section: ${requiredTerm}`);
|
|
669
|
-
}
|
|
440
|
+
function checkLessonDetailDocs() {
|
|
441
|
+
const root = filePath("docs/01-GOVERNANCE/lessons");
|
|
442
|
+
if (!fs.existsSync(root)) return;
|
|
443
|
+
for (const entry of fs.readdirSync(root)) {
|
|
444
|
+
if (!entry.endsWith(".md")) continue;
|
|
445
|
+
const relativePath = `docs/01-GOVERNANCE/lessons/${entry}`;
|
|
446
|
+
const content = read(relativePath);
|
|
447
|
+
const id = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i)?.[1];
|
|
448
|
+
if (id && !content.includes(id)) {
|
|
449
|
+
fail(`${relativePath} does not include lesson id ${id}`);
|
|
670
450
|
}
|
|
671
451
|
}
|
|
672
452
|
}
|
|
@@ -675,7 +455,7 @@ function checkWalkthroughTemplate() {
|
|
|
675
455
|
const walkthroughTemplate = "docs/10-WALKTHROUGH/_walkthrough-template.md";
|
|
676
456
|
if (!exists(walkthroughTemplate)) return;
|
|
677
457
|
const content = read(walkthroughTemplate);
|
|
678
|
-
if (!content
|
|
458
|
+
if (!contentIncludesAny(content, ["Lessons Reflection", "Lessons 回看"])) {
|
|
679
459
|
fail(`${walkthroughTemplate} missing Lessons Reflection section`);
|
|
680
460
|
}
|
|
681
461
|
}
|
|
@@ -707,7 +487,7 @@ function main() {
|
|
|
707
487
|
checkReviewTemplate();
|
|
708
488
|
checkHarnessLedger();
|
|
709
489
|
checkCloseoutSsot();
|
|
710
|
-
|
|
490
|
+
checkLessonDetailDocs();
|
|
711
491
|
checkWalkthroughTemplate();
|
|
712
492
|
checkReferencePlaceholders();
|
|
713
493
|
|