@peterxiaoyang/superspec 0.1.11 → 0.1.13

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 (44) hide show
  1. package/README.md +4 -4
  2. package/adapters/codex/agents/explore.toml +13 -0
  3. package/adapters/codex/install-map.json +10 -0
  4. package/dist/src/apply_worker_chain.d.ts +6 -3
  5. package/dist/src/apply_worker_chain.js +125 -30
  6. package/dist/src/apply_worker_chain_lifecycle.d.ts +7 -5
  7. package/dist/src/apply_worker_chain_lifecycle.js +153 -22
  8. package/dist/src/cli_args.d.ts +1 -0
  9. package/dist/src/cli_args.js +33 -15
  10. package/dist/src/disclosure.js +2 -2
  11. package/dist/src/evidence.js +104 -8
  12. package/dist/src/gates.d.ts +1 -0
  13. package/dist/src/gates.js +160 -27
  14. package/dist/src/hooks/guard_api.js +2 -2
  15. package/dist/src/hooks/policy_event.js +11 -1
  16. package/dist/src/i18n.js +63 -3
  17. package/dist/src/init_cli.js +1 -1
  18. package/dist/src/openspec.d.ts +2 -0
  19. package/dist/src/openspec.js +31 -0
  20. package/dist/src/packet_render.d.ts +1 -0
  21. package/dist/src/packet_render.js +248 -31
  22. package/dist/src/packet_schema.d.ts +2 -0
  23. package/dist/src/tasks.d.ts +5 -0
  24. package/dist/src/tasks.js +62 -0
  25. package/dist/src/util.d.ts +4 -0
  26. package/dist/src/util.js +13 -0
  27. package/dist/superspec.js +4 -4
  28. package/package.json +3 -3
  29. package/templates/sidecar/discovery.md +9 -0
  30. package/templates/sidecar/test-contract.md +2 -1
  31. package/templates/workflow/prompts/code-reviewer.md +1 -1
  32. package/templates/workflow/prompts/executor.md +1 -1
  33. package/templates/workflow/prompts/explore.md +27 -0
  34. package/templates/workflow/prompts/test-engineer.md +1 -0
  35. package/templates/workflow/prompts/test-runner.md +3 -1
  36. package/templates/workflow/prompts/verifier.md +9 -9
  37. package/templates/workflow/skills/superspec-apply/SKILL.md +58 -33
  38. package/templates/workflow/skills/superspec-archive/SKILL.md +5 -5
  39. package/templates/workflow/skills/superspec-explore/SKILL.md +31 -13
  40. package/templates/workflow/skills/superspec-propose/SKILL.md +26 -15
  41. package/templates/workflow/skills/superspec-review/SKILL.md +17 -17
  42. package/dist/src/packet_measure.d.ts +0 -43
  43. package/dist/src/packet_measure.js +0 -382
  44. /package/bin/{superspec-guard.js → superspec-check.js} +0 -0
@@ -2,17 +2,43 @@ import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { load_config, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
4
4
  import {} from "./cli_args.js";
5
- import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, fingerprint_obj, GuardError, MAIN_ADJUDICATION_REQUIRED_FIELDS, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, REVIEW_EVIDENCE_REQUIRED_FIELDS, ROLE_EVIDENCE_FIELDS, SOURCE_GUIDANCE_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, deepEqual, isObject, reason, renderList, runtime, safe_within, sha256_text, } from "./util.js";
5
+ import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, fingerprint_obj, GuardError, MAIN_ADJUDICATION_REQUIRED_FIELDS, MAIN_ADJUDICATION_DECISIONS, NO_TDD_REASONS, REQUEST_CHANGES_ROUTES, REVIEW_EVIDENCE_REQUIRED_FIELDS, ROLE_EVIDENCE_FIELDS, SOURCE_GUIDANCE_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, deepEqual, isObject, reason, renderList, runtime, safe_within, sha256_text, } from "./util.js";
6
6
  import { artifact_status_map, gate_route_phase, get_change_root, get_repo_root, normalize_gate, openspec_status, openspec_status_shape_reasons, } from "./openspec.js";
7
- import { build_finding_ledger, enumerate_review_targets, render_finding_ledger, REVIEW_TARGETS_BY_GATE, review_round_number, } from "./disclosure.js";
7
+ import { build_finding_ledger, enumerate_review_targets, FINDING_CATEGORIES, FINDING_TYPES, MATERIAL_CATEGORIES, render_finding_ledger, REVIEW_TARGETS_BY_GATE, review_round_number, } from "./disclosure.js";
8
8
  import { evidence_schema_guard, check_apply_ready, check_archive_ready, check_review_complete, check_superspec_gate, check_task_complete, check_task_edit, check_task_reopen, } from "./gates.js";
9
9
  import { final_verification_evidences, index_evidence, live_pass, live_user_confirmations } from "./evidence.js";
10
10
  import { file_blob_sha, dirty_worktree_paths } from "./git.js";
11
11
  import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
12
12
  import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
13
- import { parse_tasks, resolve_test_contract_command, splitList, test_contract_invariant_refs_by_test } from "./tasks.js";
14
- import { APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS, APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS, APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS, APPLY_VERIFIER_REPORT_REQUIRED_FIELDS, apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, apply_worker_protected_path_refs, compute_apply_worker_freshness, fingerprint_digest, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
13
+ import { parse_tasks, resolve_test_contract_command, splitList, task_apply_execution_surface, task_apply_execution_surface_reasons, test_contract_invariant_refs_by_test, } from "./tasks.js";
14
+ import { APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS, APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS, APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS, APPLY_VERIFIER_REPORT_REQUIRED_FIELDS, apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, apply_worker_protected_path_refs, compute_apply_worker_freshness, fingerprint_digest, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, pre_edit_worker_test_run_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
15
15
  import { apply_worker_chain_lifecycle_state } from "./apply_worker_chain_lifecycle.js";
16
+ import { PACKAGE_ROOT } from "./install_engine.js";
17
+ // B (template serving): discovery.md fill rules handed to the model alongside the template skeleton
18
+ // so every model produces the same structure (instead of freehanding a section the gate then has to
19
+ // parse heuristically). Kept here, not in the .md template, so they are versioned with the code that
20
+ // depends on them (the open-questions checkbox convention consumed by discovery_open_question_count).
21
+ const DISCOVERY_RULES = [
22
+ "按模板骨架填写 discovery.md,保留全部段落(调查范围 / 现有实现事实 / 隐性合约 / 风险与歧义 / 待确认问题 / Subagent Evidence),不得自创或删减段落骨架。",
23
+ "所有需要用户拍板的问题写进「## 待确认问题」段,每条用「- [ ]」;用户确认后改成「- [x]」或在项内写「已确认:」。",
24
+ "explore_complete 检查在该段仍有「- [ ]」或「仍需确认/待确认」项时不会通过。",
25
+ "段标题含「确认」字样即可被识别(待确认问题 / 需要用户确认的问题 等),不要改成无「确认」字样的标题。",
26
+ ];
27
+ // Reads the canonical discovery template from the package. packageRoot is parameterized so tests can
28
+ // inject an empty dir to exercise the "unreadable → omit field" path; PACKAGE_ROOT itself is a
29
+ // module-load-time const and cannot be mocked. Returns null (not throws) when missing so a transient
30
+ // read failure never turns the whole packet into an error response.
31
+ export function read_discovery_template(packageRoot = PACKAGE_ROOT) {
32
+ const p = join(packageRoot, "templates", "sidecar", "discovery.md");
33
+ if (!existsSync(p) || !statSync(p).isFile())
34
+ return null;
35
+ try {
36
+ return readFileSync(p, "utf8");
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
16
42
  function load_packet_context(change) {
17
43
  if (typeof runtime.load_context === "function") {
18
44
  const [status, repoRoot, changeRoot, evidences] = runtime.load_context(change);
@@ -168,18 +194,18 @@ function evaluate_workflow_decision(ctx, gateRaw, taskId) {
168
194
  function gate_recheck_command(change, gate, taskId, diagnostic = false) {
169
195
  const format = diagnostic ? "json" : "agent";
170
196
  if (gate === "apply_ready")
171
- return `superspec guard check-apply-ready --change "${change}" --format ${format}`;
197
+ return `superspec check check-apply-ready --change "${change}" --format ${format}`;
172
198
  if (gate === "review_complete")
173
- return `superspec guard check-review-complete --change "${change}" --format ${format}`;
199
+ return `superspec check check-review-complete --change "${change}" --format ${format}`;
174
200
  if (gate === "archive_ready")
175
- return `superspec guard check-archive-ready --change "${change}" --format ${format}`;
201
+ return `superspec check check-archive-ready --change "${change}" --format ${format}`;
176
202
  if (gate === "task_edit")
177
- return `superspec guard check-task-edit --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
203
+ return `superspec check check-task-edit --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
178
204
  if (gate === "task_complete")
179
- return `superspec guard check-task-complete --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
205
+ return `superspec check check-task-complete --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
180
206
  if (gate === "task_reopen")
181
- return `superspec guard check-task-reopen --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
182
- return `superspec guard check-enter --change "${change}" --gate "${gate}" --format ${format}`;
207
+ return `superspec check check-task-reopen --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
208
+ return `superspec check check-enter --change "${change}" --gate "${gate}" --format ${format}`;
183
209
  }
184
210
  function default_allowed_next_action(gate) {
185
211
  if (gate === "review_complete")
@@ -390,6 +416,14 @@ function workflow_packet(ctx, gateRaw, taskId) {
390
416
  const decisions = decision_selectors_for_gate(ctx, gate);
391
417
  if (decisions.length > 0)
392
418
  packet.must_read_verbatim_decisions = decisions;
419
+ // B (template serving): hand the model the discovery skeleton + fill rules so its output structure
420
+ // is uniform. Gate-scoped to explore_complete (the only gate that produces discovery.md).
421
+ if (gate === "explore_complete") {
422
+ const tpl = read_discovery_template();
423
+ if (tpl)
424
+ packet.discovery_template = tpl;
425
+ packet.discovery_rules = [...DISCOVERY_RULES];
426
+ }
393
427
  return packet;
394
428
  }
395
429
  function live_output_refs(changeRoot, evidences) {
@@ -428,7 +462,19 @@ function verification_output_fields() {
428
462
  return [...ROLE_EVIDENCE_FIELDS, ...VERIFY_EVIDENCE_REQUIRED_FIELDS, "scope_drift"];
429
463
  }
430
464
  function review_output_fields() {
431
- return [...ROLE_EVIDENCE_FIELDS, "review_round_id", "findings", "acknowledged_accepted_deviation_uids"];
465
+ return [
466
+ ...ROLE_EVIDENCE_FIELDS,
467
+ "review_round_id format: <gate>-r<round>",
468
+ "findings[] object fields: finding_id, finding_uid, finding_type, category, summary",
469
+ "finding_uid format: <gate>:<evidence_id>:<finding_id>",
470
+ `finding_type values: ${renderList([...FINDING_TYPES].sort())}`,
471
+ `category values: ${renderList([...FINDING_CATEGORIES].sort())}`,
472
+ `material_categories values: ${renderList([...MATERIAL_CATEGORIES].sort())}`,
473
+ "decision_scope_key required when material_categories is non-empty",
474
+ "summary must be verbatim disclosure source text",
475
+ "acknowledged_accepted_deviation_uids must be a string array when present",
476
+ "findings[].supersedes_finding_uids must be a string array when present",
477
+ ];
432
478
  }
433
479
  function source_guidance_output_fields() {
434
480
  return [
@@ -617,6 +663,7 @@ function apply_worker_stop_conditions(kind) {
617
663
  if (kind === "apply_test") {
618
664
  return [
619
665
  "Stop after reporting the bounded test command result only.",
666
+ "Do not let the main thread run or forge formal RED/characterization/GREEN evidence.",
620
667
  "Do not change implementation files.",
621
668
  "Return raw logs as pinned artifact refs when output is long.",
622
669
  ];
@@ -705,7 +752,7 @@ function require_active_apply_worker_chain(ctx, taskId, blockers) {
705
752
  blockers.push(reason("missing_apply_worker_chain_active", `task ${taskId} requires an active apply_worker_chain marker before downstream worker packet generation`));
706
753
  }
707
754
  else {
708
- blockers.push(...pre_edit_evidence_ref_reasons(ctx.evidences, active, taskId, "apply_worker_chain_active_invalid"));
755
+ blockers.push(...pre_edit_evidence_ref_reasons(ctx.changeRoot, ctx.evidences, active, taskId, "apply_worker_chain_active_invalid"));
709
756
  }
710
757
  return chainId;
711
758
  }
@@ -871,7 +918,7 @@ function validate_test_run_evidence_refs(ctx, refs, expected) {
871
918
  }
872
919
  return { refs: resolved, blockers };
873
920
  }
874
- function validate_active_apply_worker_chain_refs(ctx, refs, taskId, expectedChainId, expectedPacketFingerprint, expectedSourceImplementationFingerprint, expectedDeclaredTaskWriteScope, expectedPreEditEvidenceRefs) {
921
+ function validate_active_apply_worker_chain_refs(ctx, refs, taskId, expectedChainId, expectedPacketFingerprint, expectedSourceImplementationFingerprint, expectedDeclaredTaskWriteScope, expectedPreEditEvidenceRefs, expectedActiveFields = {}) {
875
922
  if (refs.length === 0) {
876
923
  return {
877
924
  refs: [],
@@ -902,6 +949,11 @@ function validate_active_apply_worker_chain_refs(ctx, refs, taskId, expectedChai
902
949
  if (!deepEqual(stringList(value.pre_edit_evidence_refs), stringList(expectedPreEditEvidenceRefs))) {
903
950
  fieldBlockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: pre_edit_evidence_refs mismatch`, [ref]));
904
951
  }
952
+ for (const field of ["pre_edit_proof_kind", "apply_execution_surface", "tdd_required", "no_tdd_reason"]) {
953
+ if (field in expectedActiveFields && !deepEqual(value[field], expectedActiveFields[field])) {
954
+ fieldBlockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: ${field} mismatch`, [ref]));
955
+ }
956
+ }
905
957
  return fieldBlockers;
906
958
  };
907
959
  for (const ref of refs) {
@@ -961,9 +1013,69 @@ function pre_edit_refs_bound_to_active_chain(ctx, active, refs, label, taskId) {
961
1013
  if (!allowed.has(evidenceId))
962
1014
  blockers.push(reason("pinned_evidence_ref_invalid", `${label} evidence ref is not part of active apply_worker_chain pre_edit_evidence_refs: ${evidenceId}`, [evidenceId]));
963
1015
  }
964
- blockers.push(...pre_edit_evidence_ref_reasons(ctx.evidences, active, taskId, "pinned_evidence_ref_invalid"));
1016
+ blockers.push(...pre_edit_evidence_ref_reasons(ctx.changeRoot, ctx.evidences, active, taskId, "pinned_evidence_ref_invalid"));
965
1017
  return blockers;
966
1018
  }
1019
+ function validate_alternative_verification_evidence_refs(ctx, refs, taskId) {
1020
+ if (refs.length === 0) {
1021
+ return {
1022
+ refs: [],
1023
+ blockers: [reason("missing_alternative_verification_evidence_ref", "apply-verify-packet requires --alternative-verification-evidence-ref for no-TDD alternative verification")],
1024
+ };
1025
+ }
1026
+ const resolved = [];
1027
+ const blockers = [];
1028
+ for (const ref of refs) {
1029
+ let evidenceId = ref;
1030
+ const maybePath = safe_within(ctx.changeRoot, ref);
1031
+ if (maybePath !== null && existsSync(maybePath) && statSync(maybePath).isFile()) {
1032
+ const loaded = read_json_ref(ctx, ref);
1033
+ blockers.push(...loaded.blockers);
1034
+ if (loaded.value) {
1035
+ if (loaded.value.kind !== "alternative_verification" && loaded.value.kind !== "manual_verification") {
1036
+ blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: kind must be alternative_verification or manual_verification`, [ref]));
1037
+ }
1038
+ if (normalize_gate(String(loaded.value.gate ?? "")) !== "task_complete")
1039
+ blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be task_complete`, [ref]));
1040
+ if (loaded.value.task_id !== taskId)
1041
+ blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: task_id must be ${taskId}`, [ref]));
1042
+ if (typeof loaded.value.evidence_id === "string" && loaded.value.evidence_id)
1043
+ evidenceId = loaded.value.evidence_id;
1044
+ }
1045
+ }
1046
+ const ev = live_pass(ctx.evidences, { task_id: taskId })
1047
+ .find((item) => String(item.evidence_id ?? "") === evidenceId && (item.kind === "alternative_verification" || item.kind === "manual_verification"));
1048
+ if (!ev) {
1049
+ blockers.push(reason("pinned_evidence_ref_invalid", `alternative verification evidence ref is not live/pass for ${taskId}: ${ref}`, [ref]));
1050
+ continue;
1051
+ }
1052
+ if (normalize_gate(String(ev.gate ?? "")) !== "task_complete")
1053
+ blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be task_complete`, [ref]));
1054
+ resolved.push(ev);
1055
+ }
1056
+ return { refs: resolved, blockers };
1057
+ }
1058
+ function active_chain_verifier_input_ref(active) {
1059
+ return {
1060
+ kind: "apply_worker_chain",
1061
+ evidence_id: active?.evidence_id,
1062
+ task_id: active?.task_id,
1063
+ apply_worker_chain_id: active?.apply_worker_chain_id,
1064
+ pre_edit_proof_kind: active?.pre_edit_proof_kind ?? "red_or_characterization",
1065
+ pre_edit_evidence_refs: Array.isArray(active?.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String).filter(Boolean).sort() : [],
1066
+ ...(active?.apply_execution_surface !== undefined ? { apply_execution_surface: active.apply_execution_surface } : {}),
1067
+ ...(active?.tdd_required !== undefined ? { tdd_required: active.tdd_required } : {}),
1068
+ ...(active?.no_tdd_reason !== undefined ? { no_tdd_reason: active.no_tdd_reason } : {}),
1069
+ };
1070
+ }
1071
+ function alternative_evidence_input_ref(ev) {
1072
+ return {
1073
+ kind: ev.kind,
1074
+ evidence_id: ev.evidence_id,
1075
+ gate: ev.gate,
1076
+ task_id: ev.task_id,
1077
+ };
1078
+ }
967
1079
  function write_scope_blockers(taskId, writeScope) {
968
1080
  const blockers = [];
969
1081
  if (writeScope.length === 0) {
@@ -987,13 +1099,23 @@ function write_scope_blockers(taskId, writeScope) {
987
1099
  function pre_edit_evidence_refs(ctx, taskId) {
988
1100
  return live_pass(ctx.evidences, { gate: "task_edit", kind: "test_run", task_id: taskId })
989
1101
  .filter((ev) => ev.semantic_status === "expected_failure" || ev.semantic_status === "expected_success")
1102
+ .filter((ev) => pre_edit_worker_test_run_reasons(ctx.changeRoot, ev, taskId).length === 0)
990
1103
  .map((ev) => String(ev.evidence_id ?? ""))
991
1104
  .filter(Boolean)
992
1105
  .sort();
993
1106
  }
994
1107
  function apply_task_context_fields(ctx, taskId, task, writeScope, expectedGuardRefs = []) {
1108
+ const executionSurface = task ? task_apply_execution_surface(task) : "";
995
1109
  return {
996
1110
  task_content_ref: change_pinned_ref(ctx.changeRoot, "tasks.md"),
1111
+ apply_execution_surface: executionSurface,
1112
+ apply_execution_surface_source: task?.attrs.apply_execution_surface ? "explicit" : "default",
1113
+ apply_execution_surface_policy: {
1114
+ implementation_requires_executor_worker_chain: true,
1115
+ runtime_config_requires_executor_worker_chain: true,
1116
+ docs_generated_allows_direct_alternative_verification: true,
1117
+ no_code_requires_empty_write_scope: true,
1118
+ },
997
1119
  task_acceptance_refs: task ? splitList(task.attrs.requirement_refs ?? "") : [],
998
1120
  task_invariant_refs: task ? splitList(task.attrs.invariant_refs ?? "") : [],
999
1121
  task_test_refs: task ? splitList(task.attrs.test_refs ?? "") : [],
@@ -1113,15 +1235,32 @@ function apply_executor_packet(ctx, args) {
1113
1235
  const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
1114
1236
  blockers.push(...taskBlockers);
1115
1237
  const writeScope = task ? splitList(task.attrs.write_scope ?? "") : [];
1116
- blockers.push(...write_scope_blockers(taskId, writeScope));
1117
- if (task && (task.attrs.tdd_required ?? "true").toLowerCase() === "false") {
1118
- blockers.push(reason("unsupported_executor_tdd_mode", `task ${taskId} has tdd_required:false and must stay on main-thread apply path`));
1238
+ const executionSurface = task ? task_apply_execution_surface(task) : "";
1239
+ const tddRequired = task ? (task.attrs.tdd_required ?? "true").toLowerCase() !== "false" : true;
1240
+ const noTddReason = task?.attrs.no_tdd_reason;
1241
+ const preEditProofKind = tddRequired ? "red_or_characterization" : "no_tdd_declared";
1242
+ const activePreEditRefs = preEditProofKind === "no_tdd_declared" ? [] : pre_edit_evidence_refs(ctx, taskId);
1243
+ if (task) {
1244
+ blockers.push(...task_apply_execution_surface_reasons(taskId, task));
1245
+ if (executionSurface === "implementation" || executionSurface === "runtime_config") {
1246
+ if (!tddRequired && !NO_TDD_REASONS.has(String(noTddReason ?? ""))) {
1247
+ blockers.push(reason("invalid_no_tdd_reason", `task ${taskId}: no_tdd_reason=${String(noTddReason ?? "")}`));
1248
+ }
1249
+ blockers.push(...write_scope_blockers(taskId, writeScope));
1250
+ }
1251
+ else {
1252
+ blockers.push(reason("executor_handoff_not_required", `task ${taskId} apply_execution_surface=${executionSurface} does not use executor-worker implementation handoff`));
1253
+ }
1254
+ }
1255
+ else {
1256
+ blockers.push(...write_scope_blockers(taskId, writeScope));
1119
1257
  }
1120
1258
  blockers.push(...apply_worker_chain_packet_state(ctx, taskId).blockers);
1121
1259
  const packet = apply_packet_common(ctx, "apply_executor", taskId, "executor_worker", blockers);
1122
1260
  const chainId = active_apply_worker_chain_id(ctx, taskId) ?? generated_apply_worker_chain_id(ctx, taskId, writeScope);
1123
1261
  packet.worker_chain_context = "executor_worker";
1124
1262
  packet.apply_worker_chain_id = chainId;
1263
+ packet.apply_execution_surface = executionSurface;
1125
1264
  packet.declared_task_write_scope = writeScope;
1126
1265
  packet.task_edit = decision_status(taskEdit);
1127
1266
  packet.task_complete = safe_decision_status(ctx, "task_complete", taskId);
@@ -1145,11 +1284,20 @@ function apply_executor_packet(ctx, args) {
1145
1284
  executor_packet_fingerprint: "",
1146
1285
  source_implementation_fingerprint: implementation_fingerprint(ctx, writeScope),
1147
1286
  declared_task_write_scope: writeScope,
1148
- pre_edit_evidence_refs: pre_edit_evidence_refs(ctx, taskId),
1287
+ pre_edit_proof_kind: preEditProofKind,
1288
+ apply_execution_surface: executionSurface,
1289
+ tdd_required: tddRequired,
1290
+ ...(tddRequired ? {} : { no_tdd_reason: noTddReason }),
1291
+ pre_edit_evidence_refs: activePreEditRefs,
1149
1292
  };
1150
1293
  if (args.packet_format === "prompt") {
1151
1294
  const expectedPacketFingerprint = apply_worker_packet_fingerprint(packet);
1152
- const activeRefs = validate_active_apply_worker_chain_refs(ctx, args.apply_worker_chain_refs ?? [], taskId, chainId, expectedPacketFingerprint, packet.chain_activation_template.source_implementation_fingerprint, packet.chain_activation_template.declared_task_write_scope, packet.chain_activation_template.pre_edit_evidence_refs);
1295
+ const activeRefs = validate_active_apply_worker_chain_refs(ctx, args.apply_worker_chain_refs ?? [], taskId, chainId, expectedPacketFingerprint, packet.chain_activation_template.source_implementation_fingerprint, packet.chain_activation_template.declared_task_write_scope, packet.chain_activation_template.pre_edit_evidence_refs, {
1296
+ pre_edit_proof_kind: packet.chain_activation_template.pre_edit_proof_kind,
1297
+ apply_execution_surface: packet.chain_activation_template.apply_execution_surface,
1298
+ tdd_required: packet.chain_activation_template.tdd_required,
1299
+ ...(packet.chain_activation_template.no_tdd_reason !== undefined ? { no_tdd_reason: packet.chain_activation_template.no_tdd_reason } : {}),
1300
+ });
1153
1301
  blockers.push(...activeRefs.blockers);
1154
1302
  packet.apply_worker_chain_refs = args.apply_worker_chain_refs ?? [];
1155
1303
  packet.apply_worker_chain_active_refs = activeRefs.refs;
@@ -1240,10 +1388,13 @@ function apply_verify_packet(ctx, args) {
1240
1388
  const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
1241
1389
  blockers.push(...taskBlockers);
1242
1390
  const writeScope = task ? splitList(task.attrs.write_scope ?? "") : [];
1391
+ const executionSurface = task ? task_apply_execution_surface(task) : "";
1392
+ const tddRequired = task ? (task.attrs.tdd_required ?? "true").toLowerCase() !== "false" : true;
1243
1393
  blockers.push(...write_scope_blockers(taskId, writeScope));
1244
1394
  const chainId = require_active_apply_worker_chain(ctx, taskId, blockers);
1245
1395
  const activeChain = active_apply_worker_chain(ctx, taskId).active;
1246
1396
  const packet = apply_packet_common(ctx, "apply_verify", taskId, "executor_worker", blockers);
1397
+ const completionProofKind = (args.alternative_verification_evidence_refs ?? []).length > 0 ? "alternative_verification" : "green_tests";
1247
1398
  if (chainId) {
1248
1399
  const executorRefs = validate_worker_report_refs(ctx, args.executor_report_refs ?? [], {
1249
1400
  role: "executor",
@@ -1286,7 +1437,8 @@ function apply_verify_packet(ctx, args) {
1286
1437
  missingCode: "missing_characterization_test_run_evidence_ref",
1287
1438
  missingMessage: "apply-verify-packet requires --characterization-test-run-evidence-ref",
1288
1439
  });
1289
- blockers.push(...executorRefs.blockers, ...codeReviewRefs.blockers, ...greenRefs.blockers);
1440
+ const alternativeRefs = validate_alternative_verification_evidence_refs(ctx, args.alternative_verification_evidence_refs ?? [], taskId);
1441
+ blockers.push(...executorRefs.blockers, ...codeReviewRefs.blockers);
1290
1442
  if (executorRefs.refs[0]) {
1291
1443
  blockers.push(...worker_report_origin_blockers(executorRefs.refs[0], activeChain?.executor_packet_fingerprint, "executor_report_ref"));
1292
1444
  if (activeChain) {
@@ -1296,19 +1448,47 @@ function apply_verify_packet(ctx, args) {
1296
1448
  if (executorRefs.refs[0] && codeReviewRefs.refs[0]) {
1297
1449
  blockers.push(...worker_report_input_blockers(codeReviewRefs.refs[0], [executorRefs.refs[0]], "task_code_review_report_ref"));
1298
1450
  }
1299
- if ((args.red_test_run_evidence_refs ?? []).length === 0 && (args.characterization_test_run_evidence_refs ?? []).length === 0) {
1300
- blockers.push(reason("missing_pre_edit_test_run_evidence_ref", "apply-verify-packet requires --red-test-run-evidence-ref or --characterization-test-run-evidence-ref"));
1451
+ if (completionProofKind === "green_tests") {
1452
+ blockers.push(...greenRefs.blockers);
1453
+ if ((args.red_test_run_evidence_refs ?? []).length === 0 && (args.characterization_test_run_evidence_refs ?? []).length === 0) {
1454
+ blockers.push(reason("missing_pre_edit_test_run_evidence_ref", "apply-verify-packet requires --red-test-run-evidence-ref or --characterization-test-run-evidence-ref"));
1455
+ }
1456
+ else {
1457
+ if ((args.red_test_run_evidence_refs ?? []).length > 0)
1458
+ blockers.push(...redRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, redRefs.refs, "red", taskId));
1459
+ if ((args.characterization_test_run_evidence_refs ?? []).length > 0)
1460
+ blockers.push(...characterizationRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, characterizationRefs.refs, "characterization", taskId));
1461
+ }
1301
1462
  }
1302
1463
  else {
1303
- if ((args.red_test_run_evidence_refs ?? []).length > 0)
1304
- blockers.push(...redRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, redRefs.refs, "red", taskId));
1305
- if ((args.characterization_test_run_evidence_refs ?? []).length > 0)
1306
- blockers.push(...characterizationRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, characterizationRefs.refs, "characterization", taskId));
1464
+ blockers.push(...alternativeRefs.blockers);
1465
+ const activePreIds = Array.isArray(activeChain?.pre_edit_evidence_refs) ? activeChain.pre_edit_evidence_refs.map(String).filter(Boolean) : [];
1466
+ if (activeChain?.pre_edit_proof_kind !== "no_tdd_declared") {
1467
+ blockers.push(reason("apply_worker_chain_active_invalid", "alternative verification requires active apply_worker_chain pre_edit_proof_kind=no_tdd_declared"));
1468
+ }
1469
+ if (activePreIds.length > 0) {
1470
+ blockers.push(reason("apply_worker_chain_active_invalid", `alternative verification requires empty active pre_edit_evidence_refs: ${renderList(activePreIds)}`, activePreIds));
1471
+ }
1472
+ if (tddRequired || activeChain?.tdd_required !== false) {
1473
+ blockers.push(reason("apply_worker_chain_active_invalid", "alternative verification requires task and active apply_worker_chain tdd_required=false"));
1474
+ }
1475
+ if (executionSurface !== "implementation" && executionSurface !== "runtime_config") {
1476
+ blockers.push(reason("alternative_verification_surface_invalid", `alternative verification worker-chain proof requires implementation/runtime_config surface, got ${executionSurface}`));
1477
+ }
1478
+ if (activeChain?.apply_execution_surface !== executionSurface) {
1479
+ blockers.push(reason("apply_worker_chain_active_invalid", "active apply_worker_chain apply_execution_surface must match task surface"));
1480
+ }
1481
+ if (!NO_TDD_REASONS.has(String(activeChain?.no_tdd_reason ?? "")) || activeChain?.no_tdd_reason !== task?.attrs.no_tdd_reason) {
1482
+ blockers.push(reason("apply_worker_chain_active_invalid", "active apply_worker_chain no_tdd_reason must be valid and match task"));
1483
+ }
1307
1484
  }
1308
1485
  packet.apply_worker_chain_id = chainId;
1486
+ packet.completion_proof_kind = completionProofKind;
1487
+ packet.pre_edit_proof_kind = activeChain?.pre_edit_proof_kind ?? "red_or_characterization";
1309
1488
  packet.executor_report_pinned_refs = executorRefs.refs;
1310
1489
  packet.task_code_review_report_pinned_refs = codeReviewRefs.refs;
1311
1490
  packet.green_test_run_evidence_pinned_refs = greenRefs.refs;
1491
+ packet.alternative_verification_evidence_pinned_refs = alternativeRefs.refs;
1312
1492
  packet.red_test_run_evidence_pinned_refs = redRefs.refs;
1313
1493
  packet.characterization_test_run_evidence_pinned_refs = characterizationRefs.refs;
1314
1494
  if (executorRefs.refs[0])
@@ -1318,13 +1498,14 @@ function apply_verify_packet(ctx, args) {
1318
1498
  if (executorRefs.refs[0] && codeReviewRefs.refs[0]) {
1319
1499
  packet.expected_code_review_input_ref_digest = worker_input_ref_digest([executorRefs.refs[0]]);
1320
1500
  }
1321
- if (executorRefs.refs[0] && codeReviewRefs.refs[0] && greenRefs.refs[0]) {
1501
+ if (completionProofKind === "green_tests" && executorRefs.refs[0] && codeReviewRefs.refs[0] && greenRefs.refs[0]) {
1322
1502
  const canonicalGreenRefs = [...greenRefs.refs].sort((a, b) => String(a.evidence_id ?? "").localeCompare(String(b.evidence_id ?? "")));
1323
1503
  const expectedFreshness = compute_apply_worker_freshness(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId, chainId, {
1324
1504
  executor_report_ref: executorRefs.refs[0],
1325
1505
  task_code_review_report_ref: codeReviewRefs.refs[0],
1326
1506
  green_test_run_evidence_ref: String(canonicalGreenRefs[0].evidence_id ?? ""),
1327
1507
  green_test_run_evidence_refs: canonicalGreenRefs.map((ev) => String(ev.evidence_id ?? "")).filter(Boolean),
1508
+ completion_proof_kind: "green_tests",
1328
1509
  });
1329
1510
  packet.expected_freshness_fingerprint = expectedFreshness;
1330
1511
  const preEditRefs = [
@@ -1351,8 +1532,34 @@ function apply_verify_packet(ctx, args) {
1351
1532
  blockers.push(reason("protected_path_dirty", `apply-verify-packet requires protected change artifacts to stay unchanged during executor-worker verification: ${renderList(expectedFreshness.protected_dirty_paths.map(String))}`, expectedFreshness.protected_dirty_paths.map(String)));
1352
1533
  }
1353
1534
  }
1535
+ else if (completionProofKind === "alternative_verification" && executorRefs.refs[0] && codeReviewRefs.refs[0] && alternativeRefs.refs[0]) {
1536
+ const canonicalAlternativeRefs = [...alternativeRefs.refs].sort((a, b) => String(a.evidence_id ?? "").localeCompare(String(b.evidence_id ?? "")));
1537
+ const expectedFreshness = compute_apply_worker_freshness(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId, chainId, {
1538
+ executor_report_ref: executorRefs.refs[0],
1539
+ task_code_review_report_ref: codeReviewRefs.refs[0],
1540
+ alternative_verification_evidence_refs: canonicalAlternativeRefs.map((ev) => String(ev.evidence_id ?? "")).filter(Boolean),
1541
+ completion_proof_kind: "alternative_verification",
1542
+ });
1543
+ packet.expected_freshness_fingerprint = expectedFreshness;
1544
+ packet.expected_verifier_input_ref_digest = worker_input_ref_digest([
1545
+ executorRefs.refs[0],
1546
+ codeReviewRefs.refs[0],
1547
+ ...canonicalAlternativeRefs.map(alternative_evidence_input_ref),
1548
+ active_chain_verifier_input_ref(activeChain),
1549
+ ]);
1550
+ if (!fingerprint_matches(codeReviewRefs.refs[0].observed_implementation_fingerprint, expectedFreshness.implementation_fingerprint)) {
1551
+ blockers.push(reason("task_code_review_implementation_fingerprint_mismatch", "apply-verify-packet requires code-reviewer observed_implementation_fingerprint to match current implementation fingerprint"));
1552
+ }
1553
+ if (Array.isArray(expectedFreshness.protected_dirty_paths) && expectedFreshness.protected_dirty_paths.length > 0) {
1554
+ blockers.push(reason("protected_path_dirty", `apply-verify-packet requires protected change artifacts to stay unchanged during executor-worker verification: ${renderList(expectedFreshness.protected_dirty_paths.map(String))}`, expectedFreshness.protected_dirty_paths.map(String)));
1555
+ }
1556
+ }
1354
1557
  }
1355
1558
  packet.declared_task_write_scope = writeScope;
1559
+ packet.apply_execution_surface = executionSurface;
1560
+ packet.tdd_required = tddRequired;
1561
+ if (!tddRequired)
1562
+ packet.no_tdd_reason = task?.attrs.no_tdd_reason;
1356
1563
  packet.task_edit = decision_status(taskEdit);
1357
1564
  packet.task_complete = safe_decision_status(ctx, "task_complete", taskId);
1358
1565
  Object.assign(packet, apply_task_context_fields(ctx, taskId, task, writeScope, [
@@ -1369,20 +1576,30 @@ function apply_verify_packet(ctx, args) {
1369
1576
  require_scope_verdict: true,
1370
1577
  };
1371
1578
  packet.verification_checks = [
1372
- "red_or_characterization_pre_edit_evidence_bound_to_active_chain",
1579
+ completionProofKind === "green_tests"
1580
+ ? "red_or_characterization_pre_edit_evidence_bound_to_active_chain"
1581
+ : "no_tdd_declared_active_chain_has_empty_pre_edit_refs",
1373
1582
  "executor_report_same_chain_and_fresh",
1374
1583
  "task_code_review_report_same_chain_and_fresh",
1375
- "green_test_run_same_chain_and_pinned_transcripts",
1584
+ completionProofKind === "green_tests"
1585
+ ? "green_test_run_same_chain_and_pinned_transcripts"
1586
+ : "alternative_or_manual_verification_refs_live_pass_and_digest_bound",
1376
1587
  "current_freshness_matches_expected_freshness_fingerprint",
1377
1588
  "protected_change_artifacts_clean",
1378
1589
  ];
1379
1590
  packet.executor_report_required_fields = [...APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS];
1380
1591
  packet.code_review_report_required_fields = [...APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS];
1381
- packet.verifier_report_required_fields = [...APPLY_VERIFIER_REPORT_REQUIRED_FIELDS];
1592
+ packet.verifier_report_required_fields = [
1593
+ ...APPLY_VERIFIER_REPORT_REQUIRED_FIELDS,
1594
+ ...(completionProofKind === "green_tests"
1595
+ ? ["green_test_run_evidence_refs", "red_test_run_evidence_refs_or_characterization_test_run_evidence_refs"]
1596
+ : ["alternative_verification_evidence_refs", "apply_execution_surface", "tdd_required", "no_tdd_reason"]),
1597
+ ];
1382
1598
  packet.test_evidence_required_fields = ["command", "cwd", "exit_code", "repo_head", "implementation_fingerprint", "guard_artifact_manifest_fingerprint", "raw_log_pinned_refs"];
1383
1599
  packet.executor_report_refs = args.executor_report_refs ?? [];
1384
1600
  packet.task_code_review_report_refs = args.task_code_review_report_refs ?? [];
1385
1601
  packet.green_test_run_evidence_refs = args.green_test_run_evidence_refs ?? [];
1602
+ packet.alternative_verification_evidence_refs = args.alternative_verification_evidence_refs ?? [];
1386
1603
  packet.red_test_run_evidence_refs = args.red_test_run_evidence_refs ?? [];
1387
1604
  packet.characterization_test_run_evidence_refs = args.characterization_test_run_evidence_refs ?? [];
1388
1605
  packet.worker_state = blockers.length === 0 ? "ready" : "blocked";
@@ -29,6 +29,8 @@ export type WorkflowPacket = {
29
29
  must_read_verbatim_findings?: FindingSelector[];
30
30
  must_read_verbatim_decisions?: DecisionSelector[];
31
31
  diagnostic_command?: string;
32
+ discovery_template?: string;
33
+ discovery_rules?: string[];
32
34
  };
33
35
  export type ReviewPacket = {
34
36
  consumer: "role" | "main-thread";
@@ -25,6 +25,11 @@ export declare function parse_spec_scenarios(changeRoot: string): string[];
25
25
  export declare function test_contract_covers_scenario(changeRoot: string, scenario: string): boolean;
26
26
  export declare function test_contract_invariant_refs_by_test(changeRoot: string): Map<string, Set<string>>;
27
27
  export declare function task_test_refs(tasks: Record<string, TaskInfo>): Set<string>;
28
+ export declare const APPLY_EXECUTION_SURFACES: Set<string>;
29
+ export type ApplyExecutionSurface = "implementation" | "runtime_config" | "docs_generated" | "no_code";
30
+ export declare function docs_generated_write_scope_reasons(writeScope: string[], taskId?: string): Reason[];
31
+ export declare function task_apply_execution_surface(task: TaskInfo): ApplyExecutionSurface | string;
32
+ export declare function task_apply_execution_surface_reasons(taskId: string, task: TaskInfo): Reason[];
28
33
  export declare function write_scope_conflict_reasons(tasks: Record<string, TaskInfo>): Reason[];
29
34
  export declare function red_green_test_ids(evidences: JsonMap[]): Set<string>;
30
35
  export declare function task_test_evidence(evidences: JsonMap[], taskId: string, semanticStatus: string, gate?: string | null): JsonMap[];
package/dist/src/tasks.js CHANGED
@@ -243,6 +243,68 @@ export function task_test_refs(tasks) {
243
243
  }
244
244
  return refs;
245
245
  }
246
+ export const APPLY_EXECUTION_SURFACES = new Set(["implementation", "runtime_config", "docs_generated", "no_code"]);
247
+ function cleanWriteScope(scope) {
248
+ return scope.trim().replace(/\\/g, "/");
249
+ }
250
+ function unsafeWriteScope(scope) {
251
+ const parts = scope.split("/");
252
+ return scope === "" || scope === "." || scope.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..");
253
+ }
254
+ function rootMarkdown(scope) {
255
+ return !scope.includes("/") && /\.(?:md|mdx|rst|adoc)$/iu.test(scope);
256
+ }
257
+ function rootReadmeOrChangelog(scope) {
258
+ if (scope.includes("/"))
259
+ return false;
260
+ return /^(?:README|CHANGELOG)(?:\.(?:md|mdx|rst|adoc))?$/iu.test(scope);
261
+ }
262
+ function docsGeneratedScopeAllowed(scope) {
263
+ if (unsafeWriteScope(scope))
264
+ return false;
265
+ if (scope.startsWith("docs/") || scope.startsWith("doc/"))
266
+ return true;
267
+ if (rootMarkdown(scope) || rootReadmeOrChangelog(scope))
268
+ return true;
269
+ return scope.startsWith("templates/") && scope.endsWith(".md");
270
+ }
271
+ export function docs_generated_write_scope_reasons(writeScope, taskId = "") {
272
+ const invalid = writeScope
273
+ .map(cleanWriteScope)
274
+ .filter((scope) => !docsGeneratedScopeAllowed(scope))
275
+ .sort();
276
+ if (invalid.length === 0)
277
+ return [];
278
+ const prefix = taskId ? `task ${taskId} ` : "";
279
+ return [reason("invalid_docs_generated_write_scope", `${prefix}apply_execution_surface=docs_generated only allows docs/**, doc/**, root README/CHANGELOG/markdown, and templates/**/*.md write_scope entries: ${renderList(invalid)}`, invalid)];
280
+ }
281
+ export function task_apply_execution_surface(task) {
282
+ const explicit = task.attrs.apply_execution_surface?.trim();
283
+ if (explicit)
284
+ return explicit;
285
+ return splitList(task.attrs.write_scope ?? "").length > 0 ? "implementation" : "no_code";
286
+ }
287
+ export function task_apply_execution_surface_reasons(taskId, task) {
288
+ const surface = task_apply_execution_surface(task);
289
+ const writeScope = splitList(task.attrs.write_scope ?? "");
290
+ if (!APPLY_EXECUTION_SURFACES.has(surface)) {
291
+ return [reason("invalid_apply_execution_surface", `task ${taskId}: apply_execution_surface=${surface}`)];
292
+ }
293
+ if (surface === "implementation" || surface === "runtime_config") {
294
+ return writeScope.length === 0
295
+ ? [reason("missing_write_scope", `task ${taskId} apply_execution_surface=${surface} requires non-empty write_scope`)]
296
+ : [];
297
+ }
298
+ if (surface === "no_code") {
299
+ return writeScope.length === 0
300
+ ? []
301
+ : [reason("invalid_apply_execution_surface_write_scope", `task ${taskId} apply_execution_surface=no_code requires empty write_scope`, writeScope)];
302
+ }
303
+ if (writeScope.length === 0) {
304
+ return [reason("missing_write_scope", `task ${taskId} apply_execution_surface=docs_generated requires non-empty write_scope`)];
305
+ }
306
+ return docs_generated_write_scope_reasons(writeScope, taskId);
307
+ }
246
308
  export function write_scope_conflict_reasons(tasks) {
247
309
  const byGroup = {};
248
310
  for (const [taskId, task] of Object.entries(tasks)) {
@@ -53,6 +53,10 @@ export declare const ALLOWED_CONFIG_KEYS: Set<string>;
53
53
  export declare const EVIDENCE_STATUSES: Set<string>;
54
54
  export declare const EVIDENCE_KINDS: Set<string>;
55
55
  export declare const HUMAN_CONFIRMATION_GATES: Set<string>;
56
+ export declare const PENDING_CONFIRMATION_RE: RegExp;
57
+ export declare const UNCHECKED_CHECKBOX_RE: RegExp;
58
+ export declare const CHECKED_CHECKBOX_RE: RegExp;
59
+ export declare function confirmation_text_has_pending_wording(text: string): boolean;
56
60
  export declare const NO_TDD_REASONS: Set<string>;
57
61
  export declare const TDD_MODES: Set<string>;
58
62
  export declare const ROLE_EVIDENCE_FIELDS: readonly ["agent_role", "agent_id", "prompt_ref", "output_ref", "source_anchors", "target_refs"];
package/dist/src/util.js CHANGED
@@ -87,8 +87,21 @@ export const HUMAN_CONFIRMATION_GATES = new Set([
87
87
  "branch_handling",
88
88
  "apply_isolation",
89
89
  "scope_expansion",
90
+ "serial_takeover",
90
91
  "verify_failure_handling",
91
92
  ]);
93
+ // Pending-confirmation wording shared by two defenses so they cannot drift:
94
+ // - the explore_complete open-questions gate (B1) counts list items inside discovery.md's
95
+ // "待确认问题" section that carry this wording;
96
+ // - the human_confirmation self-pending guard (B3) rejects confirmation_text that carries it.
97
+ // Worded to confirmation semantics only: "仍需/还需要 + 评估/细化" (legitimate follow-up prose,
98
+ // not an unresolved confirmation) is intentionally excluded because it does not end in 确认.
99
+ export const PENDING_CONFIRMATION_RE = /仍需确认|待确认|尚未确认|未确认完|还需要[^,。;\n]{0,20}确认/u;
100
+ export const UNCHECKED_CHECKBOX_RE = /\[ \]/u;
101
+ export const CHECKED_CHECKBOX_RE = /\[[xX]\]/u;
102
+ export function confirmation_text_has_pending_wording(text) {
103
+ return PENDING_CONFIRMATION_RE.test(text);
104
+ }
92
105
  export const NO_TDD_REASONS = new Set([
93
106
  "documentation-only",
94
107
  "configuration-only",
package/dist/superspec.js CHANGED
@@ -25,14 +25,14 @@ function help() {
25
25
  " init install SuperSpec Codex surfaces (asks project/user; default project)",
26
26
  " update update SuperSpec CLI, then update manifest-managed surfaces",
27
27
  " uninstall remove manifest-managed SuperSpec surfaces",
28
- " guard run the SuperSpec guard command surface",
28
+ " check run the SuperSpec check command surface",
29
29
  " doctor diagnose SuperSpec/OpenSpec/npm/PATH wiring",
30
30
  " version print SuperSpec CLI version",
31
31
  "",
32
32
  "examples:",
33
33
  " superspec init --scope project",
34
34
  " superspec init --scope user",
35
- " superspec guard check-init --change <change> --format agent",
35
+ " superspec check check-init --change <change> --format agent",
36
36
  " superspec doctor",
37
37
  "",
38
38
  ].join("\n");
@@ -80,11 +80,11 @@ export async function main_superspec(argv = process.argv.slice(2)) {
80
80
  }
81
81
  if (command === "uninstall")
82
82
  return main_init_async([...rest, "--uninstall"]);
83
- if (command === "guard")
83
+ if (command === "check" || command === "guard")
84
84
  return main(rest);
85
85
  if (command === "doctor")
86
86
  return main_doctor(rest);
87
- // Convenience fallback: `superspec check-init ...` behaves like `superspec guard check-init ...`.
87
+ // Convenience fallback: `superspec check-init ...` behaves like `superspec check check-init ...`.
88
88
  if (command.startsWith("check-") || command === "status" || command === "recompute" || command === "init") {
89
89
  return main(argv);
90
90
  }