@ccoalm/ccl-skills 0.15.0 → 0.15.1
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/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +80 -4
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +16 -4
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +13 -2
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +7 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +4 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +14 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +40 -83
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +44 -37
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +8 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +3 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +31 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +74 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +103 -21
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +74 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py +428 -0
- package/dist/assets/release.json +45 -30
- package/dist/claude-adapter.js +14 -7
- package/dist/codex-host.d.ts +1 -3
- package/dist/codex-host.js +6 -9
- package/dist/host-probe.d.ts +11 -0
- package/dist/host-probe.js +27 -0
- package/dist/opencode-adapter.js +24 -19
- package/dist/unified.js +11 -9
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# upstream-owner skill must declare a behavioral-evidence status and an
|
|
6
6
|
# observed-failure state, and (for non-wording changes) name an owner-scoped
|
|
7
7
|
# FIRING PATH that resolves to this diff — an anchor on a changed normative
|
|
8
|
-
# rule line, or a changed owner executable. The statuses are required author
|
|
8
|
+
# rule or table data line, or a changed owner executable. The statuses are required author
|
|
9
9
|
# declarations; the firing path and the wording-only classification are the
|
|
10
10
|
# machine-verified core. Extracted from the former inline `ruby -e` block in
|
|
11
11
|
# check-ccl-skills.sh so the program gets normal Ruby tooling and no
|
|
@@ -1141,9 +1141,80 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
|
|
|
1141
1141
|
blob[:content].scan(Regexp.new(Regexp.escape(anchor))).length == 1
|
|
1142
1142
|
end
|
|
1143
1143
|
end
|
|
1144
|
+
# Definition/decision tables are firing surfaces too. Recognize explicit
|
|
1145
|
+
# Markdown tables with a header and delimiter; comments and code examples do
|
|
1146
|
+
# not count. This proves location/shape, not the truth of the definition.
|
|
1147
|
+
table_data_anchor_valid = lambda do |scope, parts|
|
|
1148
|
+
prior = blob_at.call(scope.base, parts[:path])
|
|
1149
|
+
next false if prior && prior[:content].include?(parts[:anchor])
|
|
1150
|
+
previous_cells = nil
|
|
1151
|
+
columns = nil
|
|
1152
|
+
fence = nil
|
|
1153
|
+
comment = false
|
|
1154
|
+
raw_html = nil
|
|
1155
|
+
html_block = false
|
|
1156
|
+
head_blob.call(scope, parts[:path])[:content].each_line do |raw_line|
|
|
1157
|
+
line = raw_line.chomp
|
|
1158
|
+
if fence
|
|
1159
|
+
fence = nil if line.match?(/\A {0,3}#{Regexp.escape(fence[0])}{#{fence.length},}\s*\z/)
|
|
1160
|
+
next
|
|
1161
|
+
end
|
|
1162
|
+
if raw_html
|
|
1163
|
+
raw_html = nil if line.match?(raw_html)
|
|
1164
|
+
next
|
|
1165
|
+
end
|
|
1166
|
+
if html_block
|
|
1167
|
+
html_block = false if line.strip.empty?
|
|
1168
|
+
next
|
|
1169
|
+
end
|
|
1170
|
+
hidden = comment || line.include?("<!--") || line.include?("-->")
|
|
1171
|
+
line.scan(/<!--|-->/).each { |marker| comment = marker == "<!--" }
|
|
1172
|
+
if hidden
|
|
1173
|
+
previous_cells = columns = nil
|
|
1174
|
+
next
|
|
1175
|
+
end
|
|
1176
|
+
if (opening = line.match(/\A {0,3}(`{3,}|~{3,})/))
|
|
1177
|
+
fence = opening[1]
|
|
1178
|
+
previous_cells = columns = nil
|
|
1179
|
+
next
|
|
1180
|
+
end
|
|
1181
|
+
terminator = case line
|
|
1182
|
+
when /\A {0,3}<\?/ then /\?>/
|
|
1183
|
+
when /\A {0,3}<!\[CDATA\[/ then /\]\]>/
|
|
1184
|
+
when /\A {0,3}<![A-Z]/ then />/
|
|
1185
|
+
end
|
|
1186
|
+
if terminator
|
|
1187
|
+
raw_html = terminator unless line.match?(terminator)
|
|
1188
|
+
previous_cells = columns = nil
|
|
1189
|
+
next
|
|
1190
|
+
end
|
|
1191
|
+
if line.match?(%r{\A {0,3}</?[A-Za-z][\w-]*(?:\s|>|/|\z)})
|
|
1192
|
+
tag = line[/\A {0,3}<(script|pre|style|textarea)(?:\s|>|\z)/i, 1]
|
|
1193
|
+
raw_html = %r{</#{tag}\s*>}i if tag && !line.match?(%r{</#{tag}\s*>}i)
|
|
1194
|
+
html_block = !tag
|
|
1195
|
+
previous_cells = columns = nil
|
|
1196
|
+
next
|
|
1197
|
+
end
|
|
1198
|
+
unless line.match?(/\A {0,3}\|.*\|\s*\z/)
|
|
1199
|
+
previous_cells = columns = nil
|
|
1200
|
+
next
|
|
1201
|
+
end
|
|
1202
|
+
cells = line.strip[1...-1].split(/(?<!\\)\|/, -1).map(&:strip)
|
|
1203
|
+
delimiter = cells.length >= 2 && cells.all? { |cell| cell.match?(/\A:?-{3,}:?\z/) }
|
|
1204
|
+
if delimiter
|
|
1205
|
+
columns = previous_cells && previous_cells.length == cells.length ? cells.length : nil
|
|
1206
|
+
elsif columns && columns == cells.length
|
|
1207
|
+
break true if cells.any? { |cell| cell.include?(parts[:anchor]) }
|
|
1208
|
+
else
|
|
1209
|
+
columns = nil
|
|
1210
|
+
end
|
|
1211
|
+
previous_cells = cells
|
|
1212
|
+
end == true
|
|
1213
|
+
end
|
|
1144
1214
|
enforcing_file_locator_valid = lambda do |scope, parts|
|
|
1145
1215
|
next false unless parts && parts[:kind] == "file"
|
|
1146
1216
|
next false unless parts[:path].end_with?(".md")
|
|
1217
|
+
next false if parts[:path] == LEDGER_PATH # Evidence cannot certify itself.
|
|
1147
1218
|
next false unless locator_valid.call(scope, "file:#{parts[:path]}##{parts[:anchor]}")
|
|
1148
1219
|
line = added_lines_for.call(scope, parts[:path]).find { |added| added.include?(parts[:anchor]) }
|
|
1149
1220
|
next false unless line
|
|
@@ -1161,7 +1232,7 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
|
|
|
1161
1232
|
# "the adapter does not support"), and single characters with broad
|
|
1162
1233
|
# compounds (应/只/别 — 应用/只是/区别).
|
|
1163
1234
|
normative = line.match?(/(?:\b(?:must|shall|never|do\s+not|don'?t|required?|requires?|block(?:s|ed)?|reject(?:s|ed)?|deny|denied|invalidates?|forbid(?:s|den)?|cannot|enforcement)\b|必须|不得|禁止|拒绝|作废|仅限|只能|应当|应该|务必|不能|不允许|不可)/i)
|
|
1164
|
-
list_rule && normative
|
|
1235
|
+
(list_rule && normative) || table_data_anchor_valid.call(scope, parts)
|
|
1165
1236
|
end
|
|
1166
1237
|
# A routing-surface-only owner has no changed rule line to anchor on: its whole
|
|
1167
1238
|
# change is one YAML scalar. The answer is NOT to exempt it — a description edit
|
|
@@ -1360,7 +1431,7 @@ if upstream.any? || routing_entrypoint_changed || changed_paths.include?(LEDGER_
|
|
|
1360
1431
|
firing_parts = locator_parts.call(firing_path)
|
|
1361
1432
|
firing_path_valid = firing_locator_valid.call(row_scope, firing_parts, owner)
|
|
1362
1433
|
# The machine-checked core is the FIRING PATH (an owner-scoped anchor on a
|
|
1363
|
-
# changed normative rule, or a changed owner executable) plus the
|
|
1434
|
+
# changed normative rule/table data, or a changed owner executable) plus the
|
|
1364
1435
|
# deterministic wording-only classification. The behavioral-evidence
|
|
1365
1436
|
# status and observed-failure fields are required author declarations —
|
|
1366
1437
|
# honest labels, not digest-verified artifacts: a digest-bound evidence
|
|
@@ -52,7 +52,7 @@ Usage:
|
|
|
52
52
|
Run in a SCRATCH checkout: the current arm executes the installed hooks/plugins (not just the
|
|
53
53
|
read-only model tools), so treat it as potentially side-effecting, not inert.
|
|
54
54
|
"""
|
|
55
|
-
import argparse, hashlib, json, os, re, subprocess, sys,
|
|
55
|
+
import argparse, hashlib, json, os, re, signal, subprocess, sys, time
|
|
56
56
|
|
|
57
57
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
58
58
|
DEFAULT_FIXTURES = os.path.normpath(os.path.join(HERE, "..", "..", "..", "eval", "behavior-fixtures.jsonl"))
|
|
@@ -100,23 +100,62 @@ def _headless_claude(cmd, prompt, timeout_s):
|
|
|
100
100
|
# stderr → DEVNULL: we never read it, and a full stderr pipe would deadlock the child
|
|
101
101
|
# on a verbose run and time out an otherwise-valid answer.
|
|
102
102
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
103
|
-
stderr=subprocess.DEVNULL, text=True
|
|
103
|
+
stderr=subprocess.DEVNULL, text=True,
|
|
104
|
+
start_new_session=(os.name == "posix"))
|
|
104
105
|
except FileNotFoundError:
|
|
105
106
|
return None, "claude_not_found", None, []
|
|
106
|
-
out = {"s": ""}
|
|
107
|
-
t = threading.Thread(target=lambda: out.__setitem__("s", p.stdout.read()))
|
|
108
|
-
t.start()
|
|
109
107
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
# One deadline covers stdin backpressure, stdout collection and process
|
|
109
|
+
# completion. A reader-only timer leaves writes and p.wait() unbounded.
|
|
110
|
+
out, _ = p.communicate(input=prompt, timeout=timeout_s)
|
|
111
|
+
except BaseException as stopped:
|
|
112
|
+
# The group can outlive its leader while a descendant holds stdout open.
|
|
113
|
+
# Kill the recorded group even when the direct child has already exited.
|
|
114
|
+
cleanup_errors = []
|
|
115
|
+
try:
|
|
116
|
+
if os.name == "posix":
|
|
117
|
+
os.killpg(p.pid, signal.SIGKILL)
|
|
118
|
+
else:
|
|
119
|
+
cleanup_errors.append("descendant_cleanup_unsupported")
|
|
120
|
+
p.kill()
|
|
121
|
+
except ProcessLookupError:
|
|
122
|
+
pass
|
|
123
|
+
except PermissionError:
|
|
124
|
+
target = "group" if os.name == "posix" else "process"
|
|
125
|
+
cleanup_errors.append(f"{target}_kill_permission_denied")
|
|
126
|
+
try:
|
|
127
|
+
p.communicate(timeout=1)
|
|
128
|
+
except (subprocess.TimeoutExpired, OSError) as cleanup_error:
|
|
129
|
+
# An escaped descendant may still own a pipe; do not wait for EOF.
|
|
130
|
+
cleanup_errors.append("stdio_timeout" if isinstance(cleanup_error, subprocess.TimeoutExpired) else "stdio_error")
|
|
131
|
+
try:
|
|
132
|
+
p.kill()
|
|
133
|
+
except ProcessLookupError:
|
|
134
|
+
pass
|
|
135
|
+
except PermissionError:
|
|
136
|
+
cleanup_errors.append("process_kill_permission_denied")
|
|
137
|
+
try:
|
|
138
|
+
p.wait(timeout=1)
|
|
139
|
+
except (subprocess.TimeoutExpired, OSError) as cleanup_error:
|
|
140
|
+
cleanup_errors.append("wait_timeout" if isinstance(cleanup_error, subprocess.TimeoutExpired) else "wait_error")
|
|
141
|
+
# A denied kill or incomplete drain/reap is not evidence of cleanup.
|
|
142
|
+
# Return it so callers can save partial results and stop new processes.
|
|
143
|
+
interrupted = not isinstance(stopped, subprocess.TimeoutExpired)
|
|
144
|
+
error = "interrupted" if interrupted else f"timeout_{timeout_s}s"
|
|
145
|
+
if cleanup_errors:
|
|
146
|
+
error += ";cleanup_unconfirmed:" + ",".join(cleanup_errors)
|
|
147
|
+
if interrupted:
|
|
148
|
+
if cleanup_errors:
|
|
149
|
+
print(f"{error}; confirm process termination before resuming", file=sys.stderr)
|
|
150
|
+
raise
|
|
151
|
+
return None, error, None, []
|
|
152
|
+
finally:
|
|
153
|
+
p.stdin.close()
|
|
154
|
+
p.stdout.close()
|
|
155
|
+
rc = p.returncode
|
|
118
156
|
parts, result_text, result_subtype, util, invoked = [], None, None, None, []
|
|
119
|
-
|
|
157
|
+
terminal_results = []
|
|
158
|
+
for ln in out.splitlines():
|
|
120
159
|
# The stream is external/untrusted: a line may be invalid JSON, deeply nested (RecursionError),
|
|
121
160
|
# or a shape-drifted value. Wrap the WHOLE per-line parse+extract so any bad line skips itself
|
|
122
161
|
# and never crashes the eval run (fail-closed-skip). We read only known fields of known event
|
|
@@ -127,6 +166,7 @@ def _headless_claude(cmd, prompt, timeout_s):
|
|
|
127
166
|
if not isinstance(ev, dict):
|
|
128
167
|
continue
|
|
129
168
|
if ev.get("type") == "result":
|
|
169
|
+
terminal_results.append(ev)
|
|
130
170
|
result_subtype = ev.get("subtype")
|
|
131
171
|
if result_subtype == "success":
|
|
132
172
|
result_text = ev.get("result")
|
|
@@ -159,8 +199,19 @@ def _headless_claude(cmd, prompt, timeout_s):
|
|
|
159
199
|
# teardown failure — the answer is complete, accept. Without that event we do NOT bank the
|
|
160
200
|
# text, even if some assistant chunks streamed and rc==0: a truncation or format drift before
|
|
161
201
|
# the terminal event would otherwise be recorded as a valid sample.
|
|
162
|
-
if
|
|
163
|
-
return
|
|
202
|
+
if len(terminal_results) > 1:
|
|
203
|
+
return None, "invalid_terminal_result", util, invoked
|
|
204
|
+
if result_subtype == "success" and terminal_results:
|
|
205
|
+
terminal = terminal_results[0]
|
|
206
|
+
if (terminal.get("is_error") not in (None, False)
|
|
207
|
+
or terminal.get("permission_denials")
|
|
208
|
+
or terminal.get("api_error_status") not in (None, 0, "0")
|
|
209
|
+
or terminal.get("terminal_reason") not in (None, "completed")):
|
|
210
|
+
return None, "invalid_terminal_result", util, invoked
|
|
211
|
+
text = "\n\n".join(parts) if parts else result_text
|
|
212
|
+
if not isinstance(text, str) or not text.strip():
|
|
213
|
+
return None, "invalid_terminal_result", util, invoked
|
|
214
|
+
return text, None, util, invoked
|
|
164
215
|
if rc != 0:
|
|
165
216
|
return None, f"claude_exit_{rc}", util, invoked
|
|
166
217
|
if result_subtype:
|
|
@@ -312,8 +363,12 @@ def build_report(rows, out_dir, do_judge, timeout_s, last_util, stop_util,
|
|
|
312
363
|
in the summary (never silently dropped, or the report reads clean when it isn't). Raw responses
|
|
313
364
|
stay on disk for audit; judgment-ASSIST, never a score. do_judge=False → fill-in scaffold."""
|
|
314
365
|
verdicts = []
|
|
366
|
+
cleanup_error = None
|
|
315
367
|
for fx in rows:
|
|
316
368
|
base = {"id": fx["id"], "axis": fx.get("axis", "")}
|
|
369
|
+
if cleanup_error:
|
|
370
|
+
verdicts.append({**base, "status": "judge-skipped", "note": cleanup_error})
|
|
371
|
+
continue
|
|
317
372
|
cur = read_saved_response(os.path.join(out_dir, f"{fx['id']}.current.s1.txt"))
|
|
318
373
|
cand = read_saved_response(os.path.join(out_dir, f"{fx['id']}.candidate.s1.txt"))
|
|
319
374
|
if not cur or not cand:
|
|
@@ -342,6 +397,8 @@ def build_report(rows, out_dir, do_judge, timeout_s, last_util, stop_util,
|
|
|
342
397
|
last_util = util
|
|
343
398
|
if err:
|
|
344
399
|
verdicts.append({**base, "status": f"judge-error:{err}"})
|
|
400
|
+
if ";cleanup_unconfirmed:" in err:
|
|
401
|
+
cleanup_error = err
|
|
345
402
|
continue
|
|
346
403
|
v.update(base); v["status"] = "judged"
|
|
347
404
|
if fixture_needs_human(fx):
|
|
@@ -467,8 +524,10 @@ def main():
|
|
|
467
524
|
if not a.no_judge:
|
|
468
525
|
print(f"--report-only will make up to {len(rows)} LLM-judge claude call(s) "
|
|
469
526
|
f"(one per fixture with both arms saved). Use --no-judge for a fill-in scaffold.")
|
|
470
|
-
build_report(rows, a.out, not a.no_judge, a.timeout, None, a.stop_util, ctext, a.current_tag)
|
|
527
|
+
verdicts = build_report(rows, a.out, not a.no_judge, a.timeout, None, a.stop_util, ctext, a.current_tag)
|
|
471
528
|
print(f"\nReport: {os.path.join(a.out, 'capability-delta-report.md')}")
|
|
529
|
+
if any(";cleanup_unconfirmed:" in v["status"] for v in verdicts):
|
|
530
|
+
sys.exit(1)
|
|
472
531
|
return
|
|
473
532
|
# Fail fast: don't burn every current-arm run and only then discover the candidate
|
|
474
533
|
# arm has no contract to inject.
|
|
@@ -501,6 +560,12 @@ def main():
|
|
|
501
560
|
f"{a.stop_util:.0%} — stopping before [{fx['id']}/{arm}/s{s}]. "
|
|
502
561
|
f"Partial results in {a.out}; rerun to continue, then --report-only.")
|
|
503
562
|
logf.close(); return
|
|
563
|
+
# A failed replacement must not leave its old response usable.
|
|
564
|
+
# Invalidate only after quota allows this sample to start.
|
|
565
|
+
try:
|
|
566
|
+
os.unlink(rpath)
|
|
567
|
+
except FileNotFoundError:
|
|
568
|
+
pass
|
|
504
569
|
t0 = time.time()
|
|
505
570
|
text, err, util, invoked = run_agent(fx["prompt"], a.timeout, arm, a.contract)
|
|
506
571
|
dt = time.time() - t0
|
|
@@ -509,7 +574,13 @@ def main():
|
|
|
509
574
|
if err:
|
|
510
575
|
print(f"[{fx['id']}/{arm}/s{s}] ERROR {err} ({dt:.0f}s)")
|
|
511
576
|
logf.write(json.dumps({"id": fx["id"], "arm": arm, "sample": s, "error": err}) + "\n")
|
|
512
|
-
logf.flush()
|
|
577
|
+
logf.flush()
|
|
578
|
+
if ";cleanup_unconfirmed:" in err:
|
|
579
|
+
print(f"*** ABORT: process cleanup is unconfirmed; no further samples or judges started. "
|
|
580
|
+
f"Partial results in {a.out}. Confirm process termination before resuming.")
|
|
581
|
+
logf.close()
|
|
582
|
+
sys.exit(1)
|
|
583
|
+
continue
|
|
513
584
|
with open(rpath, "w", encoding="utf-8") as rf:
|
|
514
585
|
rf.write(f"# sig: {sig}\n")
|
|
515
586
|
rf.write(f"# {fx['id']} / arm={arm} / sample={s} / {dt:.0f}s / util={last_util}\n")
|
|
@@ -528,13 +599,24 @@ def main():
|
|
|
528
599
|
if "current" in arms and "candidate" in arms:
|
|
529
600
|
print("Building capability-delta report (candidate vs current)"
|
|
530
601
|
+ ("" if a.no_judge else " via LLM-judge — this makes more claude calls") + " ...")
|
|
531
|
-
build_report(rows, a.out, not a.no_judge, a.timeout, last_util, a.stop_util,
|
|
532
|
-
|
|
602
|
+
verdicts = build_report(rows, a.out, not a.no_judge, a.timeout, last_util, a.stop_util,
|
|
603
|
+
contract_text, a.current_tag)
|
|
533
604
|
print(f"Report: {os.path.join(a.out, 'capability-delta-report.md')} "
|
|
534
605
|
f"(confirm every 🔴 HUMAN row by eye; it is judgment-assist, not a score).")
|
|
606
|
+
if any(";cleanup_unconfirmed:" in v["status"] for v in verdicts):
|
|
607
|
+
sys.exit(1)
|
|
535
608
|
else:
|
|
536
609
|
print("Single arm — no delta to report. Run --both-arms for the capability-delta report.")
|
|
537
610
|
|
|
538
611
|
|
|
539
612
|
if __name__ == "__main__":
|
|
540
|
-
|
|
613
|
+
previous_sigterm = signal.getsignal(signal.SIGTERM)
|
|
614
|
+
if previous_sigterm == signal.SIG_DFL:
|
|
615
|
+
def terminate(signum, _frame):
|
|
616
|
+
raise SystemExit(128 + signum)
|
|
617
|
+
signal.signal(signal.SIGTERM, terminate)
|
|
618
|
+
try:
|
|
619
|
+
main()
|
|
620
|
+
finally:
|
|
621
|
+
if previous_sigterm == signal.SIG_DFL:
|
|
622
|
+
signal.signal(signal.SIGTERM, previous_sigterm)
|
|
@@ -48,6 +48,10 @@ printf '# fixture slug-named reference\n\nNeutral placeholder for the inner-rena
|
|
|
48
48
|
# and isolate the prose-only guard from the reproduction check.
|
|
49
49
|
printf '# fixture slug mention: platform-observability\n' >> "$REPO/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh"
|
|
50
50
|
printf '\nFixture eligible sibling mention: platform-observability.\n' >> "$REPO/skills/product-rd-workflow/references/adr-convention.md"
|
|
51
|
+
# A real table baseline distinguishes a changed definition from an old anchor
|
|
52
|
+
# surviving an unrelated source-link edit on the same line.
|
|
53
|
+
TABLE_REL="skills/product-rd-workflow/references/zz-fixture-definition.md"
|
|
54
|
+
printf '# Definitions\n\n| Metric | Definition | Source |\n| --- | --- | --- |\n| Incident deployment ratio | Deployments needing later work | old-source |\n' > "$REPO/$TABLE_REL"
|
|
51
55
|
git -C "$REPO" add -A
|
|
52
56
|
git -C "$REPO" commit -qm "seed throwaway upstream reference"
|
|
53
57
|
git -C "$REPO" branch fixture-base HEAD
|
|
@@ -154,6 +158,75 @@ run_gate
|
|
|
154
158
|
assert_not_contains "impact_chain_firing_path_missing" "$out" "a never-phrased normative rule should satisfy the firing-path gate"
|
|
155
159
|
assert_rc "$rc" 0 "a never-phrased normative list rule must be accepted"
|
|
156
160
|
|
|
161
|
+
# Definitions are executable guidance even when their Markdown surface is a
|
|
162
|
+
# table instead of a normative list. Negative controls keep anchors bound to
|
|
163
|
+
# new, rendered data cells in the correct owner and round.
|
|
164
|
+
definition_table_case() {
|
|
165
|
+
local label="$1" expected="$2" anchor='Unplanned deployments caused by production incidents'
|
|
166
|
+
local firing_path="$TABLE_REL"
|
|
167
|
+
new_case "case-ref-definition-table-$label"
|
|
168
|
+
case "$label" in
|
|
169
|
+
surviving-anchor)
|
|
170
|
+
anchor='Deployments needing later work'
|
|
171
|
+
perl -pi -e 's/old-source/new-source/' "$REPO/$TABLE_REL" ;;
|
|
172
|
+
unchanged-table)
|
|
173
|
+
anchor='Deployments needing later work'
|
|
174
|
+
printf '\nChanged neighboring prose.\n' >> "$REPO/$TABLE_REL" ;;
|
|
175
|
+
*)
|
|
176
|
+
perl -pi -e 's/Deployments needing later work/Unplanned deployments caused by production incidents/' "$REPO/$TABLE_REL" ;;
|
|
177
|
+
esac
|
|
178
|
+
case "$label" in
|
|
179
|
+
foreign-owner)
|
|
180
|
+
firing_path='skills/terminal-cli-dev/references/zz-fixture-definition.md'
|
|
181
|
+
cp "$REPO/$TABLE_REL" "$REPO/$firing_path" ;;
|
|
182
|
+
duplicate) printf '\n%s\n' "$anchor" >> "$REPO/$TABLE_REL" ;;
|
|
183
|
+
inline-comment) perl -pi -e 's/Unplanned deployments caused by production incidents/<!-- Unplanned deployments caused by production incidents -->/' "$REPO/$TABLE_REL" ;;
|
|
184
|
+
multiline-comment) perl -0777 -pi -e 's/\n\| Metric/\n<!--\n| Metric/; $_ .= "-->\n"' "$REPO/$TABLE_REL" ;;
|
|
185
|
+
backtick-fence) perl -0777 -pi -e 's/\n\| Metric/\n```markdown\n| Metric/; $_ .= "```\n"' "$REPO/$TABLE_REL" ;;
|
|
186
|
+
tilde-fence) perl -0777 -pi -e 's/\n\| Metric/\n~~~markdown\n| Metric/; $_ .= "~~~\n"' "$REPO/$TABLE_REL" ;;
|
|
187
|
+
raw-html) perl -0777 -pi -e 's/\n\| Metric/\n<script>\n\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
|
|
188
|
+
raw-html-open-line) perl -0777 -pi -e 's/\n\| Metric/\n<script\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
|
|
189
|
+
processing-instruction) perl -0777 -pi -e 's/\n\| Metric/\n<?xml\n| Metric/; $_ .= "?>\n"' "$REPO/$TABLE_REL" ;;
|
|
190
|
+
cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\n| Metric/; $_ .= "]]>\n"' "$REPO/$TABLE_REL" ;;
|
|
191
|
+
declaration) perl -0777 -pi -e 's/\n\| Metric/\n<!DOCTYPE\n| Metric/; $_ .= ">\n"' "$REPO/$TABLE_REL" ;;
|
|
192
|
+
closed-cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\nclosed example\n]]>\n\n| Metric/' "$REPO/$TABLE_REL" ;;
|
|
193
|
+
html-block) perl -0777 -pi -e 's/\n\| Metric/\n<div>\n| Metric/; $_ .= "<\/div>\n"' "$REPO/$TABLE_REL" ;;
|
|
194
|
+
indented-code) perl -pi -e 's/^\|/ |/' "$REPO/$TABLE_REL" ;;
|
|
195
|
+
no-header) perl -ni -e 'print unless /^\| (Metric|---)/' "$REPO/$TABLE_REL" ;;
|
|
196
|
+
header-anchor) perl -0777 -pi -e 's/\| Metric \| Definition \| Source \|/| Unplanned deployments caused by production incidents | Definition | Source |/; s/\n\| Incident deployment ratio[^\n]*\n/\n/' "$REPO/$TABLE_REL" ;;
|
|
197
|
+
no-red) : ;;
|
|
198
|
+
esac
|
|
199
|
+
local status='RED-baseline' observed='yes'
|
|
200
|
+
if [ "$label" = no-red ]; then status='semantic-control'; observed='no'; fi
|
|
201
|
+
printf '| Fixture definition table %s | `downstream-executor` | behavioral-evidence: %s; observed-failure: %s; result-class: failure; firing-path: file:%s#%s | `updated` | `product-rd-workflow/SKILL.md` definition correction |\n' "$label" "$status" "$observed" "$firing_path" "$anchor" >> "$REGISTER"
|
|
202
|
+
commit_case "definition table $label"
|
|
203
|
+
run_gate
|
|
204
|
+
assert_rc "$rc" "$expected" "definition table $label"
|
|
205
|
+
if [ "$expected" = 1 ]; then
|
|
206
|
+
if [ "$label" = no-red ]; then
|
|
207
|
+
assert_contains 'impact_chain_behavior_evidence_missing' "$out" "definition table $label: $out"
|
|
208
|
+
else
|
|
209
|
+
assert_contains 'impact_chain_firing_path_missing' "$out" "definition table $label: $out"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
}
|
|
213
|
+
definition_table_case accepted 0
|
|
214
|
+
definition_table_case closed-cdata 0
|
|
215
|
+
for definition_control in surviving-anchor unchanged-table foreign-owner duplicate inline-comment multiline-comment backtick-fence tilde-fence raw-html raw-html-open-line processing-instruction cdata declaration html-block indented-code no-header header-anchor no-red; do
|
|
216
|
+
definition_table_case "$definition_control" 1
|
|
217
|
+
done
|
|
218
|
+
|
|
219
|
+
# Evidence is not its own firing surface. Keep the anchor only in the locator
|
|
220
|
+
# itself so uniqueness cannot accidentally reject the self-certifying row.
|
|
221
|
+
new_case case-ref-definition-table-self-register
|
|
222
|
+
printf '\nFixture bookkeeping note, with no new enforcing rule.\n' >> "$REPO/skills/skill-extraction-workflow/references/validation-and-landing.md"
|
|
223
|
+
printf '\n| Rule | Owner | Behavior | Status | Evidence |\n| --- | --- | --- | --- | --- |\n' >> "$REGISTER"
|
|
224
|
+
printf '| Fixture self-citing table | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; firing-path: file:skills/skill-extraction-workflow/references/source-register.md#SELF-REGISTER-DEFINITION-ANCHOR | updated | `skill-extraction-workflow/SKILL.md` bookkeeping edit |\n' >> "$REGISTER"
|
|
225
|
+
commit_case "source register cannot certify its own firing path"
|
|
226
|
+
run_gate
|
|
227
|
+
assert_rc "$rc" 1 "source register must not be its own firing surface"
|
|
228
|
+
assert_contains 'impact_chain_firing_path_missing' "$out" "self-register refusal must name the firing-path boundary"
|
|
229
|
+
|
|
157
230
|
# A purely DESCRIPTIVE list line ("always exposes" — no imperative/prohibitive
|
|
158
231
|
# verb) is not an enforcing rule; the widened verb list must not admit it.
|
|
159
232
|
new_case case-ref-descriptive-always-firing-path
|
|
@@ -1583,6 +1656,6 @@ assert_rc "$rc" 1 "a directory masquerading as SKILL.md must not read as a prese
|
|
|
1583
1656
|
assert_contains "platform-observability/SKILL.md" "$out" "the masqueraded owner must be named"
|
|
1584
1657
|
|
|
1585
1658
|
assert_rc "$full_check_runs" 1 "fixture suite must retain exactly one full checker wiring case"
|
|
1586
|
-
assert_rc "$gate_runs"
|
|
1659
|
+
assert_rc "$gate_runs" 118 "all remaining impact-chain fixtures must run the standalone gate"
|
|
1587
1660
|
|
|
1588
1661
|
echo "test_check_ccl_impact_chain_refscripts: ok"
|