agent-project-sdlc 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,7 +88,7 @@ Before development starts, `ARCHITECTING` can return to `REQUIREMENT_GATHERING`
88
88
 
89
89
  SPRINTING Definition of Done includes module-level runnable delivery boundaries. API, CLI, server route, service, agent, runtime, adapter, worker, provider, config-contract and fixture/live boundaries promised by a technical plan or task must be implemented or marked `BLOCKED` during development. Runtime/app/provider/live tasks must declare `evidence_level.required`, `target_runtime_environment` and `self_test_contract` in `plan.yaml`; every gate in `self_test_contract.required_gates` must also appear in task `required_gates`, and `self_test_contract.module_key_test_path` must describe the path from local start or invocation to all self-test scenarios completion, covering every runnable entry promised by the current task/module and its internal key paths. `deployed_runtime` cannot be closed by `unit`, `local_runtime`, `external_provider_live`, provider smoke, fake adapters or localhost smoke alone, and `business_handoff_ready` requires a Testing Handoff Contract. The current task implementation doc must include `Development Evidence` and a completed `Development Self-Test Report` with contract source, scenario results, executed gates, Module Key Test Path, actual evidence, missing/blockers and Testing Handoff Readiness; Module Key Test Path records actual entries, internal key paths, boundaries, checkpoints and observable completion evidence. Provider smoke, fixture smoke, fake adapters and one-shot smoke prove only local links; they do not by themselves prove application readiness. REVIEWING treats missing entry/exit, initialization, config contract, target runtime, evidence level or development evidence as blocking, and TESTING only exercises entrypoints that Review has confirmed as `PASS`; it must not add product runtime, bootstrap, provider adapter, deploy code or package runtime scripts.
90
90
 
91
- `make validate-dev` and `npx sdlc-harness validate-dev` are in-development SPRINTING gates. They allow the current `current_task_id` open task to remain in `plan.yaml` while checking that it is a valid `phase: "SPRINTING"` task with `docs`, `allowed_paths`, `required_gates`, `acceptance_criteria`, `implementation_doc`, scoped dirty files, an empty `plan.draft.yaml` queue, runtime evidence task contract, `self_test_contract`, linked runnable-entry implementation docs, structured development evidence and a completed Development Self-Test Report. The report must include Module Key Test Path so later agents can reuse the debug path from local entry to all self-test scenarios completion; that path is scoped to entries and internal key paths promised by the current task/module, not the whole system. Page tasks need a dev server or page URL plus browser/Playwright/screenshot/equivalent interaction evidence; API/CLI/worker/service/agent/runtime tasks need a startup or invocation command, endpoint/health/status, and observable response/output/side effect. `make validate-current` and `/advance` are phase-exit gates; before moving to REVIEWING, the implementation commit and completion ledger must be done and no open task may remain.
91
+ `make validate-dev` and `npx sdlc-harness validate-dev` are in-development SPRINTING gates. They allow the current `current_task_id` open task to remain in `plan.yaml` while checking that it is a valid `phase: "SPRINTING"` task with `docs`, `allowed_paths`, `required_gates`, `acceptance_criteria`, `implementation_doc`, scoped dirty files, an empty `plan.draft.yaml` queue, runtime evidence task contract, `self_test_contract`, linked runnable-entry implementation docs, structured development evidence and a completed Development Self-Test Report. The report must include Module Key Test Path so later agents can reuse the debug path from local entry to all self-test scenarios completion; that path is scoped to entries and internal key paths promised by the current task/module, not the whole system. Page tasks need a dev server or page URL plus browser/Playwright/screenshot/equivalent interaction evidence; API/CLI/worker/service/agent/runtime tasks need a startup or invocation command, endpoint/health/status, and observable response/output/side effect. `validate-dev` checks content consistency and completeness between the report and current `self_test_contract`; it does not prove commands really executed in the current run. Agents must execute the current task `required_gates` before filling the report, and writing `PASS` without running those gates is an Agent execution violation. `make validate-current` and `/advance` are phase-exit gates; before moving to REVIEWING, the implementation commit and completion ledger must be done and no open task may remain.
92
92
 
93
93
  `validate-test` keeps its command name as the TESTING phase gate. `.docs/07_test/TEST_STRATEGY.md` describes scope, environment, priority and execution strategy; `.docs/07_test/TEST_CASES.md` describes cases bound to real runnable entry/exit; `.docs/07_test/TEST_REPORT.md` only records executed TESTING evidence, test matrix, regression evidence, runnable entry/exit coverage, coverage gaps and final decision. `validate-test` only accepts `TEST_REPORT.md`; it no longer treats `TEST_PLAN.md` as a report fallback.
