@rpamis/comet 0.4.0-beta.7 → 0.4.0-beta.9

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 (161) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -3
  3. package/assets/manifest.json +1 -1
  4. package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
  5. package/assets/skills/comet/scripts/comet-entry-runtime.mjs +118 -6
  6. package/assets/skills/comet/scripts/comet-hook-router.mjs +244 -26
  7. package/assets/skills/comet/scripts/comet-runtime.mjs +221 -104
  8. package/assets/skills/comet-archive/SKILL.md +29 -20
  9. package/assets/skills/comet-native/SKILL.md +23 -15
  10. package/assets/skills/comet-native/reference/artifacts.md +19 -4
  11. package/assets/skills/comet-native/reference/commands.md +8 -2
  12. package/assets/skills/comet-native/reference/recovery.md +1 -1
  13. package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1136 -471
  14. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
  15. package/assets/skills-zh/comet-archive/SKILL.md +29 -20
  16. package/assets/skills-zh/comet-native/SKILL.md +23 -15
  17. package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
  18. package/assets/skills-zh/comet-native/reference/commands.md +8 -2
  19. package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
  20. package/dist/app/commands/init.d.ts.map +1 -1
  21. package/dist/app/commands/init.js +3 -6
  22. package/dist/app/commands/init.js.map +1 -1
  23. package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
  24. package/dist/domains/bundle/bundle-platform.js +1 -1
  25. package/dist/domains/bundle/bundle-platform.js.map +1 -1
  26. package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
  27. package/dist/domains/comet-classic/classic-guard.js +3 -2
  28. package/dist/domains/comet-classic/classic-guard.js.map +1 -1
  29. package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
  30. package/dist/domains/comet-entry/current-selection.js +6 -8
  31. package/dist/domains/comet-entry/current-selection.js.map +1 -1
  32. package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
  33. package/dist/domains/comet-entry/project-status.js +5 -1
  34. package/dist/domains/comet-entry/project-status.js.map +1 -1
  35. package/dist/domains/comet-native/native-archive-transaction.d.ts.map +1 -1
  36. package/dist/domains/comet-native/native-archive-transaction.js +5 -6
  37. package/dist/domains/comet-native/native-archive-transaction.js.map +1 -1
  38. package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -1
  39. package/dist/domains/comet-native/native-atomic-file.js +11 -7
  40. package/dist/domains/comet-native/native-atomic-file.js.map +1 -1
  41. package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -1
  42. package/dist/domains/comet-native/native-bounded-file.js +11 -7
  43. package/dist/domains/comet-native/native-bounded-file.js.map +1 -1
  44. package/dist/domains/comet-native/native-build-evidence.js +2 -2
  45. package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
  46. package/dist/domains/comet-native/native-change.d.ts +4 -2
  47. package/dist/domains/comet-native/native-change.d.ts.map +1 -1
  48. package/dist/domains/comet-native/native-change.js +18 -4
  49. package/dist/domains/comet-native/native-change.js.map +1 -1
  50. package/dist/domains/comet-native/native-check-receipt-storage.d.ts.map +1 -1
  51. package/dist/domains/comet-native/native-check-receipt-storage.js +11 -7
  52. package/dist/domains/comet-native/native-check-receipt-storage.js.map +1 -1
  53. package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
  54. package/dist/domains/comet-native/native-check-receipt.js +16 -11
  55. package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
  56. package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
  57. package/dist/domains/comet-native/native-cli.js +120 -16
  58. package/dist/domains/comet-native/native-cli.js.map +1 -1
  59. package/dist/domains/comet-native/native-config.d.ts +5 -1
  60. package/dist/domains/comet-native/native-config.d.ts.map +1 -1
  61. package/dist/domains/comet-native/native-config.js +78 -0
  62. package/dist/domains/comet-native/native-config.js.map +1 -1
  63. package/dist/domains/comet-native/native-continuation.d.ts +2 -1
  64. package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
  65. package/dist/domains/comet-native/native-continuation.js +13 -5
  66. package/dist/domains/comet-native/native-continuation.js.map +1 -1
  67. package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
  68. package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
  69. package/dist/domains/comet-native/native-diagnostics.js +19 -9
  70. package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
  71. package/dist/domains/comet-native/native-evidence-retention.d.ts.map +1 -1
  72. package/dist/domains/comet-native/native-evidence-retention.js +5 -3
  73. package/dist/domains/comet-native/native-evidence-retention.js.map +1 -1
  74. package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -1
  75. package/dist/domains/comet-native/native-evidence-storage.js +11 -7
  76. package/dist/domains/comet-native/native-evidence-storage.js.map +1 -1
  77. package/dist/domains/comet-native/native-file-identity.d.ts +3 -0
  78. package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -0
  79. package/dist/domains/comet-native/native-file-identity.js +2 -0
  80. package/dist/domains/comet-native/native-file-identity.js.map +1 -0
  81. package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
  82. package/dist/domains/comet-native/native-findings.js +19 -1
  83. package/dist/domains/comet-native/native-findings.js.map +1 -1
  84. package/dist/domains/comet-native/native-guards.d.ts +2 -1
  85. package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
  86. package/dist/domains/comet-native/native-guards.js +14 -0
  87. package/dist/domains/comet-native/native-guards.js.map +1 -1
  88. package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
  89. package/dist/domains/comet-native/native-lock.js +30 -35
  90. package/dist/domains/comet-native/native-lock.js.map +1 -1
  91. package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -1
  92. package/dist/domains/comet-native/native-protected-file.js +9 -8
  93. package/dist/domains/comet-native/native-protected-file.js.map +1 -1
  94. package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
  95. package/dist/domains/comet-native/native-root-move.js +2 -0
  96. package/dist/domains/comet-native/native-root-move.js.map +1 -1
  97. package/dist/domains/comet-native/native-run-store.d.ts.map +1 -1
  98. package/dist/domains/comet-native/native-run-store.js +9 -8
  99. package/dist/domains/comet-native/native-run-store.js.map +1 -1
  100. package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
  101. package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
  102. package/dist/domains/comet-native/native-snapshot.js +225 -5
  103. package/dist/domains/comet-native/native-snapshot.js.map +1 -1
  104. package/dist/domains/comet-native/native-transitions.d.ts +2 -1
  105. package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
  106. package/dist/domains/comet-native/native-transitions.js +34 -11
  107. package/dist/domains/comet-native/native-transitions.js.map +1 -1
  108. package/dist/domains/comet-native/native-types.d.ts +16 -0
  109. package/dist/domains/comet-native/native-types.d.ts.map +1 -1
  110. package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
  111. package/dist/domains/comet-native/native-verification-runtime.js +5 -3
  112. package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
  113. package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
  114. package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
  115. package/dist/domains/comet-native/native-verification-scope.js +6 -1
  116. package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
  117. package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
  118. package/dist/domains/dashboard/native-collector.js +4 -1
  119. package/dist/domains/dashboard/native-collector.js.map +1 -1
  120. package/dist/domains/dashboard/web/index.html +14 -14
  121. package/dist/domains/skill/platform-install.d.ts.map +1 -1
  122. package/dist/domains/skill/platform-install.js +9 -0
  123. package/dist/domains/skill/platform-install.js.map +1 -1
  124. package/dist/domains/skill/project-instructions.d.ts.map +1 -1
  125. package/dist/domains/skill/project-instructions.js +4 -2
  126. package/dist/domains/skill/project-instructions.js.map +1 -1
  127. package/dist/domains/workflow-contract/project-config.d.ts +1 -1
  128. package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
  129. package/dist/domains/workflow-contract/project-config.js +24 -2
  130. package/dist/domains/workflow-contract/project-config.js.map +1 -1
  131. package/dist/platform/fs/file-identity.d.ts +9 -0
  132. package/dist/platform/fs/file-identity.d.ts.map +1 -0
  133. package/dist/platform/fs/file-identity.js +21 -0
  134. package/dist/platform/fs/file-identity.js.map +1 -0
  135. package/dist/platform/fs/file-system.d.ts.map +1 -1
  136. package/dist/platform/fs/file-system.js +13 -9
  137. package/dist/platform/fs/file-system.js.map +1 -1
  138. package/dist/platform/fs/race-safe-read.d.ts +47 -0
  139. package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
  140. package/dist/platform/fs/race-safe-read.js +132 -0
  141. package/dist/platform/fs/race-safe-read.js.map +1 -0
  142. package/eval/README.md +1 -1
  143. package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
  144. package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
  145. package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
  146. package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
  147. package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
  148. package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
  149. package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
  150. package/eval/local/tasks/index.yaml +6 -0
  151. package/eval/local/tests/conftest.py +146 -2
  152. package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
  153. package/eval/local/tests/scaffold/test_tasks.py +440 -8
  154. package/eval/local/tests/scaffold/test_utils.py +114 -1
  155. package/eval/local/tests/tasks/test_tasks.py +2 -0
  156. package/eval/pyproject.toml +4 -4
  157. package/eval/scaffold/python/aligned_comparison.py +1 -0
  158. package/eval/scaffold/python/tasks.py +2 -0
  159. package/eval/scaffold/shell/docker.sh +1 -1
  160. package/eval/scaffold/shell/run-claude-loop.sh +25 -2
  161. package/package.json +8 -1
