fix(skills): two more skills that contradicted the platform
Same class as the `/workspace/repo` path and the ZeroClaw tool names: the skills were written alongside the platform and never compared to it again. Both found by reading the source of truth before writing a check against it. 1. `decompose-int-items` showed `PLAN_COMPLETE: INT-01..05`. An id is strictly `INT-<digits>`, so the range form is rejected outright — the plan pass records nothing while every item stays open. A live planner emitted exactly that line. Now one id per line. 2. `workspace-repo-commit-protocol` said the task-card parser advances mission state on the INT id in the commit subject. Nothing in the platform reads commit messages; the parser reads `run_events` — the agent's turn output. An agent that believed it could commit with the id and never emit `COMPLETED: INT-NN`, leaving the mission open on an item it had finished. The convention is kept, the mechanism corrected. `no_skill_shows_a_marker_the_parser_would_reject` runs the real parser over every marker in every skill's fenced blocks, negative-controlled against the range form. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_018i9Ten1LU4jUr5d7TAWda9
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a6fdfc0e6
commit
d0b657a24b
@@ -273,4 +273,73 @@ mod contradiction_tests {
|
||||
offenders.join("\n ")
|
||||
);
|
||||
}
|
||||
|
||||
/// No skill may show a marker the real parser rejects.
|
||||
///
|
||||
/// Checked by running `task_card_parser::parse` itself, never a copy of its
|
||||
/// rules — a second implementation of the contract drifts, and then the
|
||||
/// test passes while the mission loop stalls.
|
||||
///
|
||||
/// This is the third instance of one class: the skills were written
|
||||
/// alongside the platform and then never compared to it again. The first
|
||||
/// was a repo path the platform does not mount; the second a tool the agent
|
||||
/// does not have; this one is `PLAN_COMPLETE: INT-01..05` in
|
||||
/// `decompose-int-items`, which a live planner emitted verbatim. Ids are
|
||||
/// strictly `INT-<digits>`, so the range form parses to nothing — the plan
|
||||
/// pass records no completion at all while every item stays open.
|
||||
///
|
||||
/// Scoped to fenced code blocks, which is where a skill puts the text it
|
||||
/// tells an agent to EMIT. A marker named in a sentence is prose.
|
||||
#[test]
|
||||
fn no_skill_shows_a_marker_the_parser_would_reject() {
|
||||
// The templates. `INT-NN` is a placeholder an agent substitutes, not a
|
||||
// literal it emits, so it is not a contradiction.
|
||||
const PLACEHOLDERS: &[&str] = &["INT-NN", "INT-XX", "INT-N", "INT-nn"];
|
||||
let mut offenders = Vec::new();
|
||||
for (name, body) in all_skills() {
|
||||
let mut fenced = false;
|
||||
for line in body.lines() {
|
||||
if line.trim_start().starts_with("```") {
|
||||
fenced = !fenced;
|
||||
continue;
|
||||
}
|
||||
let t = line.trim();
|
||||
if !fenced || !t.contains("INT-") || !t.contains(':') {
|
||||
continue;
|
||||
}
|
||||
let Some((kind, _)) = t.split_once(':') else {
|
||||
continue;
|
||||
};
|
||||
if !MARKER_KINDS.contains(&kind.trim()) {
|
||||
continue;
|
||||
}
|
||||
if PLACEHOLDERS.iter().any(|p| t.contains(p)) {
|
||||
continue;
|
||||
}
|
||||
if crate::task_card_parser::parse(t).is_empty() {
|
||||
offenders.push(format!("{name}: {t}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
offenders.is_empty(),
|
||||
"{} skill line(s) show a marker the parser rejects — an agent that \
|
||||
follows them exactly is silently ignored:\n {}",
|
||||
offenders.len(),
|
||||
offenders.join("\n ")
|
||||
);
|
||||
}
|
||||
|
||||
/// The marker kinds, as the parser spells them.
|
||||
const MARKER_KINDS: &[&str] = &[
|
||||
"TASK",
|
||||
"PLAN_COMPLETE",
|
||||
"WORK",
|
||||
"HANDOFF",
|
||||
"TEST_PASS",
|
||||
"TEST_FAIL",
|
||||
"REVIEW_APPROVE",
|
||||
"REVIEW_BLOCK",
|
||||
"COMPLETED",
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user