94
94
 
@@ -108,7 +108,7 @@ Agent 会读取 `<harnessRoot>/state/lifecycle.yaml` 和 `<harnessRoot>/state/pl
108
108
 
109
109
  SPRINTING 的 Definition of Done 包含模块级可运行交付边界:技术方案或 task 承诺的 API、CLI、server route、service、agent、runtime、adapter、worker、provider、配置契约和 fixture/live 边界必须在开发阶段实现或明确 `BLOCKED`。runtime/app/provider/live 类 task 必须在 `plan.yaml` 声明 `evidence_level.required`、`target_runtime_environment` 和 `self_test_contract`;`self_test_contract.required_gates` 必须同步出现在 task `required_gates`,`self_test_contract.module_key_test_path` 必须描述从本地启动或调用入口开始,到完成全部自测 scenario 的模块关键测试路径,并覆盖本 task / 本模块承诺的所有可运行入口和内部关键路径。`deployed_runtime` 不能用 `unit`、`local_runtime`、`external_provider_live`、provider smoke、fake adapter 或 localhost smoke 单独关闭,`business_handoff_ready` 还必须有 Testing Handoff Contract。当前 task 的 implementation doc 还必须写入 `Development Evidence` 和 `Development Self-Test Report`,其中自测报告记录 contract source、scenario results、executed gates、Module Key Test Path、actual evidence、missing/blockers 和 Testing Handoff Readiness;`Module Key Test Path` 必须记录实际入口、内部关键路径、关键边界、观察点和可观测完成证据。provider smoke、fixture smoke、fake adapter 或 one-shot smoke 只能证明局部链路,不能单独证明 application readiness。REVIEWING 会把缺少入口/出口、初始化、配置契约、目标运行环境、证据等级或开发自测证据作为阻断项;TESTING 只调用 Review 已确认 `PASS` 的既有入口做输入输出验证,不能新增 product runtime、bootstrap、provider adapter、deploy 或 package runtime script。
110
110
 
111
- `make validate-dev` / `npx sdlc-harness validate-dev` 是 SPRINTING 开发中 gate:当前 `current_task_id` 指向的 open task 可以继续留在 `plan.yaml`,validator 会检查它是否是合法 `phase: "SPRINTING"` task、是否具备 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria`、`implementation_doc`,并校验 dirty files、`plan.draft.yaml`、runtime evidence task contract、`self_test_contract`、implementation doc、结构化 `Development Evidence` 和 `Development Self-Test Report`。自测报告必须记录 `Module Key Test Path`,便于后续 Agent 复用从本地入口到全部自测用例完成的 debug 路径;该路径只要求覆盖本 task / 本模块承诺范围内的可运行入口和内部关键路径,不要求覆盖全系统所有模块。页面类证据需要 dev server/page URL 与 browser check;API/CLI/worker/service/agent/runtime 类证据需要 startup/invocation command、endpoint/health/status 与 response/output/side effect。`make validate-current` / `/advance` 是阶段出口 gate;进入 REVIEWING 前仍必须先完成 implementation commit 和 completion ledger,把 open task 从 `plan.yaml` 移除。
111
+ `make validate-dev` / `npx sdlc-harness validate-dev` 是 SPRINTING 开发中 gate:当前 `current_task_id` 指向的 open task 可以继续留在 `plan.yaml`,validator 会检查它是否是合法 `phase: "SPRINTING"` task、是否具备 `docs`、`allowed_paths`、`required_gates`、`acceptance_criteria`、`implementation_doc`,并校验 dirty files、`plan.draft.yaml`、runtime evidence task contract、`self_test_contract`、implementation doc、结构化 `Development Evidence` 和 `Development Self-Test Report`。自测报告必须记录 `Module Key Test Path`,便于后续 Agent 复用从本地入口到全部自测用例完成的 debug 路径;该路径只要求覆盖本 task / 本模块承诺范围内的可运行入口和内部关键路径,不要求覆盖全系统所有模块。页面类证据需要 dev server/page URL 与 browser check;API/CLI/worker/service/agent/runtime 类证据需要 startup/invocation command、endpoint/health/status 与 response/output/side effect。`validate-dev` 只校验自测报告内容与当前 `self_test_contract` 的一致性和完整性,不证明命令在本轮真实执行;Agent 必须先实际运行 current task `required_gates` 后再填写 `Development Self-Test Report`,未执行 required gates 却写 `PASS` 属于 Agent execution violation。`make validate-current` / `/advance` 是阶段出口 gate;进入 REVIEWING 前仍必须先完成 implementation commit 和 completion ledger,把 open task 从 `plan.yaml` 移除。
112
112
 
113
113
  `validate-test` 仍然是 TESTING 阶段 gate 名称。`.docs/07_test/TEST_STRATEGY.md` 描述测试范围、环境、优先级和执行策略;`.docs/07_test/TEST_CASES.md` 描述绑定真实 runnable entry/exit 的测试用例;`.docs/07_test/TEST_REPORT.md` 只记录 TESTING 阶段实际执行后的 test matrix、regression evidence、runnable entry/exit coverage、coverage gaps 和 final decision。`validate-test` 只接受 `TEST_REPORT.md`,不会把 `TEST_PLAN.md` 当作 report fallback。
114
114
 
@@ -19,6 +19,8 @@ description: Use during SPRINTING to execute one task from plan.yaml, respecting
19
19
 
20
20
  `self_test_contract` 是开发阶段自测合同,由 ARCHITECTING 或 RFC_RECALIBRATION 先定义,SPRINTING 负责执行并在 implementation doc 填写 `Development Self-Test Report`。开发者不得在开发结束后用现有实现反推自测合同;如果合同缺失、入口不匹配、required gates 未同步或场景无法执行,要先回到 ARCHITECTING/RFC 或把 task 保持为 `BLOCKED`。自测报告不是 TESTING 阶段产物,它只证明当前模块级可运行交付边界已经能被 Review/Testing 消费。报告还必须记录 `Module Key Test Path`:从本地启动或调用入口开始,执行并完成 `self_test_contract` 中全部自测用例的模块关键测试路径。该路径应覆盖本 task / 本模块承诺的所有可运行入口,以及自测用例实际经过的内部关键路径、关键边界、观察点和可观测完成证据,供后续 Agent 复用和 debug。
21
21
 
22
+ 开发阶段交付包含两类产物:实现产物(代码、配置、脚本、测试等)和开发自测产物。`Development Self-Test Report` 是开发阶段产物,不是计划、模板或历史记录。若当前 task 或关联技术方案声明 `self_test_contract.status: "required"`,必须先逐条执行 `self_test_contract.scenarios[]` 和 `self_test_contract.required_gates`,再填写或更新 `Development Self-Test Report`。没有本轮执行过的 runnable entry、内部关键路径、observable exit / artifact / screenshot / response / log 等证据时,不得写 `PASS`,不得完成 task。
23
+
22
24
  页面类任务在开发阶段必须启动 dev server 或等价预览入口,并用浏览器、Playwright、截图或等价方式验证页面可加载、主入口可访问、核心按钮/表单/跳转可用、没有明显报错或空白页。API/CLI/worker/RPA/service/agent/runtime 类任务必须记录实际启动或调用命令、endpoint、worker command、dry-run/live preflight、health/status 或 server action,以及可观察的 response、队列 item、审计日志、文件产物、发送结果、错误码或 PASS/BLOCKED 结果。
23
25
 
24
26
  `/dev` 和 `/devloop` 是开发阶段的两个入口。`/dev` 创建或选择下一个最小 `TASK-*` development task,设置 `phase: "SPRINTING"`,并只完成一个 task 闭环后停止。通用规则是从任何 draft queue promote 正式 `TASK-*` 时都必须同次消费源 draft;当前开发阶段的内置 draft queue 是 `plan.draft.yaml.tasks[]`,因此如果这个 task 来自 `plan.draft.yaml.tasks[]`,promote 时必须同次删除源 draft,避免已采用草案继续显示为 `pending`。`/devloop` 连续运行 `/dev`,直到 `plan.yaml.tasks[]` 和 `plan.draft.yaml.tasks[]` 都没有明确可创建/执行的任务,或遇到需求、架构、allowed_paths、gate、commit/push blocker。
@@ -108,7 +110,7 @@ done task 的执行流水不在当前 `plan.yaml` 长期保留,也不是默认
108
110
  - [ ] 当前任务仍然是单一清晰的执行单元。
109
111
  - [ ] 技术方案或 task 承诺的 API/CLI/adapter/worker/provider、配置契约、输出/副作用和 fixture/live 边界已可运行并写入 implementation doc,或已明确 `BLOCKED`/后续 dev task。
110
112
  - [ ] implementation doc `Development Evidence` 已记录 `Evidence Level`、`Target Runtime Environment`、`Runnable Entry`、`Observable Exit`、`Client / Server Initialization`、`Config Contract`、`Testing Handoff Readiness`、`Known Missing Runtime Boundaries`、`Basic Self-test Evidence`,或写明带原因的 `Not applicable`。
111
- - [ ] 如果当前 task 有 `self_test_contract.status: "required"`,implementation doc 已填写 `Development Self-Test Report`,包含 contract source、scenario results、executed gates、Module Key Test Path、actual evidence、missing/blockers 和 Testing Handoff Readiness,且没有 `BLOCKED` 场景。
113
+ - [ ] 如果当前 task 有 `self_test_contract.status: "required"`,已逐条执行当前 `self_test_contract.scenarios[]` 和 `self_test_contract.required_gates`,并在 implementation doc `Development Self-Test Report` 写入本轮 contract source、scenario results、executed gates、runnable entry、内部关键路径、observable exit/evidence、Module Key Test Path、missing/blockers 和 Testing Handoff Readiness,且没有 `BLOCKED` 场景。
112
114
  - [ ] 如果 task 要求 `business_handoff_ready`,implementation doc 已写入 Testing Handoff Contract,包含入口、配置、初始化/health、输入样例、预期出口、清理方式和证据等级。
113
115
  - [ ] 如果当前 task 来自 `plan.draft.yaml.tasks[]`,源 draft 已在 promote 时从 draft 列表删除。
114
116
  - [ ] implementation doc 已生成或更新,并反映相关模块的真实代码。
@@ -19,7 +19,7 @@ description: Use after development gates pass to update module-level implementat
19
19
 
20
20
  如果模块包含或承诺可运行系统边界,implementation doc 必须记录 runnable entry/exit:API/CLI/server route/service/agent/runtime/adapter/worker/provider 的调用方式、初始化方式、配置契约、输入来源、输出或副作用、fixture/live 模式边界,以及哪些真实外部执行器尚未实现。还必须在 `Development Evidence` 中记录开发阶段实际验证过的 `Evidence Level`、`Target Runtime Environment`、`Runnable Entry`、`Observable Exit`、`Client / Server Initialization`、`Config Contract`、`Testing Handoff Readiness`、`Known Missing Runtime Boundaries` 和 `Basic Self-test Evidence`;`Basic Self-test Evidence` 应指向已执行的 `Development Self-Test Report`。确实没有应用入口时,`Not applicable` 必须写清原因。不能把未来才会实现的入口写成当前事实,不能把 provider smoke、fixture smoke、fake adapter 或 one-shot smoke 单独写成 application readiness。如果 task 要求 `business_handoff_ready`,还必须写 Testing Handoff Contract,包含入口、配置、初始化/health、输入样例、预期出口、清理/reset/幂等说明和证据等级。
21
21
 
22
- 如果当前 task 有 `self_test_contract.status: "required"`,implementation doc 必须填写 `Development Self-Test Report`,把设计/RFC 阶段定义的自测合同执行完成:记录 contract source、每个 scenario 的 `PASS` / `BLOCKED` 结果、实际执行入口、实际出口、证据位置或命令输出、executed gates、Module Key Test Path、missing/blockers 和 Testing Handoff Readiness。`Module Key Test Path` 必须说明从本地启动或调用入口开始,执行并完成 `self_test_contract` 中全部自测用例的模块关键测试路径。该路径应覆盖本 task / 本模块承诺的所有可运行入口,以及自测用例实际经过的内部关键路径、关键边界、观察点和可观测完成证据,供后续 Agent 复用和 debug。任何 scenario 为 `BLOCKED` 时,不得把开发 task 写成完成。
22
+ 如果当前 task 有 `self_test_contract.status: "required"`,implementation doc 必须填写 `Development Self-Test Report`,把设计/RFC 阶段定义的自测合同执行完成:记录 contract source、每个 scenario 的 `PASS` / `BLOCKED` 结果、实际执行入口、实际出口、证据位置或命令输出、executed gates、Module Key Test Path、missing/blockers 和 Testing Handoff Readiness。`Development Self-Test Report` 只能记录当前 task 本轮实际执行后的结果;不得用历史报告、模板字段、代码阅读或无关通用 gate 替代本轮 self-test scenario 执行。`Module Key Test Path` 必须说明从本地启动或调用入口开始,执行并完成 `self_test_contract` 中全部自测用例的模块关键测试路径。该路径应覆盖本 task / 本模块承诺的所有可运行入口,以及自测用例实际经过的内部关键路径、关键边界、观察点和可观测完成证据,供后续 Agent 复用和 debug。任何 scenario 为 `BLOCKED` 时,不得把开发 task 写成完成。
23
23
 
24
24
  ## 输入
25
25
 
@@ -51,7 +51,7 @@ description: Use after development gates pass to update module-level implementat
51
51
  3. 与技术方案的偏移必须明确记录,即便该偏移是合理的。
52
52
  4. runnable entry/exit、配置契约和 fixture/live 边界必须记录当前事实;缺失项写入 `未覆盖(Not covered)` 或方案偏移。
53
53
  5. `Development Evidence` 必须包含 task 合同要求的证据等级、目标运行环境、实际可调用入口、可观察出口、初始化方式、配置契约、测试交接状态、缺失 runtime 边界和开发自测证据;页面类任务记录 dev server/page URL 与 browser check,API/CLI/worker/RPA/service/agent/runtime 类任务记录 startup/invocation command、endpoint/health/status 与 response/output/side effect。
54
- 6. `Development Self-Test Report` 必须执行 `self_test_contract` 中的全部 scenario 和 required gates,并记录从本地启动或调用入口开始,到完成所有自测用例的 `Module Key Test Path`;路径必须覆盖本 task / 本模块承诺的所有可运行入口、内部关键路径、关键边界、观察点和完成证据,不能只补一句 smoke 结果。
54
+ 6. `Development Self-Test Report` 必须记录当前 task 本轮执行 `self_test_contract` 中全部 scenario 和 required gates 后的结果,并记录从本地启动或调用入口开始,到完成所有自测用例的 `Module Key Test Path`;路径必须覆盖本 task / 本模块承诺的所有可运行入口、内部关键路径、关键边界、观察点和完成证据,不能只补一句 smoke 结果,也不能复用历史 PASS、模板字段、代码阅读或无关通用 gate 作为本轮证据。
55
55
  7. 测试覆盖必须列出具体测试,或明确记录覆盖缺口。
56
56
  8. 文档粒度保持在模块、子系统或核心数据流级别;不要默认按 task 建文档,也不要写成跨全项目的巨型百科。
57
57
 
@@ -65,7 +65,7 @@ Input
65
65
 
66
66
  | Scenario ID | Result | Executed Entry | Actual Exit | Evidence |
67
67
  |---|---|---|---|---|
68
- | ST-001 | PASS / BLOCKED | | | |
68
+ | | | | | |
69
69
 
70
70
  ## 8. Testing Handoff Contract(测试交接合同)
71
71
 
@@ -113,6 +113,36 @@ const EVIDENCE_PLACEHOLDER_TERMS = [
113
113
  "待补",
114
114
  "待确认"
115
115
  ];
116
+ const SELF_TEST_REPORT_PLACEHOLDER_TERMS = [
117
+ "pass / blocked",
118
+ "pass or blocked",
119
+ "pass/block",
120
+ "pass/blocker",
121
+ "local start / invocation",
122
+ "all self-test scenarios",
123
+ "all task/module promised runnable entries",
124
+ "actual internal key paths",
125
+ "observable completion evidence"
126
+ ];
127
+ const SELF_TEST_OBSERVABLE_EVIDENCE_TERMS = [
128
+ "pass output",
129
+ "response",
130
+ "output",
131
+ "side effect",
132
+ "log",
133
+ "artifact",
134
+ "health",
135
+ "status",
136
+ "audit",
137
+ "rendered",
138
+ "page state",
139
+ "screenshot",
140
+ "browser check",
141
+ "playwright",
142
+ "command output",
143
+ "queue",
144
+ "file"
145
+ ];
116
146
  const PAGE_TASK_TERMS = ["frontend", "front-end", "browser", "page", "页面", "前端", "按钮", "表单", "跳转"];
117
147
  const PAGE_ENTRY_TERMS = ["http://", "https://", "localhost", "127.0.0.1", "page url", "页面 url", "dev server"];