@@ -0,0 +1,618 @@
1
+ """Validate deep Sequential clarification and terminal Native artifacts."""
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import yaml
11
+
12
+
13
+ WORKSPACE = Path("/workspace")
14
+ RESULTS_FILE = os.environ.get("BENCH_TEST_RESULTS", "_test_results.json")
15
+ QUESTION_SIGNAL = re.compile(
16
+ r"^(?:\[blocking\]\s*)?\**\bQUESTION\b(?:\s*\([^)\n]*\))?\**\s*(?::\**|$)",
17
+ re.IGNORECASE | re.MULTILINE,
18
+ )
19
+ CONFIRM_MARKER = re.compile(r"\[blocking\]\s*CONFIRM", re.IGNORECASE)
20
+ REQUIRED_ABBREVIATIONS = (
21
+ "dr.",
22
+ "mr.",
23
+ "mrs.",
24
+ "ms.",
25
+ "prof.",
26
+ "sr.",
27
+ "jr.",
28
+ "e.g.",
29
+ "i.e.",
30
+ "etc.",
31
+ "vs.",
32
+ "inc.",
33
+ "ltd.",
34
+ "corp.",
35
+ "st.",
36
+ "ave.",
37
+ )
38
+
39
+
40
+ def passed(name: str):
41
+ return {"check": name, "status": "passed"}
42
+
43
+
44
+ def failed(name: str, reason: str):
45
+ return {"check": name, "status": "failed", "reason": reason}
46
+
47
+
48
+ def read_context() -> dict:
49
+ path = WORKSPACE / "_test_context.json"
50
+ if not path.is_file():
51
+ return {}
52
+ return json.loads(path.read_text(encoding="utf-8"))
53
+
54
+
55
+ def decision_topics(text: str) -> set[str]:
56
+ primary_question = re.split(r"\n\s*\n", text.strip(), maxsplit=1)[0]
57
+ normalized = " ".join(primary_question.lower().split())
58
+ topics = set()
59
+ embedded_token = bool(
60
+ re.search(
61
+ r"\bterminal\s+characters?\b.{0,80}\b(?:embedded|inside)\b|"
62
+ r"\b(?:embedded|inside)\b.{0,80}\b(?:word|prose\s+token|token)\b",
63
+ normalized,
64
+ )
65
+ )
66
+ if re.search(r"\babbrevi|e\.g\.|\bdr\.", normalized) or embedded_token:
67
+ content_signal = bool(
68
+ re.search(
69
+ r"\b(?:exact|entries|items|members|contents?)\b|"
70
+ r"\b(?:which|what)\s+abbreviations?\b|"
71
+ r"\bset\s+of\s+(?:common\s+)?abbreviations?\b",
72
+ normalized,
73
+ )
74
+ )
75
+ strategy_signal = bool(
76
+ re.search(
77
+ r"\b(?:maintain|maintained|maintenance|define|defined|recognize|"
78
+ r"recognized|identify|rule|approach|hardcoded|configurable)\b",
79
+ normalized,
80
+ )
81
+ )
82
+ if content_signal:
83
+ topics.add("abbreviation-list-content")
84
+ if strategy_signal:
85
+ topics.add("abbreviation-list-strategy")
86
+ if not content_signal and not strategy_signal:
87
+ topics.add("abbreviation-policy")
88
+ if re.search(
89
+ r"\bempty(?:\s+or\s+whitespace(?:-only)?)?\s+input\b|"
90
+ r"\bempty\s+or\s+whitespace-only\b|"
91
+ r"\bno\s+input\b|\bzero[- ]length\b",
92
+ normalized,
93
+ ):
94
+ topics.add("empty")
95
+ if "?!" in normalized or re.search(
96
+ r"\b(?:consecutive|contiguous|run\s+of)\b.{0,80}"
97
+ r"\b(?:terminators?|terminal\s+characters?|punctuation|endings?)\b",
98
+ normalized,
99
+ ):
100
+ topics.add("terminator-run")
101
+ return topics
102
+
103
+
104
+ def missing_decisions(text: str) -> list[str]:
105
+ """Return fixed user decisions that are not preserved semantically."""
106
+ normalized = " ".join(text.lower().split())
107
+ missing = []
108
+ abbreviation = "abbrevi" in normalized and bool(
109
+ re.search(
110
+ r"(?:do|does|should)\s+not\s+(?:end|count)|"
111
+ r"(?:ignored|filtered|excluded|suppressed?)\s+as\s+(?:a\s+)?"
112
+ r"sentence\s+boundar|"
113
+ r"suppress(?:es|ed|ing)?\s+(?:their\s+)?periods?.{0,60}"
114
+ r"(?:sentence\s+)?boundar|"
115
+ r"abbrevi.{0,240}not.{0,80}(?:sentence\s+(?:ending|boundar)|false\s+boundar)",
116
+ normalized,
117
+ )
118
+ )
119
+ if not abbreviation:
120
+ missing.append("abbreviation behavior")
121
+
122
+ abbreviation_list = "abbrevi" in normalized and bool(
123
+ re.search(
124
+ r"(?:small|explicit|known).{0,50}(?:list|set|allowlist|collection)"
125
+ r".{0,100}(?:e\.g\.|dr\.)|"
126
+ r"(?:list|set|allowlist|collection).{0,80}(?:e\.g\.|dr\.)",
127
+ normalized,
128
+ )
129
+ )
130
+ if not abbreviation_list:
131
+ missing.append("abbreviation collection strategy")
132
+ if not all(item in normalized for item in REQUIRED_ABBREVIATIONS):
133
+ missing.append("exact abbreviation collection")
134
+
135
+ empty = "empty" in normalized and bool(
136
+ re.search(
137
+ r"empty.{0,180}(?:returns?|prints?|result(?:\s+is)?|sentences:)"
138
+ r"[^0-9]{0,24}0\b",
139
+ normalized,
140
+ )
141
+ )
142
+ if not empty:
143
+ missing.append("empty-input count")
144
+
145
+ terminators = bool(
146
+ re.search(
147
+ r"(?:consecutive|contiguous|run\s+of).{0,100}"
148
+ r"(?:terminator|terminal\s+character|punctuation)",
149
+ normalized,
150
+ )
151
+ and re.search(r"(?:one|single|exactly\s+one).{0,40}(?:sentence\s+)?boundar", normalized)
152
+ )
153
+ if not terminators:
154
+ missing.append("terminator-run behavior")
155
+ return missing
156
+
157
+
158
+ def contradictory_decisions(text: str) -> list[str]:
159
+ normalized = " ".join(text.lower().split())
160
+ abbreviation_enabled = bool(
161
+ re.search(
162
+ r"(?:do|does|should)\s+not\s+(?:end|count)|"
163
+ r"suppress(?:es|ed|ing)?\s+(?:their\s+)?periods?.{0,60}"
164
+ r"(?:sentence\s+)?boundar|"
165
+ r"abbrevi.{0,240}not.{0,80}(?:sentence\s+(?:ending|boundar)|false\s+boundar)",
166
+ normalized,
167
+ )
168
+ )
169
+ abbreviation_disabled = bool(
170
+ re.search(
171
+ r"abbrevi.{0,160}(?:no\s+special[- ]cas|ordinary\s+prose\s+tokens?.{0,80}"
172
+ r"no\s+special[- ]cas)",
173
+ normalized,
174
+ )
175
+ )
176
+ return ["abbreviation behavior"] if abbreviation_enabled and abbreviation_disabled else []
177
+
178
+
179
+ def missing_shared_understanding(text: str) -> list[str]:
180
+ missing = missing_decisions(text)
181
+ normalized = " ".join(re.sub(r"[*_`#]", "", text.lower()).split())
182
+ summary_fields = {
183
+ "goal/outcome": r"\b(?:goal|outcome)\b",
184
+ "scope": r"\b(?:scope|in scope)\b",
185
+ "explicit non-goals": r"\b(?:explicit\s+non-goals?|non-goals?|out of scope)\b",
186
+ "acceptance criteria": r"\b(?:acceptance(?:\s+criteria)?|accepted when|done when)\b",
187
+ }
188
+ for label, pattern in summary_fields.items():
189
+ if not re.search(pattern, normalized):
190
+ missing.append(label)
191
+ return missing
192
+
193
+
194
+ def is_confirmation_request(text: str) -> bool:
195
+ normalized = " ".join(text.lower().split())
196
+ if CONFIRM_MARKER.search(text):
197
+ return True
198
+ summary = bool(
199
+ re.search(
200
+ r"\b(?:(?:shared|my|our|current|final)[- ]+understanding|"
201
+ r"understanding summary|confirmation summary)\b",
202
+ normalized,
203
+ )
204
+ )
205
+ request = bool(
206
+ re.search(
207
+ r"\b(?:please\s+)?confirm\b|"
208
+ r"\breply\b.{0,40}\bconfirmed\b|"
209
+ r"\brespond\b.{0,40}\bconfirmed\b",
210
+ normalized,
211
+ )
212
+ )
213
+ return summary and request
214
+
215
+
216
+ def command_mutates_target(command: str, target: str) -> bool:
217
+ if not re.search(target, command, re.IGNORECASE):
218
+ return False
219
+ return bool(
220
+ re.search(
221
+ rf"(?i)(?:>>?|tee(?:\s+-\w+)*|set-content|add-content|out-file)"
222
+ rf"\s*[^\r\n]*{target}|"
223
+ rf"(?:\b(?:apply_patch|cp|mv|touch|new-item|write_text|write_bytes|"
224
+ rf"writealltext|writeallbytes)\b|"
225
+ rf"\*\*\*\s+(?:Add|Update|Delete)\s+File:|"
226
+ rf"\bsed\b[^\r\n]*\s-i\b|\bperl\b[^\r\n]*\s-(?:p?i|i)\b"
227
+ rf")[^\r\n]*{target}|"
228
+ rf"{target}[^\r\n]{{0,200}}\.(?:write_text|write_bytes)\b|"
229
+ rf"\bopen\s*\([^\r\n]*{target}[^\r\n]*,\s*['\"][wa]",
230
+ command,
231
+ )
232
+ )
233
+
234
+
235
+ def successful_tool_call(tool_call: dict) -> bool:
236
+ return tool_call.get("success") is True
237
+
238
+
239
+ def excluded_evidence_path(path: str) -> bool:
240
+ normalized = path.replace("\\", "/").lower()
241
+ if normalized.startswith("/workspace/"):
242
+ normalized = normalized[len("/workspace/") :]
243
+ elif normalized.startswith("/"):
244
+ return True
245
+ while normalized.startswith("./"):
246
+ normalized = normalized[2:]
247
+ return normalized.startswith((".eval-", "_eval_")) or normalized in {
248
+ "_test_context.json",
249
+ "_test_results.json",
250
+ }
251
+
252
+
253
+ def implementation_write(tool_call: dict) -> bool:
254
+ if not successful_tool_call(tool_call):
255
+ return False
256
+ name = str(tool_call.get("name") or "").lower()
257
+ path = str(tool_call.get("path") or "").replace("\\", "/").lower()
258
+ target_path = path.endswith(".py") and not excluded_evidence_path(path)
259
+ if target_path and name in {"write", "edit", "multiedit", "notebookedit"}:
260
+ return True
261
+
262
+ command = str(tool_call.get("command") or "")
263
+ if name not in {"bash", "shell", "powershell"} or not command:
264
+ return False
265
+ python_target = r"(?:[a-z]:)?[./\\\w-]*\.py\b"
266
+ if not command_mutates_target(command, python_target):
267
+ return False
268
+ paths = re.findall(python_target, command, re.IGNORECASE)
269
+ return any(not excluded_evidence_path(path) for path in paths)
270
+
271
+
272
+ def investigated_existing_implementation(tool_call: dict) -> bool:
273
+ if not successful_tool_call(tool_call):
274
+ return False
275
+ name = str(tool_call.get("name") or "").lower()
276
+ path = str(tool_call.get("path") or "").replace("\\", "/").lower()
277
+ command = str(tool_call.get("command") or "")
278
+ if name == "read" and re.search(r"(?:^|/)wordcount\.py$", path):
279
+ return True
280
+ return name in {"bash", "shell", "powershell"} and bool(
281
+ re.search(r"(?i)\bwordcount\.py\b", command)
282
+ )
283
+
284
+
285
+ def workspace_relative_path(path: str) -> str | None:
286
+ normalized = path.replace("\\", "/").lower()
287
+ if normalized.startswith("/workspace/"):
288
+ return normalized[len("/workspace/") :]
289
+ if normalized.startswith("/"):
290
+ return None
291
+ while normalized.startswith("./"):
292
+ normalized = normalized[2:]
293
+ return normalized
294
+
295
+
296
+ def persisted_native_decision_kinds(tool_call: dict) -> set[str]:
297
+ if not successful_tool_call(tool_call):
298
+ return set()
299
+ name = str(tool_call.get("name") or "").lower()
300
+ relative_path = workspace_relative_path(str(tool_call.get("path") or ""))
301
+ native_root = "docs/comet/changes/add-sentence-counting/"
302
+ kinds = set()
303
+ if name in {"write", "edit", "multiedit"} and relative_path:
304
+ if relative_path == native_root + "brief.md":
305
+ kinds.add("brief")
306
+ if re.fullmatch(
307
+ re.escape(native_root) + r"specs/[^/]+/spec\.md",
308
+ relative_path,
309
+ ):
310
+ kinds.add("spec")
311
+
312
+ command = str(tool_call.get("command") or "")
313
+ if name not in {"bash", "shell", "powershell"} or not command:
314
+ return kinds
315
+ root_pattern = r"(?:/workspace/|\./)?docs[/\\]comet[/\\]changes[/\\]"
316
+ root_pattern += r"add-sentence-counting[/\\]"
317
+ brief_pattern = root_pattern + r"brief\.md\b"
318
+ spec_pattern = root_pattern + r"specs[/\\][^/\\\s]+[/\\]spec\.md\b"
319
+ if command_mutates_target(command, brief_pattern):
320
+ kinds.add("brief")
321
+ if command_mutates_target(command, spec_pattern):
322
+ kinds.add("spec")
323
+ return kinds
324
+
325
+
326
+ def check_clarification_protocol():
327
+ context = read_context()
328
+ if context.get("treatment_name") != "COMET_NATIVE_SEQUENTIAL":
329
+ return failed(
330
+ "clarification_depth_protocol",
331
+ f"Unexpected treatment: {context.get('treatment_name')!r}",
332
+ )
333
+
334
+ config_file = WORKSPACE / ".comet" / "config.yaml"
335
+ if not config_file.is_file():
336
+ return failed("clarification_depth_protocol", "Native project config is missing")
337
+ config = yaml.safe_load(config_file.read_text(encoding="utf-8")) or {}
338
+ mode = (config.get("native") or {}).get("clarification_mode")
339
+ if mode != "sequential":
340
+ return failed(
341
+ "clarification_depth_protocol",
342
+ f"Expected native.clarification_mode sequential, got {mode!r}",
343
+ )
344
+
345
+ interaction = context.get("interaction") or {}
346
+ if interaction.get("mode") != "auto_user" or interaction.get("deterministic_replies") != 6:
347
+ return failed("clarification_depth_protocol", "Interactive simulator metadata is invalid")
348
+ if interaction.get("decision_points") != 6:
349
+ return failed(
350
+ "clarification_depth_protocol",
351
+ f"Expected six decision rounds, got {interaction.get('decision_points')!r}",
352
+ )
353
+
354
+ turns = interaction.get("subject_turns")
355
+ if not isinstance(turns, list) or not turns:
356
+ return failed("clarification_depth_protocol", "Subject-turn evidence is missing")
357
+ if interaction.get("actual_turns") != len(turns):
358
+ return failed(
359
+ "clarification_depth_protocol",
360
+ "Subject-turn evidence does not match the loop turn count",
361
+ )
362
+ if any(
363
+ not isinstance(turn.get("tool_calls"), list)
364
+ or any(
365
+ not isinstance(tool, dict) or not isinstance(tool.get("success"), bool)
366
+ for tool in turn.get("tool_calls") or []
367
+ )
368
+ for turn in turns
369
+ ):
370
+ return failed("clarification_depth_protocol", "Tool-call evidence is incomplete")
371
+
372
+ decision_turns = [
373
+ (index, turn)
374
+ for index, turn in enumerate(turns)
375
+ if QUESTION_SIGNAL.search(str(turn.get("result") or ""))
376
+ or is_confirmation_request(str(turn.get("result") or ""))
377
+ ]
378
+ if len(decision_turns) != 6:
379
+ return failed(
380
+ "clarification_depth_protocol",
381
+ f"Expected six decision turns with a final confirmation, got {len(decision_turns)}",
382
+ )
383
+
384
+ topic_order = []
385
+ for round_number, (_, turn) in enumerate(decision_turns[:5], start=1):
386
+ result = str(turn.get("result") or "")
387
+ if len(QUESTION_SIGNAL.findall(result)) != 1 or is_confirmation_request(result):
388
+ return failed(
389
+ "clarification_depth_protocol",
390
+ f"Decision round {round_number} must contain exactly one product question",
391
+ )
392
+ question_text = QUESTION_SIGNAL.split(result, maxsplit=1)[-1]
393
+ question_prompt = re.split(
394
+ r"\**\brecommend(?:ation|ed)?\b(?:\s+option)?\**\s*:",
395
+ question_text,
396
+ maxsplit=1,
397
+ flags=re.IGNORECASE,
398
+ )[0]
399
+ topics = decision_topics(question_prompt)
400
+ if len(topics) != 1:
401
+ return failed(
402
+ "clarification_depth_protocol",
403
+ f"Decision round {round_number} must ask exactly one hidden behavior decision",
404
+ )
405
+ if "recommend" not in question_text.lower() or "impact" not in question_text.lower():
406
+ return failed(
407
+ "clarification_depth_protocol",
408
+ f"Decision round {round_number} lacks recommendation or impact",
409
+ )
410
+ topic_order.append(next(iter(topics)))
411
+ if topic_order != [
412
+ "empty",
413
+ "terminator-run",
414
+ "abbreviation-policy",
415
+ "abbreviation-list-strategy",
416
+ "abbreviation-list-content",
417
+ ]:
418
+ return failed(
419
+ "clarification_depth_protocol",
420
+ "Sequential questions did not resolve the hidden and dependent decisions in order",
421
+ )
422
+
423
+ confirmation_index, confirmation_turn = decision_turns[5]
424
+ confirmation = str(confirmation_turn.get("result") or "")
425
+ if QUESTION_SIGNAL.search(confirmation) or not is_confirmation_request(confirmation):
426
+ return failed(
427
+ "clarification_depth_protocol",
428
+ "The final decision round must be one shared-understanding confirmation",
429
+ )
430
+ missing_confirmation = missing_shared_understanding(confirmation)
431
+ if missing_confirmation:
432
+ return failed(
433
+ "clarification_depth_protocol",
434
+ "The confirmation summary is incomplete: " + ", ".join(missing_confirmation),
435
+ )
436
+ first_question_index = decision_turns[0][0]
437
+ investigated = any(
438
+ investigated_existing_implementation(tool_call)
439
+ for turn in turns[: first_question_index + 1]
440
+ for tool_call in turn.get("tool_calls") or []
441
+ )
442
+ if not investigated:
443
+ return failed(
444
+ "clarification_depth_protocol",
445
+ "The existing implementation was not investigated before the first decision",
446
+ )
447
+ for round_number, (_, turn) in enumerate(decision_turns[1:], start=2):
448
+ persistence_kinds = set()
449
+ for tool_call in turn.get("tool_calls") or []:
450
+ persistence_kinds.update(persisted_native_decision_kinds(tool_call))
451
+ if persistence_kinds != {"brief", "spec"}:
452
+ missing_persistence = sorted({"brief", "spec"} - persistence_kinds)
453
+ return failed(
454
+ "clarification_depth_protocol",
455
+ f"Decision round {round_number} lacks prior-answer persistence evidence for "
456
+ + ", ".join(missing_persistence),
457
+ )
458
+ for turn in turns[: confirmation_index + 1]:
459
+ for tool_call in turn.get("tool_calls") or []:
460
+ if implementation_write(tool_call):
461
+ return failed(
462
+ "clarification_depth_protocol",
463
+ "Implementation files were modified before explicit confirmation",
464
+ )
465
+ implemented_after_confirmation = any(
466
+ implementation_write(tool_call)
467
+ for turn in turns[confirmation_index + 1 :]
468
+ for tool_call in turn.get("tool_calls") or []
469
+ )
470
+ if not implemented_after_confirmation:
471
+ return failed(
472
+ "clarification_depth_protocol",
473
+ "No implementation write evidence exists after explicit confirmation",
474
+ )
475
+ return passed("clarification_depth_protocol")
476
+
477
+
478
+ def check_behavior():
479
+ cases = [
480
+ ("", "Sentences: 0"),
481
+ ("Use e.g. examples. Ask Dr. Smith!", "Sentences: 2"),
482
+ ("Really?! Yes.", "Sentences: 2"),
483
+ ]
484
+ try:
485
+ subprocess.run(
486
+ [sys.executable, "-m", "pytest", "-q"],
487
+ cwd=WORKSPACE,
488
+ capture_output=True,
489
+ text=True,
490
+ timeout=30,
491
+ check=True,
492
+ )
493
+ for source, expected in cases:
494
+ result = subprocess.run(
495
+ [sys.executable, "wordcount.py", "--sentences"],
496
+ cwd=WORKSPACE,
497
+ input=source,
498
+ capture_output=True,
499
+ text=True,
500
+ timeout=10,
501
+ check=True,
502
+ )
503
+ if expected not in result.stdout:
504
+ return failed(
505
+ "clarification_depth_behavior",
506
+ f"Expected {expected!r}, got {result.stdout!r}",
507
+ )
508
+ except Exception as error:
509
+ return failed("clarification_depth_behavior", str(error))
510
+ return passed("clarification_depth_behavior")
511
+
512
+
513
+ def archive_directories():
514
+ root = WORKSPACE / "docs" / "comet" / "archive"
515
+ return sorted(path for path in root.glob("*-*") if path.is_dir())
516
+
517
+
518
+ def check_confirmed_archive():
519
+ active_root = WORKSPACE / "docs" / "comet" / "changes"
520
+ active = sorted(path for path in active_root.glob("*") if path.is_dir())
521
+ if active:
522
+ return failed("clarification_depth_archive", f"Active changes remain: {len(active)}")
523
+ archives = archive_directories()
524
+ if len(archives) != 1:
525
+ return failed("clarification_depth_archive", f"Expected one archive, found {len(archives)}")
526
+ archived = archives[0]
527
+ state_file = archived / "comet-state.yaml"
528
+ if not state_file.is_file():
529
+ return failed("clarification_depth_archive", "Archived state is missing")
530
+ state = yaml.safe_load(state_file.read_text(encoding="utf-8")) or {}
531
+ if (
532
+ state.get("phase") != "archive"
533
+ or state.get("archived") is not True
534
+ or state.get("approval") != "confirmed"
535
+ or state.get("verification_result") != "pass"
536
+ ):
537
+ return failed(
538
+ "clarification_depth_archive",
539
+ "Archive lacks terminal verification or explicit confirmation",
540
+ )
541
+
542
+ brief_file = archived / "brief.md"
543
+ if not brief_file.is_file():
544
+ return failed("clarification_depth_archive", "Archived brief is missing")
545
+ brief_text = brief_file.read_text(encoding="utf-8")
546
+ missing_brief = missing_decisions(brief_text)
547
+ if missing_brief:
548
+ return failed(
549
+ "clarification_depth_archive",
550
+ "Brief does not preserve decisions: " + ", ".join(missing_brief),
551
+ )
552
+ contradictory_brief = contradictory_decisions(brief_text)
553
+ if contradictory_brief:
554
+ return failed(
555
+ "clarification_depth_archive",
556
+ "Brief contains contradictory decisions: " + ", ".join(contradictory_brief),
557
+ )
558
+
559
+ spec_changes = state.get("spec_changes") or []
560
+ if len(spec_changes) != 1 or not isinstance(spec_changes[0], dict):
561
+ return failed("clarification_depth_archive", "Expected one capability specification")
562
+ capability = spec_changes[0].get("capability")
563
+ source = spec_changes[0].get("source")
564
+ canonical = WORKSPACE / "docs" / "comet" / "specs" / str(capability) / "spec.md"
565
+ archived_spec = archived / str(source or "")
566
+ if not capability or source != f"specs/{capability}/spec.md":
567
+ return failed("clarification_depth_archive", "Specification link is invalid")
568
+ if not canonical.is_file() or not archived_spec.is_file():
569
+ return failed(
570
+ "clarification_depth_archive",
571
+ "Canonical or archived specification is missing",
572
+ )
573
+ canonical_text = canonical.read_text(encoding="utf-8")
574
+ if canonical_text.lower() != archived_spec.read_text(encoding="utf-8").lower():
575
+ return failed("clarification_depth_archive", "Canonical and archived specifications differ")
576
+ missing_spec = missing_decisions(canonical_text)
577
+ if missing_spec:
578
+ return failed(
579
+ "clarification_depth_archive",
580
+ "Target specification does not preserve decisions: " + ", ".join(missing_spec),
581
+ )
582
+ contradictory_spec = contradictory_decisions(canonical_text)
583
+ if contradictory_spec:
584
+ return failed(
585
+ "clarification_depth_archive",
586
+ "Target specification contains contradictory decisions: "
587
+ + ", ".join(contradictory_spec),
588
+ )
589
+
590
+ report = archived / str(state.get("verification_report") or "")
591
+ if state.get("verification_report") != "verification.md" or not report.is_file():
592
+ return failed("clarification_depth_archive", "Verification report is missing")
593
+ if "pass" not in report.read_text(encoding="utf-8").lower():
594
+ return failed("clarification_depth_archive", "Verification report is not passing")
595
+ return passed("clarification_depth_archive")
596
+
597
+
598
+ def main():
599
+ results = [
600
+ check_clarification_protocol(),
601
+ check_behavior(),
602
+ check_confirmed_archive(),
603
+ ]
604
+ output = {
605
+ "passed": [result["check"] for result in results if result["status"] == "passed"],
606
+ "failed": [
607
+ f"{result['check']}: {result.get('reason', '')}"
608
+ for result in results
609
+ if result["status"] == "failed"
610
+ ],
611
+ }
612
+ (WORKSPACE / RESULTS_FILE).write_text(json.dumps(output, indent=2), encoding="utf-8")
613
+ print(json.dumps(output))
614
+ return 0 if not output["failed"] else 1
615
+
616
+
617
+ if __name__ == "__main__":
618
+ raise SystemExit(main())
@@ -134,7 +134,7 @@ def check_mode_and_interaction():
134
134
  if interaction.get("mode") != "auto_user" or interaction.get("deterministic_replies") != 0:
135
135
  return failed("clarification_mode_protocol", "Interactive simulator metadata is invalid")
136
136
  decision_points = interaction.get("decision_points")
137
- expected_points = 2 if expected_mode == "batch" else 3
137
+ expected_points = 2 if expected_mode == "batch" else 4
138
138
  if decision_points != expected_points:
139
139
  return failed(
140
140
  "clarification_mode_protocol",
@@ -5,6 +5,12 @@ tasks:
5
5
  - COMET_NATIVE_PHASE1
6
6
  description: Interactive Native workflow that must clarify one high-impact requirement before confirmed execution.
7
7
 
8
+ - name: comet-native-clarification-depth
9
+ category: comet-native
10
+ default_treatments:
11
+ - COMET_NATIVE_SEQUENTIAL
12
+ description: Sequential Native benchmark for discovering hidden user-owned decisions before confirmed execution.
13
+
8
14
  - name: comet-native-clarification-modes
9
15
  category: comet-native
10
16
  default_treatments: