@rpamis/comet 0.4.0-beta.8 → 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.
- package/README.md +1 -1
- package/assets/manifest.json +1 -1
- package/assets/skills/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills/comet/scripts/comet-entry-runtime.mjs +95 -4
- package/assets/skills/comet/scripts/comet-hook-router.mjs +224 -35
- package/assets/skills/comet/scripts/comet-runtime.mjs +218 -102
- package/assets/skills/comet-archive/SKILL.md +29 -20
- package/assets/skills/comet-native/SKILL.md +23 -15
- package/assets/skills/comet-native/reference/artifacts.md +19 -4
- package/assets/skills/comet-native/reference/commands.md +8 -2
- package/assets/skills/comet-native/reference/recovery.md +1 -1
- package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +1052 -458
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +2 -2
- package/assets/skills-zh/comet-archive/SKILL.md +29 -20
- package/assets/skills-zh/comet-native/SKILL.md +23 -15
- package/assets/skills-zh/comet-native/reference/artifacts.md +20 -5
- package/assets/skills-zh/comet-native/reference/commands.md +8 -2
- package/assets/skills-zh/comet-native/reference/recovery.md +1 -1
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +3 -6
- package/dist/app/commands/init.js.map +1 -1
- package/dist/domains/comet-entry/current-selection.d.ts.map +1 -1
- package/dist/domains/comet-entry/current-selection.js +6 -8
- package/dist/domains/comet-entry/current-selection.js.map +1 -1
- package/dist/domains/comet-entry/project-status.d.ts.map +1 -1
- package/dist/domains/comet-entry/project-status.js +5 -1
- package/dist/domains/comet-entry/project-status.js.map +1 -1
- package/dist/domains/comet-native/native-build-evidence.js +2 -2
- package/dist/domains/comet-native/native-build-evidence.js.map +1 -1
- package/dist/domains/comet-native/native-change.d.ts +4 -2
- package/dist/domains/comet-native/native-change.d.ts.map +1 -1
- package/dist/domains/comet-native/native-change.js +18 -4
- package/dist/domains/comet-native/native-change.js.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -1
- package/dist/domains/comet-native/native-check-receipt.js +5 -3
- package/dist/domains/comet-native/native-check-receipt.js.map +1 -1
- package/dist/domains/comet-native/native-cli.d.ts.map +1 -1
- package/dist/domains/comet-native/native-cli.js +120 -16
- package/dist/domains/comet-native/native-cli.js.map +1 -1
- package/dist/domains/comet-native/native-config.d.ts +5 -1
- package/dist/domains/comet-native/native-config.d.ts.map +1 -1
- package/dist/domains/comet-native/native-config.js +78 -0
- package/dist/domains/comet-native/native-config.js.map +1 -1
- package/dist/domains/comet-native/native-continuation.d.ts +2 -1
- package/dist/domains/comet-native/native-continuation.d.ts.map +1 -1
- package/dist/domains/comet-native/native-continuation.js +13 -5
- package/dist/domains/comet-native/native-continuation.js.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.d.ts +7 -3
- package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -1
- package/dist/domains/comet-native/native-diagnostics.js +19 -9
- package/dist/domains/comet-native/native-diagnostics.js.map +1 -1
- package/dist/domains/comet-native/native-file-identity.d.ts +2 -8
- package/dist/domains/comet-native/native-file-identity.d.ts.map +1 -1
- package/dist/domains/comet-native/native-file-identity.js +1 -20
- package/dist/domains/comet-native/native-file-identity.js.map +1 -1
- package/dist/domains/comet-native/native-findings.d.ts.map +1 -1
- package/dist/domains/comet-native/native-findings.js +19 -1
- package/dist/domains/comet-native/native-findings.js.map +1 -1
- package/dist/domains/comet-native/native-guards.d.ts +2 -1
- package/dist/domains/comet-native/native-guards.d.ts.map +1 -1
- package/dist/domains/comet-native/native-guards.js +14 -0
- package/dist/domains/comet-native/native-guards.js.map +1 -1
- package/dist/domains/comet-native/native-lock.d.ts.map +1 -1
- package/dist/domains/comet-native/native-lock.js +24 -32
- package/dist/domains/comet-native/native-lock.js.map +1 -1
- package/dist/domains/comet-native/native-root-move.d.ts.map +1 -1
- package/dist/domains/comet-native/native-root-move.js +2 -0
- package/dist/domains/comet-native/native-root-move.js.map +1 -1
- package/dist/domains/comet-native/native-snapshot.d.ts +5 -1
- package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -1
- package/dist/domains/comet-native/native-snapshot.js +218 -2
- package/dist/domains/comet-native/native-snapshot.js.map +1 -1
- package/dist/domains/comet-native/native-transitions.d.ts +2 -1
- package/dist/domains/comet-native/native-transitions.d.ts.map +1 -1
- package/dist/domains/comet-native/native-transitions.js +34 -11
- package/dist/domains/comet-native/native-transitions.js.map +1 -1
- package/dist/domains/comet-native/native-types.d.ts +16 -0
- package/dist/domains/comet-native/native-types.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-runtime.js +5 -3
- package/dist/domains/comet-native/native-verification-runtime.js.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts +2 -1
- package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -1
- package/dist/domains/comet-native/native-verification-scope.js +6 -1
- package/dist/domains/comet-native/native-verification-scope.js.map +1 -1
- package/dist/domains/dashboard/native-collector.d.ts.map +1 -1
- package/dist/domains/dashboard/native-collector.js +4 -1
- package/dist/domains/dashboard/native-collector.js.map +1 -1
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +9 -0
- package/dist/domains/skill/platform-install.js.map +1 -1
- package/dist/domains/skill/project-instructions.d.ts.map +1 -1
- package/dist/domains/skill/project-instructions.js +4 -2
- package/dist/domains/skill/project-instructions.js.map +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts.map +1 -1
- package/dist/domains/workflow-contract/project-config.js +24 -2
- package/dist/domains/workflow-contract/project-config.js.map +1 -1
- package/dist/platform/fs/file-identity.d.ts +9 -0
- package/dist/platform/fs/file-identity.d.ts.map +1 -0
- package/dist/platform/fs/file-identity.js +21 -0
- package/dist/platform/fs/file-identity.js.map +1 -0
- package/dist/platform/fs/file-system.d.ts.map +1 -1
- package/dist/platform/fs/file-system.js +13 -9
- package/dist/platform/fs/file-system.js.map +1 -1
- package/dist/platform/fs/race-safe-read.d.ts +47 -0
- package/dist/platform/fs/race-safe-read.d.ts.map +1 -0
- package/dist/platform/fs/race-safe-read.js +132 -0
- package/dist/platform/fs/race-safe-read.js.map +1 -0
- package/eval/README.md +1 -1
- package/eval/local/tasks/comet-native-clarification-depth/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification-depth/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification-depth/instruction.md +17 -0
- package/eval/local/tasks/comet-native-clarification-depth/task.toml +47 -0
- package/eval/local/tasks/comet-native-clarification-depth/validation/test_native_clarification_depth.py +618 -0
- package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +1 -1
- package/eval/local/tasks/index.yaml +6 -0
- package/eval/local/tests/conftest.py +146 -2
- package/eval/local/tests/scaffold/test_conftest_helpers.py +188 -1
- package/eval/local/tests/scaffold/test_tasks.py +440 -8
- package/eval/local/tests/scaffold/test_utils.py +114 -1
- package/eval/local/tests/tasks/test_tasks.py +2 -0
- package/eval/scaffold/python/aligned_comparison.py +1 -0
- package/eval/scaffold/python/tasks.py +2 -0
- package/eval/scaffold/shell/docker.sh +1 -1
- package/eval/scaffold/shell/run-claude-loop.sh +25 -2
- package/package.json +1 -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
|
|
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:
|