118
148
  const PAGE_BROWSER_CHECK_TERMS = ["browser check", "playwright", "screenshot", "click", "button", "form", "页面可加载", "浏览器验证"];
@@ -1318,11 +1348,26 @@ function validateDevelopmentSelfTestReport(fullText, developmentEvidenceSection,
1318
1348
  }
1319
1349
  }
1320
1350
  const moduleKeyTestPath = evidenceFieldValue(report, "Module Key Test Path") ?? "";
1351
+ if (isPlaceholderSelfTestReportValue(moduleKeyTestPath) || isTemplateModuleKeyTestPath(moduleKeyTestPath)) {
1352
+ errors.push(`${taskId} Development Self-Test Report Module Key Test Path must replace template placeholders with actual executed path evidence in ${implementationDoc}`);
1353
+ }
1321
1354
  const runnableEntry = String(contract.runnable_entry ?? "").trim();
1322
1355
  if (runnableEntry && !moduleKeyTestPath.includes(runnableEntry)) {
1323
1356
  errors.push(`${taskId} Development Self-Test Report Module Key Test Path must include runnable entry ${runnableEntry} in ${implementationDoc}`);
1324
1357
  }
1325
1358
  const scenarios = Array.isArray(contract.scenarios) ? contract.scenarios.filter(isRecord) : [];
1359
+ const exitEvidenceTerms = [
1360
+ String(contract.observable_exit ?? "").trim(),
1361
+ ...scenarios.flatMap((scenario) => [
1362
+ String(scenario.expected_exit ?? "").trim(),
1363
+ String(scenario.evidence ?? "").trim()
1364
+ ])
1365
+ ].filter(Boolean);
1366
+ if (exitEvidenceTerms.length > 0
1367
+ && !exitEvidenceTerms.some((term) => normalizedIncludes(moduleKeyTestPath, term))
1368
+ && !containsAny(moduleKeyTestPath, SELF_TEST_OBSERVABLE_EVIDENCE_TERMS)) {
1369
+ errors.push(`${taskId} Development Self-Test Report Module Key Test Path must include observable exit or evidence from self_test_contract in ${implementationDoc}`);
1370
+ }
1326
1371
  for (const scenario of scenarios) {
1327
1372
  const scenarioId = String(scenario.id ?? "").trim();
1328
1373
  if (!scenarioId)
@@ -1334,25 +1379,94 @@ function validateDevelopmentSelfTestReport(fullText, developmentEvidenceSection,
1334
1379
  if (!status) {
1335
1380
  errors.push(`${taskId} Development Self-Test Report must record scenario ${scenarioId} as PASS or BLOCKED in ${implementationDoc}`);
1336
1381
  }
1382
+ else if (status === "AMBIGUOUS") {
1383
+ errors.push(`${taskId} Development Self-Test Report scenario ${scenarioId} must choose exactly one of PASS or BLOCKED in ${implementationDoc}`);
1384
+ }
1337
1385
  else if (status === "BLOCKED") {
1338
1386
  errors.push(`${taskId} Development Self-Test Report scenario ${scenarioId} is BLOCKED; keep task open or record a blocker`);
1339
1387
  }
1388
+ errors.push(...validateScenarioTableEvidence(report, scenarioId, taskId, implementationDoc));
1389
+ }
1390
+ const targetRuntime = isRecord(task.target_runtime_environment) ? task.target_runtime_environment : undefined;
1391
+ const reportContext = `${taskText(task)}\n${report}\n${Object.values(contract).map((value) => String(value ?? "")).join("\n")}`;
1392
+ if (String(targetRuntime?.kind ?? "") === "browser" || containsAny(reportContext, PAGE_TASK_TERMS)) {
1393
+ const loweredReport = report.toLowerCase();
1394
+ if (!containsAny(loweredReport, PAGE_ENTRY_TERMS)) {
1395
+ errors.push(`${taskId} page Development Self-Test Report must include a dev server or page URL in ${implementationDoc}`);
1396
+ }
1397
+ if (!containsAny(loweredReport, PAGE_BROWSER_CHECK_TERMS)) {
1398
+ errors.push(`${taskId} page Development Self-Test Report must include browser, Playwright, screenshot, or equivalent interaction evidence in ${implementationDoc}`);
1399
+ }
1340
1400
  }
1341
1401
  return errors;
1342
1402
  }
1343
1403
  function scenarioStatus(text, scenarioId) {
1344
1404
  const escaped = scenarioId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1345
- const patterns = [
1346
- new RegExp("^.*" + escaped + ".*\\b(PASS|BLOCKED)\\b.*$", "im"),
1347
- new RegExp("\\|[^\\n|]*" + escaped + "[^\\n|]*\\|[^\\n|]*\\b(PASS|BLOCKED)\\b[^\\n|]*\\|", "i")
1348
- ];
1349
- for (const pattern of patterns) {
1350
- const match = text.match(pattern);
1351
- if (match)
1352
- return match[1].toUpperCase();
1405
+ const pattern = new RegExp("^.*" + escaped + ".*$", "gim");
1406
+ for (const match of text.matchAll(pattern)) {
1407
+ const line = match[0];
1408
+ const hasPass = /\bPASS\b/i.test(line);
1409
+ const hasBlocked = /\bBLOCKED\b/i.test(line);
1410
+ if (hasPass && hasBlocked)
1411
+ return "AMBIGUOUS";
1412
+ if (hasPass)
1413
+ return "PASS";
1414
+ if (hasBlocked)
1415
+ return "BLOCKED";
1353
1416
  }
1354
1417
  return undefined;
1355
1418
  }
1419
+ function validateScenarioTableEvidence(report, scenarioId, taskId, implementationDoc) {
1420
+ const errors = [];
1421
+ const rows = markdownTableRows(report).filter((cells) => cells.some((cell) => normalizeCell(cell) === scenarioId));
1422
+ for (const cells of rows) {
1423
+ const [id, result, executedEntry, actualExit, evidence] = cells;
1424
+ if (!id || normalizeCell(id) !== scenarioId)
1425
+ continue;
1426
+ const requiredCells = [
1427
+ ["Result", result],
1428
+ ["Executed Entry", executedEntry],
1429
+ ["Actual Exit", actualExit],
1430
+ ["Evidence", evidence]
1431
+ ];
1432
+ for (const [label, value] of requiredCells) {
1433
+ if (!value || isPlaceholderSelfTestReportValue(value)) {
1434
+ errors.push(`${taskId} Development Self-Test Report scenario ${scenarioId} table ${label} must contain concrete evidence in ${implementationDoc}`);
1435
+ }
1436
+ }
1437
+ if (result && scenarioStatus(`| ${cells.join(" | ")} |`, scenarioId) === "AMBIGUOUS") {
1438
+ errors.push(`${taskId} Development Self-Test Report scenario ${scenarioId} table Result must choose exactly one of PASS or BLOCKED in ${implementationDoc}`);
1439
+ }
1440
+ }
1441
+ return errors;
1442
+ }
1443
+ function markdownTableRows(section) {
1444
+ return section
1445
+ .split(/\r?\n/)
1446
+ .map((line) => line.trim())
1447
+ .filter((line) => line.startsWith("|") && line.endsWith("|") && !/^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|$/.test(line))
1448
+ .map((line) => line.slice(1, -1).split("|").map((cell) => cell.trim()));
1449
+ }
1450
+ function normalizeCell(value) {
1451
+ return value.replace(/`/g, "").trim();
1452
+ }
1453
+ function isTemplateModuleKeyTestPath(value) {
1454
+ const lowered = value.toLowerCase();
1455
+ return [
1456
+ "local start / invocation",
1457
+ "all self-test scenarios",
1458
+ "all task/module promised runnable entries",
1459
+ "actual internal key paths",
1460
+ "observable completion evidence"
1461
+ ].some((term) => lowered.includes(term));
1462
+ }
1463
+ function isPlaceholderSelfTestReportValue(value) {
1464
+ const normalized = value.trim().toLowerCase();
1465
+ return isPlaceholderEvidence(value) || SELF_TEST_REPORT_PLACEHOLDER_TERMS.some((term) => normalized.includes(term));
1466
+ }
1467
+ function normalizedIncludes(text, needle) {
1468
+ return text.toLowerCase().includes(needle.toLowerCase());
1469
+ }
1356
1470
  function hasConcreteDevelopmentEvidenceFields(section) {
1357
1471
  return ["Evidence Level", "Target Runtime Environment", "Runnable Entry", "Observable Exit", "Client / Server Initialization", "Config Contract"].some((field) => {
1358
1472
  const value = evidenceFieldValue(section, field);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-project-sdlc",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "CLI and canonical assets for the AI SDLC Harness workflow.",
5
5
  "type": "module",
6
6
  "bin": {