diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 305ddd198e..8951d5b198 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -717,22 +717,5 @@ "skill" ] } - ], - "renames": { - "book-distill": "knowledge", - "diagnose": "debugging", - "improve-architecture": "architecture", - "teach": "education", - "review-toolkit": "review", - "mcp-tool-audit": "mcp-tools", - "codebase-audit": "codebase-health", - "claude-config-audit": "claude-config", - "boris": "playbooks", - "thariq-skills": "playbooks", - "fable-5-playbook": "playbooks", - "re-anchor": "discipline", - "claude-config": "harness-config", - "claude-memory": "harness-memory", - "claude-ops": "harness-ops" - } + ] } diff --git a/docs/conventions/loop-lane/README.md b/docs/conventions/loop-lane/README.md index b86b3bc9a6..f240fa11db 100644 --- a/docs/conventions/loop-lane/README.md +++ b/docs/conventions/loop-lane/README.md @@ -889,7 +889,7 @@ directly, find the `Lane telemetry: ` issue and print its sentinel comment ```bash gh issue list -R "$R" --state open --search '"Lane telemetry: work-loop" in:title' --json number,title gh api "repos/$R/issues//comments" --paginate \ - --jq '.[] | select(.body | startswith("` and the restart consumer's + marker is `harness-ops:restart-consumer`; a comment posted under the old marker is no longer + found, and a new one is written. The environment variables are `HARNESS_OPS_LANES_CONFIG`, + `HARNESS_OPS_CHANGELOG_LEDGER`, `HARNESS_OPS_CHANGELOG_CAP_RELEASES` and + `HARNESS_OPS_CHANGELOG_CAP_ITEMS`; the `CLAUDE_OPS_*` names are not read. The shared shell + helpers are `harness_ops::*`. The restart consumer's Windows scheduled task is + `HarnessOps Lane Restart Consumer`; delete a task registered under the old `ClaudeOps` name + and register it again from `restart-consumer.sh print-schedule`. +- **`morning-brief` reads only lane comments that open with the + `` sentinel,** so a comment left under an older + sentinel is not reported as a second, stale row for its lane. +- **BREAKING: `probe-observability-state.sh --otel-store` recognizes only the `harness-ops@` + plugin key.** A prune task whose launcher still names `claude-ops@` reports `stale path`; + rerun the provisioning apply (melodic-software/provisioning#670). + ## [1.2.0] - 2026-10-02 ### Added diff --git a/plugins/harness-ops/README.md b/plugins/harness-ops/README.md index c2de1837cc..0e51047393 100644 --- a/plugins/harness-ops/README.md +++ b/plugins/harness-ops/README.md @@ -42,7 +42,7 @@ Claude Code's native OTEL cannot see. | `/harness-ops:changelog` | Ingests Claude Code changelog entries and integrates them into the current repo: `fetch` (read-only display), `diff` (decision rows by owner surface and action lens over a release range, no edits), `status` (the read marker from the repo's Claude Code ledger, the default range to the newest release, and the replay cap), and `apply` (executes the decisions in scope one PR per owner plugin, hands larger ones off as work items, then runs a native-surface drift pass that files work items for new overlap candidates, fired store triggers, and a degraded or broken inventory; explicit user intent only). | | `/harness-ops:plugins` | Brings a machine's plugin fleet current on demand: marketplace refresh, updates for the plugins that actually load (including in-repo project/local-scope installs), new-catalog-plugin install per policy, and scope-divergence detection. Actions: `sync` (default, CLI-mediated mutations only), `audit` (read-only dry run), `converge` (the one action that can touch a committed `.claude/settings.json`. Previews and confirms per plugin first). | | `/harness-ops:morning-brief` | Prints the read-only, `gh`-based operator morning view for the current repo in one pass: open counts per queue label (`needs-triage`, `status: ready`, `status: needs-decision`, `needs-human`), the gh-native merge-ready PR list (non-draft + `mergeStateStatus=CLEAN`), parked `status: needs-decision` issues with their RECOMMENDED lines, and loop-lane telemetry freshness (per-lane `last-cycle` age + `flags:`). Never mutates anything; the authoritative PR merge gate stays `/source-control:babysit-prs`. | -| `/harness-ops:lanes` | Starts, restarts, stops, and reports loop lanes as named background Claude Code sessions seeded from canonical prompt files. `start` (default) / `restart` pull the repo and refresh the plugin marketplace, then launch each configured lane (`claude --bg -n --permission-mode auto`, plus `--permission-prompts none` on CLI 2.1.259 or later) with its per-lane `model`/`effort`; `status` shows per-lane running state and live sessionId; `stop` ends a lane via `claude stop`; `consume-restarts` is the OS-schedulable restart-request consumer. It reads each configured lane's telemetry `restart_request` and relaunches the stopped lanes that asked, through the same launcher (#1653). Acts only on sessions whose name is a configured lane. Lanes come from a JSON config (`--config`, else `$CLAUDE_OPS_LANES_CONFIG`, else `/.work/lanes/lanes.json`, with a temporary default-only fallback to the pre-move `/.work/lanes.json` under a deprecation warning); config and prompts live in the reserved `lanes/` concern home under a hardcoded `.work` root, which is a sanctioned placement but still session-local, so a durable cross-machine home stays #480's job. | +| `/harness-ops:lanes` | Starts, restarts, stops, and reports loop lanes as named background Claude Code sessions seeded from canonical prompt files. `start` (default) / `restart` pull the repo and refresh the plugin marketplace, then launch each configured lane (`claude --bg -n --permission-mode auto`, plus `--permission-prompts none` on CLI 2.1.259 or later) with its per-lane `model`/`effort`; `status` shows per-lane running state and live sessionId; `stop` ends a lane via `claude stop`; `consume-restarts` is the OS-schedulable restart-request consumer. It reads each configured lane's telemetry `restart_request` and relaunches the stopped lanes that asked, through the same launcher (#1653). Acts only on sessions whose name is a configured lane. Lanes come from a JSON config (`--config`, else `$HARNESS_OPS_LANES_CONFIG`, else `/.work/lanes/lanes.json`, with a temporary default-only fallback to the pre-move `/.work/lanes.json` under a deprecation warning); config and prompts live in the reserved `lanes/` concern home under a hardcoded `.work` root, which is a sanctioned placement but still session-local, so a durable cross-machine home stays #480's job. | | `/harness-ops:check` | Read-only check that `node` and `jq` resolve for the hooks, with the install route for a missing tool. Model-invocable; never installs. | | `/harness-ops:machine-profile` | Discovers this machine's facts and identity domains (each tree's git include, `gh` directory and verdicts), stores them as a re-runnable profile that records the observation behind every value, and reports drift between the stored profile and the host now. Actions: `profile` (default), `diff`, `explain `, `apply --option `. Read-only unless the operator confirms a write: `record --confirm` writes the profile document, and `apply --confirm` prints what to hand to each setup and writes nothing. Never installs and never reapplies a stored value on its own. Design: [machine-profile-design](https://github.com/melodic-software/claude-code-plugins/blob/038c2ae22c23f60500b339fd2f66e4569ecbe2fd/docs/specs/machine-profile-design.md). | | `/harness-ops:setup` | `check` reports the effective known-issues-registry, skill-usage-log and hook-log-root destinations, their defaults, path containment, the hook log root's self-ignoring guard, and retired conventions (`retirements.yaml`), and prints the guidance for routing personal option changes through Claude Code's plugin configuration prompt; `apply` writes exactly one file, the guard inside the hook log root, and runs the gated retirement cleanup. | diff --git a/plugins/harness-ops/hooks/audit-event-emitter.sh b/plugins/harness-ops/hooks/audit-event-emitter.sh index 1c5549181b..c039cb7109 100755 --- a/plugins/harness-ops/hooks/audit-event-emitter.sh +++ b/plugins/harness-ops/hooks/audit-event-emitter.sh @@ -185,7 +185,7 @@ emit::skill_expansion_row() { # before jq runs. exp_type=$(printf '%s' "$INPUT" | jq -r '(.expansion_type // empty) | gsub("\r";"")' 2>/dev/null) - claude_ops::record_skill_use "UserPromptExpansion" "skill-usage-expansion-audit" \ + harness_ops::record_skill_use "UserPromptExpansion" "skill-usage-expansion-audit" \ "$INPUT" "$skill" "expansion" "$exp_type" hook::telemetry_enabled || return 0 diff --git a/plugins/harness-ops/hooks/harness-ops-paths.sh b/plugins/harness-ops/hooks/harness-ops-paths.sh index 1382220340..cbce8cc453 100644 --- a/plugins/harness-ops/hooks/harness-ops-paths.sh +++ b/plugins/harness-ops/hooks/harness-ops-paths.sh @@ -15,7 +15,7 @@ fi # Resolve a project-relative directory without allowing an absolute path, # Windows drive/UNC path, `..` traversal, or an existing symlink ancestor to # escape the physical project root. Prints the candidate on success. -claude_ops::resolve_project_relative_dir() { +harness_ops::resolve_project_relative_dir() { local project_dir="$1" configured="$2" normalized segment candidate ancestor parent local -a segments @@ -67,7 +67,7 @@ claude_ops::resolve_project_relative_dir() { # digest of the full physical path — folding alone is lossy (/tmp/a-b and # /tmp/a/b would collide), so the digest carries the uniqueness. sha1sum ships # with Git Bash and Linux; cksum is the POSIX fallback. -claude_ops::repo_slug() { +harness_ops::repo_slug() { local p="" base hash physical="" hook::physical_path_to physical "$1" || : hook::normalize_path_to p "$physical" @@ -82,7 +82,7 @@ claude_ops::repo_slug() { # empty and '.' segments dropped. The resolver tolerates ./x and x//y when # writing (mkdir normalizes), but a git ignore pattern is matched literally, so # the exclude line must be canonical. -claude_ops::normalize_rel_segments() { +harness_ops::normalize_rel_segments() { local raw="${1//\\//}" out="" seg local -a segs IFS='/' read -r -a segs <<<"$raw" @@ -98,7 +98,7 @@ claude_ops::normalize_rel_segments() { # pattern, not a glob that over-matches sibling dirs. Leading #/! need no # handling: the exclude line always begins with the root anchor `/`, so the # comment/negation meaning (first-char-only) never applies. -claude_ops::gitignore_escape() { +harness_ops::gitignore_escape() { local s="$1" out="" ch i for ((i = 0; i < ${#s}; i++)); do ch="${s:i:1}" @@ -121,19 +121,19 @@ claude_ops::gitignore_escape() { # does not apply (the data dir is plugin-owned, keyed by repo). # Prints the destination on success. Returns 1 on an invalid configured dir, # 2 when the scope's base directory is unavailable. -claude_ops::resolve_skill_usage_dir() { +harness_ops::resolve_skill_usage_dir() { local scope="$1" project_dir="$2" rel_dir="$3" case "$scope" in user) [[ -n "${HOME:-}" && -d "${HOME:-}" ]] || return 2 - claude_ops::resolve_project_relative_dir "$HOME" "$rel_dir" || return 1 + harness_ops::resolve_project_relative_dir "$HOME" "$rel_dir" || return 1 ;; data-dir) [[ -n "${CLAUDE_PLUGIN_DATA:-}" ]] || return 2 - printf '%s' "${CLAUDE_PLUGIN_DATA%/}/skill-usage/$(claude_ops::repo_slug "$project_dir")" + printf '%s' "${CLAUDE_PLUGIN_DATA%/}/skill-usage/$(harness_ops::repo_slug "$project_dir")" ;; *) - claude_ops::resolve_project_relative_dir "$project_dir" "$rel_dir" || return 1 + harness_ops::resolve_project_relative_dir "$project_dir" "$rel_dir" || return 1 ;; esac } @@ -148,7 +148,7 @@ claude_ops::resolve_skill_usage_dir() { # semantics change, so tracked content is unaffected. Disable with # skill_usage_git_exclude=false. Best-effort: every failure is a silent no-op # (the write path must never break on ignore hygiene). -claude_ops::ensure_git_exclude() { +harness_ops::ensure_git_exclude() { local project_dir="$1" rel_dir="$2" store_file="${3:-skill-usage.jsonl}" exclude_file dir line [[ "${CLAUDE_PLUGIN_OPTION_SKILL_USAGE_GIT_EXCLUDE:-true}" == "true" ]] || return 0 exclude_file=$(git -C "$project_dir" rev-parse --git-path info/exclude 2>/dev/null | tr -d '\r') @@ -157,12 +157,12 @@ claude_ops::ensure_git_exclude() { /* | [A-Za-z]:*) ;; *) exclude_file="${project_dir%/}/$exclude_file" ;; esac - dir="$(claude_ops::normalize_rel_segments "$rel_dir")" + dir="$(harness_ops::normalize_rel_segments "$rel_dir")" if [[ -n "$dir" ]]; then - line="/$(claude_ops::gitignore_escape "$dir")/" + line="/$(harness_ops::gitignore_escape "$dir")/" else # Store dir IS the repo root — ignore the specific store file, not the tree. - line="/$(claude_ops::gitignore_escape "$store_file")" + line="/$(harness_ops::gitignore_escape "$store_file")" fi if [[ -f "$exclude_file" ]] && grep -qxF -- "$line" "$exclude_file" 2>/dev/null; then return 0 @@ -180,7 +180,7 @@ claude_ops::ensure_git_exclude() { # "-badscope / -badconfig / -nodest" and emitted for # . expansion_type is recorded only when non-empty (the tool-path # producer passes ""). -claude_ops::record_skill_use() { +harness_ops::record_skill_use() { local hook_event="$1" notice_prefix="$2" input="$3" skill="$4" src="$5" exp_type="$6" local project_dir="" rel_dir scope log_dir verified_log_dir ts branch line="" local -a exp_keys=() @@ -197,15 +197,15 @@ claude_ops::record_skill_use() { scope="repo" ;; esac - if ! log_dir=$(claude_ops::resolve_skill_usage_dir "$scope" "$project_dir" "$rel_dir"); then + if ! log_dir=$(harness_ops::resolve_skill_usage_dir "$scope" "$project_dir" "$rel_dir"); then if hook::notice_once "${notice_prefix}-badconfig" "$input"; then hook::emit_skip_notice "$hook_event" \ "harness-ops skipped skill-usage logging: the skill-usage destination is invalid for scope \"${scope}\" (repo/user scopes need a contained relative skill_usage_dir — no absolute, drive, UNC, traversal, or escaping symlink path; data-dir needs CLAUDE_PLUGIN_DATA)." fi elif mkdir -p "$log_dir" 2>/dev/null && - verified_log_dir=$(claude_ops::resolve_skill_usage_dir "$scope" "$project_dir" "$rel_dir") && + verified_log_dir=$(harness_ops::resolve_skill_usage_dir "$scope" "$project_dir" "$rel_dir") && [[ "$verified_log_dir" == "$log_dir" ]]; then - [[ "$scope" == "repo" ]] && claude_ops::ensure_git_exclude "$project_dir" "$rel_dir" + [[ "$scope" == "repo" ]] && harness_ops::ensure_git_exclude "$project_dir" "$rel_dir" ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S) # No work tree or an unborn HEAD fails the read and records "unknown". branch=$(git -C "$project_dir" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch="unknown" @@ -217,7 +217,7 @@ claude_ops::record_skill_use() { skill s "$skill" \ branch s "$branch" \ project s "$(basename -- "$project_dir")" \ - project_id s "$(claude_ops::repo_slug "$project_dir")" \ + project_id s "$(harness_ops::repo_slug "$project_dir")" \ hook s "skill-usage-audit" \ source s "$src" \ ${exp_keys[@]+"${exp_keys[@]}"} diff --git a/plugins/harness-ops/hooks/harness-ops-paths.test.sh b/plugins/harness-ops/hooks/harness-ops-paths.test.sh index 6641876675..67ad18be21 100755 --- a/plugins/harness-ops/hooks/harness-ops-paths.test.sh +++ b/plugins/harness-ops/hooks/harness-ops-paths.test.sh @@ -17,7 +17,7 @@ PROJECT="$TEST_TMPDIR/project" mkdir -p "$PROJECT" assert_eq "valid nested path resolves under project" "$PROJECT/telemetry/skills" \ - "$(claude_ops::resolve_project_relative_dir "$PROJECT" 'telemetry/skills')" + "$(harness_ops::resolve_project_relative_dir "$PROJECT" 'telemetry/skills')" for case_name in posix_absolute windows_drive windows_drive_relative unc traversal backslash_traversal; do case "$case_name" in @@ -36,7 +36,7 @@ for case_name in posix_absolute windows_drive windows_drive_relative unc travers continue ;; esac - if claude_ops::resolve_project_relative_dir "$PROJECT" "$value" >/dev/null; then + if harness_ops::resolve_project_relative_dir "$PROJECT" "$value" >/dev/null; then bad "$case_name path rejected" else ok "$case_name path rejected" @@ -46,7 +46,7 @@ done OUTSIDE="$TEST_TMPDIR/outside" mkdir -p "$OUTSIDE" if ln -s "$OUTSIDE" "$PROJECT/escape" 2>/dev/null && [[ -L "$PROJECT/escape" ]]; then - if claude_ops::resolve_project_relative_dir "$PROJECT" 'escape/skills' >/dev/null; then + if harness_ops::resolve_project_relative_dir "$PROJECT" 'escape/skills' >/dev/null; then bad "escaping symlink ancestor rejected" else ok "escaping symlink ancestor rejected" @@ -75,7 +75,7 @@ EOF #!/usr/bin/env bash source "$HOOK_DIR/hook-utils.sh" source "$HOOK_DIR/harness-ops-paths.sh" -claude_ops::resolve_project_relative_dir 'Q:/no-such-ancestor' 'telemetry/skills' +harness_ops::resolve_project_relative_dir 'Q:/no-such-ancestor' 'telemetry/skills' EOF chmod +x "$SHIM_DIR/dirname" timeout 10 env PATH="$SHIM_DIR:$PATH" bash "$SHIM_DIR/run-walkup.sh" >/dev/null 2>&1 @@ -91,10 +91,10 @@ fi FAKE_HOME="$TEST_TMPDIR/home" mkdir -p "$FAKE_HOME" assert_eq "repo scope resolves under project" "$PROJECT/.claude/observability" \ - "$(claude_ops::resolve_skill_usage_dir repo "$PROJECT" '.claude/observability')" + "$(harness_ops::resolve_skill_usage_dir repo "$PROJECT" '.claude/observability')" assert_eq "user scope resolves under HOME" "$FAKE_HOME/.claude/observability" \ - "$(HOME="$FAKE_HOME" claude_ops::resolve_skill_usage_dir user "$PROJECT" '.claude/observability')" -if HOME="$FAKE_HOME" claude_ops::resolve_skill_usage_dir user "$PROJECT" '../outside' >/dev/null; then + "$(HOME="$FAKE_HOME" harness_ops::resolve_skill_usage_dir user "$PROJECT" '.claude/observability')" +if HOME="$FAKE_HOME" harness_ops::resolve_skill_usage_dir user "$PROJECT" '../outside' >/dev/null; then bad "user scope traversal rejected" else ok "user scope traversal rejected" @@ -102,34 +102,34 @@ fi DATA_DIR="$TEST_TMPDIR/plugin-data" mkdir -p "$DATA_DIR" -slug=$(claude_ops::repo_slug "$PROJECT") +slug=$(harness_ops::repo_slug "$PROJECT") case "$slug" in *[!A-Za-z0-9._-]*) bad "repo_slug emits only safe bytes" ;; '') bad "repo_slug non-empty" ;; *) ok "repo_slug emits only safe bytes" ;; esac mkdir -p "$TEST_TMPDIR/a-b" "$TEST_TMPDIR/a/b" -if [[ "$(claude_ops::repo_slug "$TEST_TMPDIR/a-b")" != "$(claude_ops::repo_slug "$TEST_TMPDIR/a/b")" ]]; then +if [[ "$(harness_ops::repo_slug "$TEST_TMPDIR/a-b")" != "$(harness_ops::repo_slug "$TEST_TMPDIR/a/b")" ]]; then ok "repo_slug distinguishes fold-colliding paths" else bad "repo_slug distinguishes fold-colliding paths" fi -assert_eq "repo_slug is stable across calls" "$slug" "$(claude_ops::repo_slug "$PROJECT")" +assert_eq "repo_slug is stable across calls" "$slug" "$(harness_ops::repo_slug "$PROJECT")" assert_eq "normalize_rel_segments drops ./ and //" ".claude/observability" \ - "$(claude_ops::normalize_rel_segments './.claude//observability/')" + "$(harness_ops::normalize_rel_segments './.claude//observability/')" # portability-ok: 'telemetry\skills' is a backslash-separated path fixture, not # a GNU grep \s class — folding it is what this case asserts. assert_eq "normalize_rel_segments folds backslashes" "telemetry/skills" \ - "$(claude_ops::normalize_rel_segments 'telemetry\skills')" + "$(harness_ops::normalize_rel_segments 'telemetry\skills')" assert_eq "gitignore_escape escapes glob metachars" 'telemetry\*/a\?b/\[x]' \ - "$(claude_ops::gitignore_escape 'telemetry*/a?b/[x]')" + "$(harness_ops::gitignore_escape 'telemetry*/a?b/[x]')" assert_eq "gitignore_escape leaves ordinary paths intact" '.claude/observability' \ - "$(claude_ops::gitignore_escape '.claude/observability')" + "$(harness_ops::gitignore_escape '.claude/observability')" assert_eq "data-dir scope keys by repo slug" "$DATA_DIR/skill-usage/$slug" \ - "$(CLAUDE_PLUGIN_DATA="$DATA_DIR" claude_ops::resolve_skill_usage_dir data-dir "$PROJECT" '.claude/observability')" -if CLAUDE_PLUGIN_DATA="" claude_ops::resolve_skill_usage_dir data-dir "$PROJECT" '.claude/observability' >/dev/null; then + "$(CLAUDE_PLUGIN_DATA="$DATA_DIR" harness_ops::resolve_skill_usage_dir data-dir "$PROJECT" '.claude/observability')" +if CLAUDE_PLUGIN_DATA="" harness_ops::resolve_skill_usage_dir data-dir "$PROJECT" '.claude/observability' >/dev/null; then bad "data-dir scope without CLAUDE_PLUGIN_DATA fails" else ok "data-dir scope without CLAUDE_PLUGIN_DATA fails" @@ -139,7 +139,7 @@ fi REPO="$TEST_TMPDIR/repo" mkdir -p "$REPO" if git -C "$REPO" init -q 2>/dev/null; then - claude_ops::ensure_git_exclude "$REPO" '.claude/observability' + harness_ops::ensure_git_exclude "$REPO" '.claude/observability' EXCL="$REPO/.git/info/exclude" if grep -qxF -- '/.claude/observability/' "$EXCL" 2>/dev/null; then ok "exclude line added to .git/info/exclude" @@ -147,7 +147,7 @@ if git -C "$REPO" init -q 2>/dev/null; then bad "exclude line added to .git/info/exclude" fi before=$(grep -cxF -- '/.claude/observability/' "$EXCL") - claude_ops::ensure_git_exclude "$REPO" '.claude/observability' + harness_ops::ensure_git_exclude "$REPO" '.claude/observability' assert_eq "exclude append is idempotent" "$before" \ "$(grep -cxF -- '/.claude/observability/' "$EXCL")" mkdir -p "$REPO/.claude/observability" @@ -160,7 +160,7 @@ if git -C "$REPO" init -q 2>/dev/null; then REPO3="$TEST_TMPDIR/repo3" mkdir -p "$REPO3" git -C "$REPO3" init -q 2>/dev/null - claude_ops::ensure_git_exclude "$REPO3" './.claude//observability/' + harness_ops::ensure_git_exclude "$REPO3" './.claude//observability/' if grep -qxF -- '/.claude/observability/' "$REPO3/.git/info/exclude" 2>/dev/null; then ok "denormalized configured dir yields canonical exclude line" else @@ -177,7 +177,7 @@ if git -C "$REPO" init -q 2>/dev/null; then mkdir -p "$REPO2" git -C "$REPO2" init -q 2>/dev/null CLAUDE_PLUGIN_OPTION_SKILL_USAGE_GIT_EXCLUDE=false \ - claude_ops::ensure_git_exclude "$REPO2" '.claude/observability' + harness_ops::ensure_git_exclude "$REPO2" '.claude/observability' if grep -qxF -- '/.claude/observability/' "$REPO2/.git/info/exclude" 2>/dev/null; then bad "skill_usage_git_exclude=false suppresses the exclude write" else @@ -189,7 +189,7 @@ if git -C "$REPO" init -q 2>/dev/null; then REPO4="$TEST_TMPDIR/repo4" mkdir -p "$REPO4" git -C "$REPO4" init -q 2>/dev/null - claude_ops::ensure_git_exclude "$REPO4" 'telemetry*' + harness_ops::ensure_git_exclude "$REPO4" 'telemetry*' if grep -qxF -- '/telemetry\*/' "$REPO4/.git/info/exclude" 2>/dev/null; then ok "glob-metachar configured dir written as literal exclude pattern" else @@ -199,7 +199,7 @@ if git -C "$REPO" init -q 2>/dev/null; then REPO5="$TEST_TMPDIR/repo5" mkdir -p "$REPO5" git -C "$REPO5" init -q 2>/dev/null - claude_ops::ensure_git_exclude "$REPO5" '.' + harness_ops::ensure_git_exclude "$REPO5" '.' if grep -qxF -- '/skill-usage.jsonl' "$REPO5/.git/info/exclude" 2>/dev/null; then ok "repo-root store excludes the store file, not the tree" else @@ -213,7 +213,7 @@ if git -C "$REPO" init -q 2>/dev/null; then fi fi # --- The skill-usage store record ------------------------------------------- -# One writer for the row: claude_ops::record_skill_use formats it through +# One writer for the row: harness_ops::record_skill_use formats it through # session-log-lib.sh, so the row's escaping is the plugin's one escaping path # and costs no jq process. The store keeps its OWN key set (`event: # "SkillUse"`), read by skills/audit-skill-visibility; it is not a hook event @@ -227,9 +227,9 @@ SU_SCHEMA='(.ts|type)=="string" and .event=="SkillUse" SU_LOG="$SU_PROJ/.claude/observability/skill-usage.jsonl" SU_SKILL='a:one "quoted" \ tricky' export CLAUDE_PROJECT_DIR="$SU_PROJ" -claude_ops::record_skill_use PostToolUse skill-usage-audit \ +harness_ops::record_skill_use PostToolUse skill-usage-audit \ '{"session_id":"s1"}' "$SU_SKILL" tool '' >/dev/null -claude_ops::record_skill_use UserPromptExpansion skill-usage-expansion-audit \ +harness_ops::record_skill_use UserPromptExpansion skill-usage-expansion-audit \ '{"session_id":"s1"}' b:two expansion slash_command >/dev/null unset CLAUDE_PROJECT_DIR if [[ -s "$SU_LOG" ]]; then @@ -255,7 +255,7 @@ if git -C "$SU_REPO" init -q 2>/dev/null && SU_REPO_LOG="$SU_REPO/.claude/observability/skill-usage.jsonl" SU_REPO_BRANCH="$(git -C "$SU_REPO" rev-parse --abbrev-ref HEAD)" export CLAUDE_PROJECT_DIR="$SU_REPO" - claude_ops::record_skill_use PostToolUse skill-usage-audit \ + harness_ops::record_skill_use PostToolUse skill-usage-audit \ '{"session_id":"s2"}' one tool '' >/dev/null assert_eq "skill-usage route: branch is the checked-out branch" "$SU_REPO_BRANCH" \ "$(head -1 "$SU_REPO_LOG" | jq -r '.branch')" @@ -264,7 +264,7 @@ fi NONREPO="$TEST_TMPDIR/nonrepo" mkdir -p "$NONREPO" -claude_ops::ensure_git_exclude "$NONREPO" '.claude/observability' +harness_ops::ensure_git_exclude "$NONREPO" '.claude/observability' ok "non-repo project is a silent no-op for exclude hygiene" report diff --git a/plugins/harness-ops/hooks/skill-usage-audit.sh b/plugins/harness-ops/hooks/skill-usage-audit.sh index b6bc4e0d04..e9398d09b6 100755 --- a/plugins/harness-ops/hooks/skill-usage-audit.sh +++ b/plugins/harness-ops/hooks/skill-usage-audit.sh @@ -55,7 +55,7 @@ SKILL=$( SKILL="${SKILL#/}" # --- Second store: skill-usage.jsonl (unconditional) ------------------------ -claude_ops::record_skill_use "PostToolUse" "skill-usage-audit" "$INPUT" "$SKILL" "tool" "" +harness_ops::record_skill_use "PostToolUse" "skill-usage-audit" "$INPUT" "$SKILL" "tool" "" # --- Telemetry envelope (only when a sink is wired) ------------------------- if hook::telemetry_enabled; then diff --git a/plugins/harness-ops/skills/audit-skill-visibility/reference/pair-cooccurrence.md b/plugins/harness-ops/skills/audit-skill-visibility/reference/pair-cooccurrence.md index 836a62864e..ad7a3ca13b 100644 --- a/plugins/harness-ops/skills/audit-skill-visibility/reference/pair-cooccurrence.md +++ b/plugins/harness-ops/skills/audit-skill-visibility/reference/pair-cooccurrence.md @@ -28,7 +28,7 @@ unreadable store or a destination that cannot be resolved, `3` for bad arguments ## The default store is the writer's store Without `--store` the script sources the hooks' own resolver -(`hooks/harness-ops-paths.sh`, `claude_ops::resolve_skill_usage_dir`) and gives it the same +(`hooks/harness-ops-paths.sh`, `harness_ops::resolve_skill_usage_dir`) and gives it the same three inputs the writers use: the scope, the relative dir, and the project root (`CLAUDE_PROJECT_DIR`, else the working directory). So the file it opens is the file the hooks wrote, in every scope. A default restated here would be one branch of that policy, correct diff --git a/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.sh b/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.sh index a8d06417b1..7ff498469e 100755 --- a/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.sh +++ b/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.sh @@ -31,7 +31,7 @@ # # WHERE THE STORE IS — the hooks decide, this script asks them: # -# The writers select the store through claude_ops::resolve_skill_usage_dir +# The writers select the store through harness_ops::resolve_skill_usage_dir # in ../../../hooks/harness-ops-paths.sh (skill_usage_scope: repo, user or # data-dir; skill_usage_dir under the scope root). Without --store this script # sources that same resolver and feeds it the same options, so the file it @@ -218,7 +218,7 @@ resolve_store_from_scope() { # skill subprocess, the working directory otherwise. An unresolved root # (not a git checkout) falls back to the hint, as it does for the writers. project_dir=$(hook::repo_root "${CLAUDE_PROJECT_DIR:-.}") || true - store_dir=$(CLAUDE_PLUGIN_DATA="$DATA_ROOT" claude_ops::resolve_skill_usage_dir "$SCOPE" "$project_dir" "$REL_DIR") + store_dir=$(CLAUDE_PLUGIN_DATA="$DATA_ROOT" harness_ops::resolve_skill_usage_dir "$SCOPE" "$project_dir" "$REL_DIR") rc=$? case "$rc" in 0) ;; diff --git a/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.test.sh b/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.test.sh index 79e30c33f3..f608092c0e 100755 --- a/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.test.sh +++ b/plugins/harness-ops/skills/audit-skill-visibility/scripts/skill-pair-cooccurrence.test.sh @@ -245,7 +245,7 @@ assert_contains "…and names the explicit override as where it looked" "$err" " # --- the default store is the writer's store, in every scope ----------------- # The writer (the skill-usage-audit hook) selects its destination through -# claude_ops::resolve_skill_usage_dir; the reader must open that same file for +# harness_ops::resolve_skill_usage_dir; the reader must open that same file for # each scope, not a restated copy of one branch. Each case runs the REAL writer # with the hook-environment option mirrors, then the reader with the rendered # option values the skill body passes, and asserts the two meet: the reader's @@ -283,7 +283,7 @@ run_reader() { # expected_store : the writer's destination, from the resolver. expected_store() { local dir - dir="$(HOME="$FAKE_HOME" CLAUDE_PLUGIN_DATA="$DATA_ROOT" claude_ops::resolve_skill_usage_dir "$1" "$PROJECT" "$2")" || return 1 + dir="$(HOME="$FAKE_HOME" CLAUDE_PLUGIN_DATA="$DATA_ROOT" harness_ops::resolve_skill_usage_dir "$1" "$PROJECT" "$2")" || return 1 printf '%s/skill-usage.jsonl' "$dir" } diff --git a/plugins/harness-ops/skills/changelog/SKILL.md b/plugins/harness-ops/skills/changelog/SKILL.md index 28e3a54a0f..40045a6dee 100644 --- a/plugins/harness-ops/skills/changelog/SKILL.md +++ b/plugins/harness-ops/skills/changelog/SKILL.md @@ -51,7 +51,7 @@ On every `apply` or `diff` invocation, compare the newest release in the range a ## Read marker and replay cap One line in the repository's upstream ledger for Claude Code releases (default -`docs/upstream/claude-code.md`, override `CLAUDE_OPS_CHANGELOG_LEDGER`) records the newest release +`docs/upstream/claude-code.md`, override `HARNESS_OPS_CHANGELOG_LEDGER`) records the newest release the repository has been read against. `status` reads that line; with no ledger it falls back to the highest version named in a Conventional Commits SUBJECT of the form `chore(): address Claude Code v.. changelog`, and it never reads commit bodies, because diff --git a/plugins/harness-ops/skills/changelog/context/read-actions.md b/plugins/harness-ops/skills/changelog/context/read-actions.md index a4e7ad4dcf..5a53888193 100644 --- a/plugins/harness-ops/skills/changelog/context/read-actions.md +++ b/plugins/harness-ops/skills/changelog/context/read-actions.md @@ -20,7 +20,7 @@ requires. Git history of the ledger records when the line moved. Where the ledger lives, first hit wins: -1. `CLAUDE_OPS_CHANGELOG_LEDGER`, used verbatim when set. +1. `HARNESS_OPS_CHANGELOG_LEDGER`, used verbatim when set. 2. `docs/upstream/claude-code.md` under the repository root (the git toplevel, or the working directory outside a repository). @@ -42,7 +42,7 @@ marker; a single `vX` is that one release. The replay cap is ten releases or 300 core items (bullet lines in a release block, `[VSCode]` lines excluded), overridable with `--cap-releases` and `--cap-items` or the -`CLAUDE_OPS_CHANGELOG_CAP_RELEASES` and `CLAUDE_OPS_CHANGELOG_CAP_ITEMS` environment variables. Beyond +`HARNESS_OPS_CHANGELOG_CAP_RELEASES` and `HARNESS_OPS_CHANGELOG_CAP_ITEMS` environment variables. Beyond the cap, replaying items costs more than it returns: the current docs already carry the cumulative state, so the honest move is a docs-conformance recheck of the components against them, then a marker set at the newest published release and a `diff` from there. `diff` and `apply` stop at an diff --git a/plugins/harness-ops/skills/changelog/evals/evals.json b/plugins/harness-ops/skills/changelog/evals/evals.json index e760449767..a6442dbfc3 100644 --- a/plugins/harness-ops/skills/changelog/evals/evals.json +++ b/plugins/harness-ops/skills/changelog/evals/evals.json @@ -72,7 +72,7 @@ { "id": 6, "name": "status-ignores-commit-body-mentions", - "prompt": "/harness-ops:changelog status\n\nThis repository has no docs/upstream/claude-code.md and no CLAUDE_OPS_CHANGELOG_LEDGER. Its recent git log is reproduced in evals/fixtures/git-log-body-mentions.txt: three commits whose BODIES mention Claude Code v2.1.252, Claude Code v2.1.255, and CC v2.1.250, and no commit whose SUBJECT reads `address Claude Code v changelog`. Use evals/fixtures/changelog-sample.md as the changelog copy instead of fetching.", + "prompt": "/harness-ops:changelog status\n\nThis repository has no docs/upstream/claude-code.md and no HARNESS_OPS_CHANGELOG_LEDGER. Its recent git log is reproduced in evals/fixtures/git-log-body-mentions.txt: three commits whose BODIES mention Claude Code v2.1.252, Claude Code v2.1.255, and CC v2.1.250, and no commit whose SUBJECT reads `address Claude Code v changelog`. Use evals/fixtures/changelog-sample.md as the changelog copy instead of fetching.", "expected_output": "The status action reports last-applied none with source none: the ledger is absent and no commit SUBJECT names an applied release. It does NOT report 2.1.252, 2.1.255, or 2.1.250 as applied, because those versions appear only in commit bodies, which are recency stamps on docs rather than applies. It reports the whole fixture feed as the range (2.1.257..2.1.263, 6 releases, 20 core items), cap within budget, and relays the script's recommendation to create the ledger with the marker at 2.1.263 once that range is applied.", "files": [ "evals/fixtures/git-log-body-mentions.txt", @@ -88,7 +88,7 @@ { "id": 7, "name": "range-beyond-cap-stops-with-docs-conformance-recommendation", - "prompt": "/harness-ops:changelog diff v2.1.257..v2.1.263\n\nFor this run the replay cap is 3 releases (CLAUDE_OPS_CHANGELOG_CAP_RELEASES=3). The ledger is evals/fixtures/ledger-marker.md and the changelog copy is evals/fixtures/changelog-sample.md; use them instead of fetching.", + "prompt": "/harness-ops:changelog diff v2.1.257..v2.1.263\n\nFor this run the replay cap is 3 releases (HARNESS_OPS_CHANGELOG_CAP_RELEASES=3). The ledger is evals/fixtures/ledger-marker.md and the changelog copy is evals/fixtures/changelog-sample.md; use them instead of fetching.", "expected_output": "The diff runs the status script with `--range v2.1.257..v2.1.263`, which reports range 2.1.257..2.1.263 (6 releases, 20 core items) and cap exceeded (3 releases / 300 items). The diff STOPS there: no per-item triage table, no explore pass over repo surfaces, no research subagents. It relays the script's recommendation as the output: run a docs-conformance recheck of the components against the current docs, then set the marker in the ledger to 2.1.263 and diff only from there. No file is edited.", "files": [ "evals/fixtures/ledger-marker.md", diff --git a/plugins/harness-ops/skills/changelog/scripts/changelog-status.sh b/plugins/harness-ops/skills/changelog/scripts/changelog-status.sh index 48df8b9bd9..70e59e0075 100755 --- a/plugins/harness-ops/skills/changelog/scripts/changelog-status.sh +++ b/plugins/harness-ops/skills/changelog/scripts/changelog-status.sh @@ -15,7 +15,7 @@ # Marker resolution, first hit wins: # 1. The ledger's marker line: # **Last audited upstream state:** changelog through `X.Y.Z` -# Ledger path: --ledger, else $CLAUDE_OPS_CHANGELOG_LEDGER (verbatim), else +# Ledger path: --ledger, else $HARNESS_OPS_CHANGELOG_LEDGER (verbatim), else # /docs/upstream/claude-code.md. # 2. A Conventional Commits SUBJECT on the current branch naming an applied release: # (): address Claude Code v[..] changelog @@ -38,8 +38,8 @@ # the newest published release. --range A..B (v prefix optional) is inclusive at # both ends and ignores the marker; --range X is the single release X. # Core items: bullet lines inside an block, excluding lines tagged [VSCode]. -# Cap: --cap-releases (default $CLAUDE_OPS_CHANGELOG_CAP_RELEASES, else 10) and -# --cap-items (default $CLAUDE_OPS_CHANGELOG_CAP_ITEMS, else 300). Beyond the cap, +# Cap: --cap-releases (default $HARNESS_OPS_CHANGELOG_CAP_RELEASES, else 10) and +# --cap-items (default $HARNESS_OPS_CHANGELOG_CAP_ITEMS, else 300). Beyond the cap, # replaying items costs more than it returns because the current docs already carry # the cumulative state, so the output recommends a docs-conformance recheck of the # components and a marker reset instead of a replay. @@ -73,8 +73,8 @@ ledger_arg="" changelog_arg="" no_fetch=0 range_arg="" -cap_releases="${CLAUDE_OPS_CHANGELOG_CAP_RELEASES:-10}" -cap_items="${CLAUDE_OPS_CHANGELOG_CAP_ITEMS:-300}" +cap_releases="${HARNESS_OPS_CHANGELOG_CAP_RELEASES:-10}" +cap_items="${HARNESS_OPS_CHANGELOG_CAP_ITEMS:-300}" need_value() { if [[ $# -lt 2 || -z "$2" ]]; then @@ -175,8 +175,8 @@ fi # --- Ledger and marker ----------------------------------------------------------- if [[ -n "$ledger_arg" ]]; then ledger="$ledger_arg" -elif [[ -n "${CLAUDE_OPS_CHANGELOG_LEDGER:-}" ]]; then - ledger="$CLAUDE_OPS_CHANGELOG_LEDGER" +elif [[ -n "${HARNESS_OPS_CHANGELOG_LEDGER:-}" ]]; then + ledger="$HARNESS_OPS_CHANGELOG_LEDGER" else ledger="$repo_root/docs/upstream/claude-code.md" fi diff --git a/plugins/harness-ops/skills/changelog/scripts/changelog-status.test.sh b/plugins/harness-ops/skills/changelog/scripts/changelog-status.test.sh index a8504356fd..87aef2e7c1 100755 --- a/plugins/harness-ops/skills/changelog/scripts/changelog-status.test.sh +++ b/plugins/harness-ops/skills/changelog/scripts/changelog-status.test.sh @@ -16,7 +16,7 @@ # - a body whose first heading is not the changelog page is refused, and a body # with the heading but no release blocks is a parse failure, not "up to date" # - the installed-version warn compares against the newest release IN THE RANGE -# - CLAUDE_OPS_CHANGELOG_LEDGER overrides the default path; --ledger wins over it +# - HARNESS_OPS_CHANGELOG_LEDGER overrides the default path; --ledger wins over it # - a PATH-stub `claude` older than the newest release produces the warn line # - an unknown argument exits 3 # @@ -223,10 +223,10 @@ REPO_ENV="$TMP/repo-env" make_repo "$REPO_ENV" mkdir -p "$REPO_ENV/notes" sed 's/2\.1\.260/2.1.259/' "$LEDGER" >"$REPO_ENV/notes/cc.md" -OUT="$(cd "$REPO_ENV" && CLAUDE_OPS_CHANGELOG_LEDGER="$REPO_ENV/notes/cc.md" bash "$SCRIPT" --changelog "$CHANGELOG" 2>/dev/null)" +OUT="$(cd "$REPO_ENV" && HARNESS_OPS_CHANGELOG_LEDGER="$REPO_ENV/notes/cc.md" bash "$SCRIPT" --changelog "$CHANGELOG" 2>/dev/null)" assert_contains "env: override path read" "$OUT" "last-applied: 2.1.259" assert_contains "env: source names the override" "$OUT" "source: ledger:$REPO_ENV/notes/cc.md" -OUT="$(cd "$REPO_ENV" && CLAUDE_OPS_CHANGELOG_LEDGER="$REPO_ENV/notes/cc.md" bash "$SCRIPT" --ledger "$LEDGER" --changelog "$CHANGELOG" 2>/dev/null)" +OUT="$(cd "$REPO_ENV" && HARNESS_OPS_CHANGELOG_LEDGER="$REPO_ENV/notes/cc.md" bash "$SCRIPT" --ledger "$LEDGER" --changelog "$CHANGELOG" 2>/dev/null)" assert_contains "env: --ledger wins over the env" "$OUT" "last-applied: 2.1.260" # --- Case 11: default ledger path under the repo root --------------------------------- diff --git a/plugins/harness-ops/skills/lanes/SKILL.md b/plugins/harness-ops/skills/lanes/SKILL.md index 687b611498..1667c1a67e 100644 --- a/plugins/harness-ops/skills/lanes/SKILL.md +++ b/plugins/harness-ops/skills/lanes/SKILL.md @@ -162,7 +162,7 @@ registering the schedule or changing the consumer. ## Lane config Lanes are defined in a JSON config, resolved first-hit-wins: -`--config FILE` → `$CLAUDE_OPS_LANES_CONFIG` → `/.work/lanes/lanes.json`. Each lane +`--config FILE` → `$HARNESS_OPS_LANES_CONFIG` → `/.work/lanes/lanes.json`. Each lane carries a `name`, a `prompt` file path, and optional `model`/`effort`/`settings` (a session-only `claude --settings` override, e.g. opting the lane into the `autonomy` plugin's lane-stop gate). The full @@ -174,7 +174,7 @@ files live inside `/.work/lanes/`, a reserved first-level name under the memory root, not as bare files at the root itself. Lanes **hardcodes the literal `.work` root**: it does not resolve a repointed memory root, and a consumer that has repointed it elsewhere must pass `--config` or set -`$CLAUDE_OPS_LANES_CONFIG`. That is a stated carve-out, not an oversight. The +`$HARNESS_OPS_LANES_CONFIG`. That is a stated carve-out, not an oversight. The launcher is an operator script invoked outside a session (an OS schedule, a bare shell), where no skill body is loaded to resolve the setting for it, and the escape hatch is what covers the remaining case. @@ -183,7 +183,7 @@ escape hatch is what covers the remaining case. bare `/.work/lanes.json` keeps working: when the `lanes/` home holds no config, the launcher reads the old path and prints a one-line deprecation warning naming the move. Only the **default** falls back, so `--config` and -`$CLAUDE_OPS_LANES_CONFIG` keep meaning exactly what they say. A config resolved +`$HARNESS_OPS_LANES_CONFIG` keep meaning exactly what they say. A config resolved at the old path also keeps the old `prompt_dir` default (`.work`), so prompts that never moved still resolve. Move both into `.work/lanes/` to clear the warning; the fallback is temporary. diff --git a/plugins/harness-ops/skills/lanes/context/config.md b/plugins/harness-ops/skills/lanes/context/config.md index 0c42b95f69..30c7186f94 100644 --- a/plugins/harness-ops/skills/lanes/context/config.md +++ b/plugins/harness-ops/skills/lanes/context/config.md @@ -8,7 +8,7 @@ to manage. This file is the full contract; the SKILL.md keeps only the summary. First hit wins: 1. `--config FILE` -2. `$CLAUDE_OPS_LANES_CONFIG` +2. `$HARNESS_OPS_LANES_CONFIG` 3. `/.work/lanes/lanes.json` (repo = `--repo DIR`, else the git toplevel of the cwd) A missing config exits `4`; malformed JSON or a config with no lanes exits `3`. @@ -18,12 +18,12 @@ and the lane prompt files live inside it rather than as bare files at the root. The `.work` root is **hardcoded**: the launcher does not resolve a repointed memory root, because it runs as an operator script outside any session that could resolve one. A consumer that has repointed the memory root passes -`--config` or sets `$CLAUDE_OPS_LANES_CONFIG` instead. +`--config` or sets `$HARNESS_OPS_LANES_CONFIG` instead. **Pre-move compatibility.** When step 3 finds nothing and the pre-move `/.work/lanes.json` exists, that file is read and a one-line deprecation warning names the move. Only the default falls back: `--config` and -`$CLAUDE_OPS_LANES_CONFIG` are used verbatim, so a config kept outside the memory +`$HARNESS_OPS_LANES_CONFIG` are used verbatim, so a config kept outside the memory root still fails loudly rather than silently resolving to a leftover file. A config resolved at the pre-move path also keeps the pre-move `prompt_dir` default (`.work`), so a config that never named one still finds the prompts it left beside diff --git a/plugins/harness-ops/skills/lanes/context/restart-consumer.md b/plugins/harness-ops/skills/lanes/context/restart-consumer.md index b6f22f4ba9..ccc611cd4d 100644 --- a/plugins/harness-ops/skills/lanes/context/restart-consumer.md +++ b/plugins/harness-ops/skills/lanes/context/restart-consumer.md @@ -50,7 +50,7 @@ no-op that only refreshes the consumer's own freshness telemetry. Discovered from the producers (`work-items:work-loop`, `source-control:babysit-loop`), not assumed: each lane upserts one -sentinel-marked comment (``) on its +sentinel-marked comment (``) on its `Lane telemetry: ` issue whose fenced JSON state block carries `restart_request`, emitted as `null` in every published example. No producer specifies a non-null shape, so the consumer treats **any non-null value** as a @@ -91,8 +91,8 @@ Git Bash, whose MSYS path conversion rewrites `/`-style options (`schtasks `/TR` paths are Windows-form (`cygpath -w`), since cmd.exe cannot use the MSYS-form paths Git Bash resolves. -**Verify:** from cmd.exe, `schtasks /Query /TN "ClaudeOps Lane Restart -Consumer"`, then `schtasks /Run /TN "ClaudeOps Lane Restart Consumer"` and +**Verify:** from cmd.exe, `schtasks /Query /TN "HarnessOps Lane Restart +Consumer"`, then `schtasks /Run /TN "HarnessOps Lane Restart Consumer"` and confirm a fresh `last-cycle:` on the consumer's **telemetry comment**. That one signal, and not a local-file alternative, is the whole check: `last-cycle:` is written only by `upsert_own_telemetry`, which returns early unless the action is @@ -148,7 +148,7 @@ parity claim. ## Observability (the consumer must not become the next silent gap) - **Morning-brief surfacing.** On every `run` the consumer upserts its own - sentinel-marked comment (marker `claude-ops:restart-consumer`) carrying the + sentinel-marked comment (marker `harness-ops:restart-consumer`) carrying the same `lane:` / `last-cycle:` / `flags:` header `morning-brief.sh` already parses. The comment must land on the ONE issue that reader resolves, so the consumer's default discovery reuses the brief's own title search; when that diff --git a/plugins/harness-ops/skills/lanes/evals/evals.json b/plugins/harness-ops/skills/lanes/evals/evals.json index 54c3fab97a..ce5e576135 100644 --- a/plugins/harness-ops/skills/lanes/evals/evals.json +++ b/plugins/harness-ops/skills/lanes/evals/evals.json @@ -5,7 +5,7 @@ "id": 1, "name": "start-is-default-action", "prompt": "launch my lanes", - "expected_output": "Routes to the default `start` action: runs lane-launcher.sh, which resolves the lane config (--config, else $CLAUDE_OPS_LANES_CONFIG, else /.work/lanes/lanes.json, falling back to the pre-move /.work/lanes.json under a deprecation warning), pulls the repo and refreshes the plugin marketplace, then launches each configured lane NOT already running as a named background session (claude --bg -n --permission-mode auto [--model] [--effort] ). Prints the script output verbatim.", + "expected_output": "Routes to the default `start` action: runs lane-launcher.sh, which resolves the lane config (--config, else $HARNESS_OPS_LANES_CONFIG, else /.work/lanes/lanes.json, falling back to the pre-move /.work/lanes.json under a deprecation warning), pulls the repo and refreshes the plugin marketplace, then launches each configured lane NOT already running as a named background session (claude --bg -n --permission-mode auto [--model] [--effort] ). Prints the script output verbatim.", "files": [], "expectations": [ "Output routes to the start action (lane-launcher.sh with no explicit action), not restart/stop/status", diff --git a/plugins/harness-ops/skills/lanes/scripts/lane-launcher.sh b/plugins/harness-ops/skills/lanes/scripts/lane-launcher.sh index 76545f95ca..eed75a8d16 100755 --- a/plugins/harness-ops/skills/lanes/scripts/lane-launcher.sh +++ b/plugins/harness-ops/skills/lanes/scripts/lane-launcher.sh @@ -84,13 +84,13 @@ # config preflight rejects a name that is not a single path component. # # Config resolution (first hit wins): -# --config FILE → $CLAUDE_OPS_LANES_CONFIG → /.work/lanes/lanes.json +# --config FILE → $HARNESS_OPS_LANES_CONFIG → /.work/lanes/lanes.json # Compatibility: when none of those hit and the pre-move `/.work/lanes.json` # exists, that file is read instead, with a one-line deprecation WARNING. A # config resolved at the pre-move path also keeps the pre-move `prompt_dir` # default (".work"), so a config that never named one still finds the prompts # it left beside itself. Move both to `.work/lanes/` to clear the warning; -# $CLAUDE_OPS_LANES_CONFIG remains the escape hatch for a config kept elsewhere. +# $HARNESS_OPS_LANES_CONFIG remains the escape hatch for a config kept elsewhere. # # Config schema (see context/config.md for the full contract): # { "prompt_dir": ".work/lanes", @@ -341,14 +341,14 @@ LEGACY_CONFIG_HOME=0 resolve_config() { if [[ -z "$CONFIG" ]]; then - CONFIG="${CLAUDE_OPS_LANES_CONFIG:-$REPO/$LANES_CONFIG_REL}" + CONFIG="${HARNESS_OPS_LANES_CONFIG:-$REPO/$LANES_CONFIG_REL}" # Backward compatibility for a checkout that predates the move. Only the - # DEFAULT falls back: an explicit --config or $CLAUDE_OPS_LANES_CONFIG is + # DEFAULT falls back: an explicit --config or $HARNESS_OPS_LANES_CONFIG is # used verbatim, as documented, so the escape hatch keeps meaning exactly # what it says. Warn rather than fail: an operator whose lanes ran this # morning must not have `start` exit 4 on them, and the warning is what # turns a silent old-path read into a visible one-step migration. - if [[ -z "${CLAUDE_OPS_LANES_CONFIG:-}" && ! -f "$CONFIG" && -f "$REPO/$LEGACY_LANES_CONFIG_REL" ]]; then + if [[ -z "${HARNESS_OPS_LANES_CONFIG:-}" && ! -f "$CONFIG" && -f "$REPO/$LEGACY_LANES_CONFIG_REL" ]]; then warn "reading the pre-move lane config at $REPO/$LEGACY_LANES_CONFIG_REL" warn " move it (and the lane prompt files) to $REPO/.work/lanes/ — this fallback is temporary" CONFIG="$REPO/$LEGACY_LANES_CONFIG_REL" diff --git a/plugins/harness-ops/skills/lanes/scripts/lane-launcher.test.sh b/plugins/harness-ops/skills/lanes/scripts/lane-launcher.test.sh index a8c98dad48..be8e0d9ac8 100755 --- a/plugins/harness-ops/skills/lanes/scripts/lane-launcher.test.sh +++ b/plugins/harness-ops/skills/lanes/scripts/lane-launcher.test.sh @@ -1076,10 +1076,10 @@ assert_contains "with both present the lanes/ home wins" "$out" "DRY-RUN: claude assert_not_contains "the leftover pre-move config is not read" "$out" "-n stale" assert_not_contains "no warning when the lanes/ home exists" "$out" "pre-move lane config" -# $CLAUDE_OPS_LANES_CONFIG stays the escape hatch: used verbatim, never +# $HARNESS_OPS_LANES_CONFIG stays the escape hatch: used verbatim, never # suffixed and never fallen back from, so a config kept outside the memory root # fails loudly rather than silently resolving to a pre-move file. -out="$(CLAUDE_OPS_LANES_CONFIG="$TMP/nowhere.json" run_launcher status --repo "$OLD_REPO" --agents-json "$AGENTS_EMPTY" 2>&1)" +out="$(HARNESS_OPS_LANES_CONFIG="$TMP/nowhere.json" run_launcher status --repo "$OLD_REPO" --agents-json "$AGENTS_EMPTY" 2>&1)" rc=$? assert_eq "the env override is not fallen back from" 4 "$rc" assert_contains "the env override is used verbatim" "$out" "lane config not found: $TMP/nowhere.json" diff --git a/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.sh b/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.sh index 5124bbd793..fd5702f7cb 100755 --- a/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.sh +++ b/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.sh @@ -22,7 +22,7 @@ # absent () — author one (see context/config.md) # # Path resolution: -# CLAUDE_OPS_LANES_CONFIG when set and non-empty — used verbatim, no suffix +# HARNESS_OPS_LANES_CONFIG when set and non-empty — used verbatim, no suffix # appended; otherwise /.work/lanes/lanes.json, falling back to the pre-move # /.work/lanes.json when that exists and the new path does not. The @@ -68,8 +68,8 @@ repo_root() { printf '%s\n' "${root:-$PWD}" } -if [[ -n "${CLAUDE_OPS_LANES_CONFIG:-}" ]]; then - CONFIG="$CLAUDE_OPS_LANES_CONFIG" +if [[ -n "${HARNESS_OPS_LANES_CONFIG:-}" ]]; then + CONFIG="$HARNESS_OPS_LANES_CONFIG" else ROOT="$(repo_root)" CONFIG="$ROOT/.work/lanes/lanes.json" diff --git a/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.test.sh b/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.test.sh index e7994408c2..a42ab4305d 100755 --- a/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.test.sh +++ b/plugins/harness-ops/skills/lanes/scripts/probe-lane-config.test.sh @@ -12,7 +12,7 @@ # proving anything. # # Coverage: -# - CLAUDE_OPS_LANES_CONFIG override: used verbatim, no +# - HARNESS_OPS_LANES_CONFIG override: used verbatim, no # `.work/lanes/lanes.json` suffix appended; an EMPTY value falls through to # the default # - default path from the git toplevel, and from the working directory when @@ -59,7 +59,7 @@ assert_eq() { if [[ "$3" == "$2" ]]; then pass "$1"; else fail "$1" "$2" "$3"; f # --- The inline equivalent of the resolution the script bundles -------------- ORIGINAL="$TMP/original.sh" cat >"$ORIGINAL" <<'ORIG' -r="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"; c="${CLAUDE_OPS_LANES_CONFIG:-}"; if [[ -z "$c" ]]; then c="$r/.work/lanes/lanes.json"; [[ ! -f "$c" && -f "$r/.work/lanes.json" ]] && c="$r/.work/lanes.json"; fi; [[ -f "$c" ]] && echo "$c ($(jq -r '(.lanes//[])|length' "$c" 2>/dev/null) lanes)" || echo "absent ($c) — author one (see context/config.md)" +r="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"; c="${HARNESS_OPS_LANES_CONFIG:-}"; if [[ -z "$c" ]]; then c="$r/.work/lanes/lanes.json"; [[ ! -f "$c" && -f "$r/.work/lanes.json" ]] && c="$r/.work/lanes.json"; fi; [[ -f "$c" ]] && echo "$c ($(jq -r '(.lanes//[])|length' "$c" 2>/dev/null) lanes)" || echo "absent ($c) — author one (see context/config.md)" ORIG # --- Stubs ------------------------------------------------------------------- @@ -158,7 +158,7 @@ run_both() { # --- 1. Env override, present config ----------------------------------------- export STUB_GIT_TOPLEVEL="$ROOT" -export CLAUDE_OPS_LANES_CONFIG="$ROOT/.work/lanes/lanes.json" +export HARNESS_OPS_LANES_CONFIG="$ROOT/.work/lanes/lanes.json" if ((HAVE_JQ)); then run_both "override → present config with a lane count" "$ROOT/.work/lanes/lanes.json (3 lanes)" else @@ -166,19 +166,19 @@ else fi # --- 2. Env override used verbatim (no .work/lanes/lanes.json suffix appended) - -export CLAUDE_OPS_LANES_CONFIG="$TMP/nowhere.json" +export HARNESS_OPS_LANES_CONFIG="$TMP/nowhere.json" run_both "override is used verbatim when the file is absent" \ "absent ($TMP/nowhere.json) — author one (see context/config.md)" # --- 3. Empty override falls through to the default -------------------------- -export CLAUDE_OPS_LANES_CONFIG="" +export HARNESS_OPS_LANES_CONFIG="" if ((HAVE_JQ)); then run_both "empty override falls through to /.work/lanes/lanes.json" \ "$ROOT/.work/lanes/lanes.json (3 lanes)" else run_both "empty override falls through (jq absent; equivalence only)" "SKIP" fi -unset CLAUDE_OPS_LANES_CONFIG +unset HARNESS_OPS_LANES_CONFIG # --- 4. Default path from the git toplevel ----------------------------------- export STUB_GIT_TOPLEVEL="$ELSEWHERE" @@ -220,20 +220,20 @@ fi # --- 7. `.lanes` absent from the config, and malformed JSON ------------------ export STUB_GIT_TOPLEVEL="$ROOT" if ((HAVE_JQ)); then - export CLAUDE_OPS_LANES_CONFIG="$NO_LANES_KEY" + export HARNESS_OPS_LANES_CONFIG="$NO_LANES_KEY" run_both "config without a .lanes key counts 0" "$NO_LANES_KEY (0 lanes)" - export CLAUDE_OPS_LANES_CONFIG="$MALFORMED" + export HARNESS_OPS_LANES_CONFIG="$MALFORMED" run_both "malformed config degrades the count to empty" "$MALFORMED ( lanes)" - unset CLAUDE_OPS_LANES_CONFIG + unset HARNESS_OPS_LANES_CONFIG else printf 'SKIP: jq absent — real-count cases not exercised\n' fi # --- 8. jq missing → the count degrades to empty, not an error --------------- export PATH="$NOJQ:$BASE_PATH" -export CLAUDE_OPS_LANES_CONFIG="$ROOT/.work/lanes/lanes.json" +export HARNESS_OPS_LANES_CONFIG="$ROOT/.work/lanes/lanes.json" run_both "jq missing degrades the count to empty" "$ROOT/.work/lanes/lanes.json ( lanes)" -unset CLAUDE_OPS_LANES_CONFIG +unset HARNESS_OPS_LANES_CONFIG export PATH="$BASE_PATH" # --- 9. Argument validation --------------------------------------------------- diff --git a/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh b/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh index 19ce7d6e22..ab853e77c8 100755 --- a/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh +++ b/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh @@ -27,7 +27,7 @@ # # Options: # --config FILE lane config JSON (same resolution as lane-launcher.sh: -# --config -> $CLAUDE_OPS_LANES_CONFIG -> +# --config -> $HARNESS_OPS_LANES_CONFIG -> # /.work/lanes/lanes.json, with the same temporary # default-only fallback to the pre-move # /.work/lanes.json under a deprecation warning) @@ -80,7 +80,7 @@ # "instance": "laptop-a", "repo": "owner/name" } } # issue default: the open issue titled exactly `Lane telemetry: `. # marker default: any comment on that issue carrying the shared sentinel -# `` whose fenced JSON +# `` whose fenced JSON # block has a `restart_request` key. A bound marker matches that lane # type across EVERY writer instance (`@`, #1295) — # an exact-equality match would go blind the moment lanes adopted the @@ -154,7 +154,7 @@ # `skipped-running` / `breaker-open` ticks are reported but NOT ledgered, so the # file grows with incidents rather than with the polling interval. `check` is # read-only and writes nothing. Every `run` also upserts the consumer's own -# sentinel-marked telemetry comment (marker `claude-ops:restart-consumer`) +# sentinel-marked telemetry comment (marker `harness-ops:restart-consumer`) # carrying the `lane:` / `last-cycle:` / `flags:` fields `morning-brief.sh` # already parses — so a consumer that stops running surfaces as a STALE lane in # the morning brief instead of becoming a second silent gap. @@ -190,10 +190,10 @@ INTERVAL_MINUTES=15 NOW_EPOCH="" declare -a TARGET_LANES=() -CONSUMER_MARKER="claude-ops:restart-consumer" +CONSUMER_MARKER="harness-ops:restart-consumer" CONSUMER_LANE="restart-consumer" -SENTINEL_PREFIX="\nlane: x\n\n```json\n{"schema":"x@1","cycle":9,"loop_started_at":"2026-07-23T15:00:00Z","restart_request":%s}\n```\n' "$1" "$2" + printf '\nlane: x\n\n```json\n{"schema":"x@1","cycle":9,"loop_started_at":"2026-07-23T15:00:00Z","restart_request":%s}\n```\n' "$1" "$2" } write_telemetry() { # $1 outfile, $2 work request, $3 babysit request @@ -373,7 +373,7 @@ assert_not_contains "--lane drops the others" "$OUT" "| work |" OUT="$(bash "$SCRIPT" print-schedule --repo "$REPO" --interval-minutes 20 2>&1)" RC=$? assert_eq "print-schedule exits 0" "0" "$RC" -assert_contains "print-schedule emits a schtasks create" "$OUT" "schtasks /Create /TN \"ClaudeOps Lane Restart Consumer\"" +assert_contains "print-schedule emits a schtasks create" "$OUT" "schtasks /Create /TN \"HarnessOps Lane Restart Consumer\"" assert_contains "print-schedule honors --interval-minutes" "$OUT" "/SC MINUTE /MO 20" assert_contains "print-schedule avoids elevation and stored passwords" "$OUT" "/IT /RL LIMITED" assert_contains "print-schedule emits the removal command" "$OUT" "schtasks /Delete" @@ -552,7 +552,7 @@ OUT="$(GH_LOG="$GH_PINNED" run_telemetry --telemetry-issue 77)" assert_contains "a pinned --telemetry-issue is used verbatim" "$(cat "$GH_PINNED")" "issues/77/comments" assert_not_contains "a pinned issue skips discovery entirely" "$(cat "$GH_PINNED")" "in:title" assert_contains "the upserted body carries the morning-brief header fields" "$(cat "$GH_PINNED")" "last-cycle: 2027-01-15T08:00:00Z" -assert_contains "the upsert is marker-identified for edit-in-place" "$(cat "$GH_PINNED")" "claude-ops:restart-consumer" +assert_contains "the upsert is marker-identified for edit-in-place" "$(cat "$GH_PINNED")" "harness-ops:restart-consumer" GH_SEARCH="$TMP/gh-search.log" OUT="$(GH_LOG="$GH_SEARCH" GH_SEARCH_RESULT='[{"number":55},{"number":91}]' run_telemetry)" @@ -857,7 +857,7 @@ cp "$CONFIG" "$REPO/.work/lanes/lanes.json" OUT="$(run_default_config)" assert_not_contains "the lanes/ home wins and nothing warns" "$OUT" "reading the pre-move lane config" -OUT="$(CLAUDE_OPS_LANES_CONFIG="$TMP/nowhere.json" run_default_config)" +OUT="$(HARNESS_OPS_LANES_CONFIG="$TMP/nowhere.json" run_default_config)" RC=$? assert_eq "the env override is not fallen back from" "4" "$RC" assert_contains "the env override is used verbatim" "$OUT" "lane config not found: $TMP/nowhere.json" diff --git a/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.sh b/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.sh index 08920b0ae6..8eab28a890 100755 --- a/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.sh +++ b/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.sh @@ -11,7 +11,7 @@ # MARKER / DETECTION CONVENTION (reusable across lanes and tracking issues): # The script writes a machine-detectable sentinel as the FIRST line of the # comment body: -# +# # is a caller-supplied short id (e.g. `lane:triage`) constrained to # [A-Za-z0-9:@._-] so it can never contain the `>` that would close the comment # early. The sentinel is an HTML comment: invisible in the rendered issue, and @@ -310,7 +310,7 @@ if ((body_bytes < MIN_BODY_BYTES)); then exit 3 fi -SENTINEL="" +SENTINEL="" new_body="$SENTINEL"$'\n'"$body_text" # --- Resolve owner/repo ------------------------------------------------------ diff --git a/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.test.sh b/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.test.sh index 20f0c5f2b6..745429b13c 100755 --- a/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.test.sh +++ b/plugins/harness-ops/skills/lanes/scripts/telemetry-upsert.test.sh @@ -164,7 +164,7 @@ printf 'lane: triage\nlast-cycle: 2026-07-21T06:00:00Z\nflags: none\n' >"$BODY" REPO="melodic-software/claude-code-plugins" run() { STUB_COMMENTS_FILE="$1" bash "$SCRIPT" --repo "$REPO" --issue 502 --marker "lane:triage" --body-file "$BODY" --body-dir "$SAFE_DIR" "${@:2}"; } -SENT='' +SENT='' export STUB_SENTINEL="$SENT" # ============================================================================ @@ -241,7 +241,7 @@ assert_not_contains "never PATCHes another user's comment" "$log" "method=PATCH" cat >"$TMP/prefix-collision.json" <<'JSON' [ { "id": 555, "created_at": "2026-07-19T00:00:00Z", "user": {"login": "octocat"}, - "body": "\nother lane" } + "body": "\nother lane" } ] JSON : >"$LOG" @@ -259,7 +259,7 @@ assert_not_contains "never PATCHes the longer lane's comment" "$log" "method=PAT cat >"$TMP/instance-suffix.json" <<'JSON' [ { "id": 666, "created_at": "2026-07-19T00:00:00Z", "user": {"login": "octocat"}, - "body": "\nlane: triage" } + "body": "\nlane: triage" } ] JSON : >"$LOG" @@ -272,11 +272,11 @@ assert_not_contains "never PATCHes an instance-suffixed comment" "$log" "method= # an instance marker must not adopt a LONGER instance marker either — the same # boundary rule one level down (`lane:triage@a` vs `lane:triage@a@b`). # ============================================================================ -INST_SENT='' +INST_SENT='' cat >"$TMP/instance-superstring.json" <<'JSON' [ { "id": 888, "created_at": "2026-07-19T00:00:00Z", "user": {"login": "octocat"}, - "body": "\nlane: triage" } + "body": "\nlane: triage" } ] JSON : >"$LOG" diff --git a/plugins/harness-ops/skills/morning-brief/morning-brief.test.sh b/plugins/harness-ops/skills/morning-brief/morning-brief.test.sh index bd0cf0ed6a..e72ce1ff76 100755 --- a/plugins/harness-ops/skills/morning-brief/morning-brief.test.sh +++ b/plugins/harness-ops/skills/morning-brief/morning-brief.test.sh @@ -97,13 +97,14 @@ cat >"$TMP/decisions.json" <<'EOF' EOF # babysit fresh (1h30m), triage stale (26h), work unparsable stamp, work-2 has -# flags, and a non-lane scope-note comment that must be skipped. +# flags; a lane comment without the sentinel and a scope-note comment are skipped. cat >"$TMP/telemetry.json" <<'EOF' [ - {"body": "- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}, - {"body": "- lane: triage\n- last-cycle: 2026-07-19T06:00Z\n- flags: queue-depth climbing"}, - {"body": "- lane: work\n- last-cycle: 2026-07-20T07:XX\n- flags: -"}, - {"body": "- lane: work-2\n- last-cycle: 2026-07-20T07:40Z\n- flags: gh-identity drift detected"}, + {"body": "\n- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}, + {"body": "\n- lane: triage\n- last-cycle: 2026-07-19T06:00Z\n- flags: queue-depth climbing"}, + {"body": "\n- lane: work\n- last-cycle: 2026-07-20T07:XX\n- flags: -"}, + {"body": "\n- lane: work-2\n- last-cycle: 2026-07-20T07:40Z\n- flags: gh-identity drift detected"}, + {"body": "- lane: unmarked\n- last-cycle: 2026-07-18T06:00Z\n- flags: none"}, {"body": "Scope note: this issue is the interim telemetry surface."} ] EOF @@ -288,6 +289,7 @@ assert_contains "telemetry triage stale flagged" "$OUT" "STALE (>6h)" assert_contains "telemetry unparsable stamp handled" "$OUT" "unparsable timestamp" assert_contains "telemetry surfaces flags" "$OUT" "gh-identity drift detected" assert_not_contains "telemetry hides flags=none" "$OUT" "flags: none" +assert_not_contains "telemetry skips a lane comment without the sentinel" "$OUT" "unmarked" # --- RECOMMENDED preview truncation ------------------------------------------ OUT_TRUNC="$(bash "$BRIEF" --now "$NOW" --rec-maxlen 30 \ @@ -749,7 +751,7 @@ cat >"$REST/$(rest_key "$R/issues?state=open&per_page=100").json" <<'EOF' {"number": 52, "title": "loop-lane telemetry running per-lane status (older duplicate)", "pull_request": {"url": "x"}}] EOF cat >"$REST/$(rest_key "$R/issues/50/comments?per_page=100").json" <<'EOF' -[{"body": "- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}] +[{"body": "\n- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}] EOF OUT_REST="$(run_stub rest)" @@ -787,7 +789,7 @@ cat >"$TMP/same-decisions.json" <<'EOF' [{"number": 42, "title": "pick a store", "url": "http://x/i/42", "body": "Options weighed.\nRECOMMENDED: option B.", "comments": [{"body": "no change of lean"}]}] EOF cat >"$TMP/same-telemetry.json" <<'EOF' -[{"body": "- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}] +[{"body": "\n- lane: babysit\n- last-cycle: 2026-07-20T06:30Z\n- flags: none"}] EOF OUT_SAME="$(bash "$BRIEF" --now "$NOW" --stale-hours 6 --repo "$FIXTURE_REPO" \ --repo-labels-json "$TMP/same-labels.json" \ diff --git a/plugins/harness-ops/skills/morning-brief/scripts/morning-brief.sh b/plugins/harness-ops/skills/morning-brief/scripts/morning-brief.sh index b935738bbc..70a977b744 100755 --- a/plugins/harness-ops/skills/morning-brief/scripts/morning-brief.sh +++ b/plugins/harness-ops/skills/morning-brief/scripts/morning-brief.sh @@ -890,12 +890,14 @@ print_telemetry() { return fi - # A lane comment carries a `lane:` field. For each, pull lane, last-cycle, flags. + # A lane comment opens with the lane-telemetry sentinel and carries a `lane:` + # field. For each, pull lane, last-cycle, flags. local n n="$(jq -r 'length' <<<"$comments" 2>/dev/null || echo 0)" local i body lane last flags any=0 for ((i = 0; i < n; i++)); do body="$(jq -r ".[$i].body // \"\"" <<<"$comments" 2>/dev/null)" + [[ "$body" == "`,** matching the `harness-ops` and + `work-items` writers. + ## [0.71.5] - 2026-10-02 ### Changed diff --git a/plugins/source-control/skills/babysit-loop/SKILL.md b/plugins/source-control/skills/babysit-loop/SKILL.md index 27dff09f32..c8d76da93f 100644 --- a/plugins/source-control/skills/babysit-loop/SKILL.md +++ b/plugins/source-control/skills/babysit-loop/SKILL.md @@ -229,7 +229,7 @@ absent, the canonical `needs-human` default applies with a loud notice, plus a m escalation comment whose first line is ``. That marker grammar is the attended queue's escalated-view data contract; the sentinel names the contract owner, not the writer -(as the `claude-ops:lane-telemetry` sentinel below does), so babysit escalations surface in the same +(as the `harness-ops:lane-telemetry` sentinel below does), so babysit escalations surface in the same attention view as worker escalations. The same step performs the contract's escalation record write shape, suppression, and the seam it feeds are §2's; three things a lane executor must not get wrong are restated here. diff --git a/plugins/source-control/skills/babysit-loop/reference/telemetry-upsert.md b/plugins/source-control/skills/babysit-loop/reference/telemetry-upsert.md index b17456f7ea..dec6d74489 100644 --- a/plugins/source-control/skills/babysit-loop/reference/telemetry-upsert.md +++ b/plugins/source-control/skills/babysit-loop/reference/telemetry-upsert.md @@ -35,7 +35,7 @@ esac exit 1 } MARKER="source-control:babysit-loop@$INSTANCE" -SENT="" # $BODY_FILE MUST open with this line +SENT="" # $BODY_FILE MUST open with this line LOOKUP() { gh api --paginate "repos/$REPO/issues/$ISSUE/comments?per_page=100" \ --jq ".[] | select(.body | startswith(\"$SENT\")) | .id"; } SENTINEL_OK() { # $1 = text; true iff line 1 is exactly $SENT and >=16 payload bytes follow diff --git a/plugins/testing/.claude-plugin/plugin.json b/plugins/testing/.claude-plugin/plugin.json index 8926a47aee..c815d96187 100644 --- a/plugins/testing/.claude-plugin/plugin.json +++ b/plugins/testing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "testing", - "version": "0.18.2", + "version": "0.18.3", "description": "Test-stage discipline across all ecosystems: coverage-gap analysis and test planning (`/testing:plan`), TDD test authoring and placement (`/testing:write`), live E2E plus non-UI smoke verification (`/testing:run-e2e`), failing-test root-cause diagnosis with the reproduce → isolate → fix → retest loop (`/testing:diagnose`), a deterministic can't-fail test audit with a fail-closed gate mode and opt-in findings persistence (`/testing:audit`), a cleanup that rewrites, quarantines or, on approval, deletes low-value tests in one folder behind a mutation gate (`/testing:cleanup`), its configuration (`/testing:setup`), and opt-in hooks that scan each test file Claude writes and question edits that weaken tests.", "author": { "name": "Melodic Software", diff --git a/plugins/testing/CHANGELOG.md b/plugins/testing/CHANGELOG.md index 5d44b9608b..8747ee1035 100644 --- a/plugins/testing/CHANGELOG.md +++ b/plugins/testing/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the `testing` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.18.3] - 2026-10-02 + +### Changed + +- **The judge-calibration u16 case moves to `cases/u16/plugins/harness-ops/`, and the u31 + fixtures carry the `harness-ops:lane-telemetry` sentinel.** The `source` column keeps its + commit-pinned `path@sha`, which resolves only under the path at that commit. + ## [0.18.2] - 2026-10-02 ### Fixed diff --git a/plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/claude-ops/skills/observability/scripts/probe-observability-state.sh.fixture b/plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/harness-ops/skills/observability/scripts/probe-observability-state.sh.fixture similarity index 100% rename from plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/claude-ops/skills/observability/scripts/probe-observability-state.sh.fixture rename to plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/harness-ops/skills/observability/scripts/probe-observability-state.sh.fixture diff --git a/plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/claude-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture b/plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture similarity index 100% rename from plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/claude-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture rename to plugins/testing/skills/audit/evals/judge-calibration/cases/u16/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture diff --git a/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.sh.fixture b/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.sh.fixture index 9679e238f8..8237e94372 100755 --- a/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.sh.fixture +++ b/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.sh.fixture @@ -32,7 +32,7 @@ # --help # # MARKER. `work-items:@`, and the sentinel written as the body's -# first line is ``. The sentinel +# first line is ``. The sentinel # is an HTML comment: invisible when rendered, distinct per writer, so sibling # instances each own one comment on the SAME issue without colliding. # @@ -92,7 +92,7 @@ # KNOWN LIMITS. A PATCH that succeeds while storing the previous body still # verifies: the read-back asserts that SOME well-formed telemetry is present, not # that THIS cycle's write is what is present. Not implemented at all: the 64 KiB -# cap, body-file containment, and read retries that the `claude-ops` lanes wrapper +# cap, body-file containment, and read retries that the `harness-ops` lanes wrapper # carries. An installed plugin cannot invoke a sibling plugin's scripts, which is # why this mechanism lives here rather than deferring to that wrapper. @@ -238,7 +238,7 @@ for bin in gh jq; do done MARKER="work-items:$LANE@$INSTANCE" -SENT="" +SENT="" # --- Pre-write body gate ----------------------------------------------------- if [[ ! -s "$BODY_FILE" ]]; then diff --git a/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.test.sh.fixture b/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.test.sh.fixture index 5f22bfe579..6942f3697b 100755 --- a/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.test.sh.fixture +++ b/plugins/testing/skills/audit/evals/judge-calibration/cases/u31/plugins/work-items/scripts/lane-telemetry-upsert.test.sh.fixture @@ -144,7 +144,7 @@ LANE=work-loop INSTANCE=laptop-a REPO=melodic-software/example-repo ISSUE=42 -SENT="" +SENT="" run() { # sets RC, STDERR, STDOUT; args after the fixed five STDERR="$WORK/stderr.txt" @@ -235,7 +235,7 @@ fi # --- 4. a sibling instance's comment is never adopted or tombstoned --------- fresh_state -printf '%s\nsibling lane body\n' "" \ +printf '%s\nsibling lane body\n' "" \ >"$FAKE_GH_STATE/c-5.body" run "$GOOD" check_rc 0 "$RC" "sibling instance present still exits 0" diff --git a/plugins/testing/skills/audit/evals/judge-calibration/labels.tsv b/plugins/testing/skills/audit/evals/judge-calibration/labels.tsv index ff834bf4b0..c92833b590 100644 --- a/plugins/testing/skills/audit/evals/judge-calibration/labels.tsv +++ b/plugins/testing/skills/audit/evals/judge-calibration/labels.tsv @@ -43,7 +43,7 @@ u13-2 medley:tools/shared/video-digestion/frames/scene-detect.test.js@3e2675609a u14-1 claude-code-plugins:plugins/machine-health/skills/audit/tests/windows/lib/Get-ApprovalState.Tests.ps1@92bbfc809f13c66a48b6a4b3a348851c332d6665 pwsh-pester cases/u14/plugins/machine-health/skills/audit/tests/windows/lib/Get-ApprovalState.Tests.ps1.fixture returns the parsed state with approvals intact PASS PASS PASS PASS changed 1-205; code under test: plugins/machine-health/skills/audit/scripts/windows/lib/Get-ApprovalState.ps1 plugins/machine-health/skills/audit/tests/helpers/Mock-Helpers.psm1 in-use holdout u14-2 claude-code-plugins:plugins/machine-health/skills/audit/tests/windows/lib/Get-ApprovalState.Tests.ps1@92bbfc809f13c66a48b6a4b3a348851c332d6665 pwsh-pester cases/u14/plugins/machine-health/skills/audit/tests/windows/lib/Get-ApprovalState.Tests.ps1.fixture records approved_by and a timestamp in migrated entries PASS PASS UNKNOWN PASS changed 1-205; code under test: plugins/machine-health/skills/audit/scripts/windows/lib/Get-ApprovalState.ps1 plugins/machine-health/skills/audit/tests/helpers/Mock-Helpers.psm1 in-use holdout u15-1 medley:tools/github-events/stop-webhook-broker.test.sh@3e2675609a171c7216220352a81780b8469a62d0 bash-harness cases/u15/tools/github-events/stop-webhook-broker.test.sh.fixture stop-webhook-broker.test.sh UNKNOWN FLAG UNKNOWN PASS changed 59-60,91-93,125-128,194-213; code under test: tools/github-events/stop-webhook-broker.sh tests/shell/lib.sh tools/shared/process-management/pid-alive.sh in-use tune -u16-1 claude-code-plugins:plugins/claude-ops/skills/observability/scripts/probe-observability-state.test.sh@7708cbae98e6b61356226e002b2a62650e963f21 bash-harness cases/u16/plugins/claude-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture probe-observability-state.test.sh PASS PASS PASS PASS changed 27-28,139-139,210-210,213-236; code under test: plugins/claude-ops/skills/observability/scripts/probe-observability-state.sh in-use tune +u16-1 claude-code-plugins:plugins/claude-ops/skills/observability/scripts/probe-observability-state.test.sh@7708cbae98e6b61356226e002b2a62650e963f21 bash-harness cases/u16/plugins/harness-ops/skills/observability/scripts/probe-observability-state.test.sh.fixture probe-observability-state.test.sh PASS PASS PASS PASS changed 27-28,139-139,210-210,213-236; code under test: plugins/harness-ops/skills/observability/scripts/probe-observability-state.sh in-use tune u17-1 ci-runner:internal/controller/shutdown_test.go@524dc550c19694a8d3ee702ed72b35acb1827877 go-testing cases/u17/internal/controller/shutdown_test.go.fixture TestShutdownDrainsTransientlyAndClosesAdapters PASS PASS PASS PASS changed 1-74; code under test: internal/controller/control_handler.go internal/controller/force_stop.go internal/controller/plan.go internal/controller/ports.go internal/controller/reconciler.go internal/controller/retry.go internal/controller/shutdown.go in-use tune u18-1 claude-code-plugins:plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh@354c67d0bd9d505a60b650b47f085f12a62e9e09 bash-harness cases/u18/plugins/source-control/hooks/pr-linkage-mcp-gate.test.sh.fixture pr-linkage-mcp-gate.test.sh PASS PASS UNKNOWN PASS changed 114-115; code under test: plugins/source-control/hooks/pr-linkage-mcp-gate.sh in-use tune u19-1 claude-code-plugins:plugins/knowledge/skills/video-digest/extraction/transcript/write-transcript.test.js@1e977ae552135f0905628c89b8259b73b94d3809 js-vitest cases/u19/plugins/knowledge/skills/video-digest/extraction/transcript/write-transcript.test.js.fixture caption-absent + capability absent degrades explicitly in the named provenance field UNKNOWN PASS UNKNOWN PASS changed 96-113,120-120,160-160,183-190,200-209,211-211,239-239,247-254; code under test: plugins/knowledge/skills/video-digest/extraction/transcript/write-transcript.js plugins/knowledge/skills/video-digest/extraction/adapters/adapter-contract.js in-use holdout diff --git a/plugins/work-items/.claude-plugin/plugin.json b/plugins/work-items/.claude-plugin/plugin.json index 58937ab775..dc231ea837 100644 --- a/plugins/work-items/.claude-plugin/plugin.json +++ b/plugins/work-items/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "work-items", - "version": "0.43.8", + "version": "0.44.0", "description": "Manages development work items through a provider-neutral tracker seam that ships with the plugin (bundled dispatcher plus github, local-markdown, jira, gitea, and linear adapters; seam plugin-dir canonical, adapters consumer-local-first): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical-slice items, a macro-journey router over spec containers (rollup, per-container execution shape, next-step routing), raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states), plus the two work-items loop lanes of the loop-lane convention: a self-paced autonomous work-loop drain (work-class admission gate, adaptive item cap, PR-only) and an attended attend-queue escalation lane. The re-runnable setup skill binds the provider (.work-item-tracker.json), seeds the recurring-schedule seam (.github/recurring-schedule.json), and remaps canonical role labels.", "author": { "name": "Melodic Software", diff --git a/plugins/work-items/CHANGELOG.md b/plugins/work-items/CHANGELOG.md index 8de636ca5e..b21c424900 100644 --- a/plugins/work-items/CHANGELOG.md +++ b/plugins/work-items/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the `work-items` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.44.0] - 2026-10-02 + +### Changed + +- **BREAKING: the lane telemetry sentinel is ``.** + `lane-telemetry-upsert.sh` no longer finds a comment posted under the old `claude-ops:` + sentinel and writes a new one; delete the old comment by hand. + ## [0.43.8] - 2026-10-02 ### Fixed diff --git a/plugins/work-items/scripts/lane-telemetry-upsert.sh b/plugins/work-items/scripts/lane-telemetry-upsert.sh index 8f737bb64b..d488fece72 100755 --- a/plugins/work-items/scripts/lane-telemetry-upsert.sh +++ b/plugins/work-items/scripts/lane-telemetry-upsert.sh @@ -32,7 +32,7 @@ # --help # # MARKER. `work-items:@`, and the sentinel written as the body's -# first line is ``. The sentinel +# first line is ``. The sentinel # is an HTML comment: invisible when rendered, distinct per writer, so sibling # instances each own one comment on the SAME issue without colliding. # @@ -218,7 +218,7 @@ for bin in gh jq; do done MARKER="work-items:$LANE@$INSTANCE" -SENT="" +SENT="" # --- Pre-write body gate ----------------------------------------------------- if [[ ! -s "$BODY_FILE" ]]; then diff --git a/plugins/work-items/scripts/lane-telemetry-upsert.test.sh b/plugins/work-items/scripts/lane-telemetry-upsert.test.sh index adaf54c611..c44ac30529 100755 --- a/plugins/work-items/scripts/lane-telemetry-upsert.test.sh +++ b/plugins/work-items/scripts/lane-telemetry-upsert.test.sh @@ -141,7 +141,7 @@ LANE=work-loop INSTANCE=laptop-a REPO=melodic-software/example-repo ISSUE=42 -SENT="" +SENT="" run() { # sets RC, STDERR, STDOUT; args after the fixed five STDERR="$WORK/stderr.txt" @@ -232,7 +232,7 @@ fi # --- 4. a sibling instance's comment is never adopted or tombstoned --------- fresh_state -printf '%s\nsibling lane body\n' "" \ +printf '%s\nsibling lane body\n' "" \ >"$FAKE_GH_STATE/c-5.body" run "$GOOD" check_rc 0 "$RC" "sibling instance present still exits 0" diff --git a/plugins/work-items/skills/attend-queue/reference/telemetry-upsert.md b/plugins/work-items/skills/attend-queue/reference/telemetry-upsert.md index 57f002b5f5..99456a199b 100644 --- a/plugins/work-items/skills/attend-queue/reference/telemetry-upsert.md +++ b/plugins/work-items/skills/attend-queue/reference/telemetry-upsert.md @@ -35,7 +35,7 @@ bash "/scripts/lane-telemetry-upsert.sh" \ ## What the upsert writes The marker is `work-items:attend-queue@` and the comment's first line is the sentinel -``, an HTML comment that is invisible when +``, an HTML comment that is invisible when rendered and distinct per writer, so sibling instances each own one comment on the same issue. The lookup is a `startswith` match on that full sentinel, so a body that merely quotes a sibling's sentinel is never adopted. Where the lookup finds nothing the script creates the comment; where it diff --git a/plugins/work-items/skills/triage/SKILL.md b/plugins/work-items/skills/triage/SKILL.md index bc781a1951..e80bfa91ed 100644 --- a/plugins/work-items/skills/triage/SKILL.md +++ b/plugins/work-items/skills/triage/SKILL.md @@ -55,7 +55,7 @@ Three rules bound what enters this flow: - **A PR is an item with attached code.** An unsolicited or external PR enters the same intake as an issue: same states, same machine. Its diff is an **attachment to evaluate**, check it out, run the relevant tests, never an obligation to merge. Read the state names against the code: briefed means a brief exists for what to do with the diff; human-gated means a human should decide the merge. - **Never re-triage already-triaged output.** Items born triaged. Published by `/work-items:decompose`, or created by a `/work-items:track add` that leaves no raw marker. Already carry a routing decision. They never re-enter this flow, and the attention view excludes them by construction (being neither unlabeled nor marked with the raw marker, they fall in none of its buckets). This exclusion keys on **absence of the raw marker**, not authorship and not the mere presence of classification labels: the raw marker (bare `needs-triage`) or being unlabeled puts an item in scope even alongside default labels, so a team-authored dogfood issue filed with a default `priority:` label *and* the raw marker is in scope (the marker wins), while a `track add` item that carries classification labels but no raw marker is out of scope for the same reason decompose output is. If someone names an already-triaged item explicitly, say it is already triaged and stop. -- **Lane infrastructure is never intake.** The loop-lane convention's per-lane telemetry tracking issues, the surfaces holding that convention's sentinel-marked status comment, are lane infrastructure, not backlog: an open one is a lane operating. **Identify one the way the lane resolves its own telemetry home**, never by title alone: the issue the lane's launch config pins (`lanes[].telemetry.issue` in the `harness-ops` lane config, read from `/.work/lanes/lanes.json`, or from `/.work/lanes.json` when only that file exists), else the default `Lane telemetry: ` title (`/work-items:work-loop`, "Telemetry and durable loop state"); and, independent of both, **any issue carrying the convention's sentinel status comment** (``). The two signals cover each other: a config pinned to an operator-titled issue defeats the title test, and an issue pinned but not yet written to carries no sentinel, a title-only test admits exactly the first case and then relabels or closes the surface holding durable lane state. **Also exclude `work-map` container items**. Ordinary open issues carrying the tracker seam's container label (`WIT_CONTAINER_LABEL`, default `work-map`): they are never claimable frontier work (`list-frontier` drops them unconditionally per the seam contract) and their openness means the map exists, not that backlog is waiting. The exclusion never keys on labels either for telemetry (since the raw marker rides in as a creation-time filing default and a lane can re-add it at any cycle, so it holds **whatever labels they carry, the raw marker included**). A telemetry issue never enters the attention view, and one named explicitly is reported as lane infrastructure and stopped on, never state-machined, relabeled, or closed, since the lane reads that surface to operate. Container items are filtered from the attention view the same way. The lanes' own snapshots exclude the same populations by pointing here; it is defined here because this skill defines the intake population every lane composes. +- **Lane infrastructure is never intake.** The loop-lane convention's per-lane telemetry tracking issues, the surfaces holding that convention's sentinel-marked status comment, are lane infrastructure, not backlog: an open one is a lane operating. **Identify one the way the lane resolves its own telemetry home**, never by title alone: the issue the lane's launch config pins (`lanes[].telemetry.issue` in the `harness-ops` lane config, read from `/.work/lanes/lanes.json`, or from `/.work/lanes.json` when only that file exists), else the default `Lane telemetry: ` title (`/work-items:work-loop`, "Telemetry and durable loop state"); and, independent of both, **any issue carrying the convention's sentinel status comment** (``). The two signals cover each other: a config pinned to an operator-titled issue defeats the title test, and an issue pinned but not yet written to carries no sentinel, a title-only test admits exactly the first case and then relabels or closes the surface holding durable lane state. **Also exclude `work-map` container items**. Ordinary open issues carrying the tracker seam's container label (`WIT_CONTAINER_LABEL`, default `work-map`): they are never claimable frontier work (`list-frontier` drops them unconditionally per the seam contract) and their openness means the map exists, not that backlog is waiting. The exclusion never keys on labels either for telemetry (since the raw marker rides in as a creation-time filing default and a lane can re-add it at any cycle, so it holds **whatever labels they carry, the raw marker included**). A telemetry issue never enters the attention view, and one named explicitly is reported as lane infrastructure and stopped on, never state-machined, relabeled, or closed, since the lane reads that surface to operate. Container items are filtered from the attention view the same way. The lanes' own snapshots exclude the same populations by pointing here; it is defined here because this skill defines the intake population every lane composes. ## Triage states diff --git a/plugins/work-items/skills/triage/evals/evals.json b/plugins/work-items/skills/triage/evals/evals.json index 1d37f9aee0..e2b5300612 100644 --- a/plugins/work-items/skills/triage/evals/evals.json +++ b/plugins/work-items/skills/triage/evals/evals.json @@ -150,7 +150,7 @@ { "id": 12, "name": "triage-excludes-a-config-pinned-telemetry-issue-with-a-non-default-title", - "prompt": "/work-items:triage — `.work/lanes/lanes.json` pins `lanes[0] = {\"name\": \"work\", \"telemetry\": {\"issue\": 412}}`, and open issue #412 is titled `Worker lane state (do not close)`, carries `needs-triage` and one comment opening ``; there is also one ordinary unlabeled bug report", + "prompt": "/work-items:triage — `.work/lanes/lanes.json` pins `lanes[0] = {\"name\": \"work\", \"telemetry\": {\"issue\": 412}}`, and open issue #412 is titled `Worker lane state (do not close)`, carries `needs-triage` and one comment opening ``; there is also one ordinary unlabeled bug report", "expected_output": "The attention view lists only the ordinary bug report. #412 is excluded as lane infrastructure even though its title does not match `Lane telemetry: `, on either signal available: the launch config pins it as the `work` lane's telemetry home, and it carries the convention's sentinel status comment. It is not relabeled, not commented on, and not closed.", "files": [], "expectations": [ diff --git a/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md b/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md index 12c7bfc79a..085782bf4c 100644 --- a/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md +++ b/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md @@ -34,7 +34,7 @@ bash "" \ ## What the upsert writes The marker is `work-items:work-loop@` and the comment's first line is the sentinel -``, an HTML comment that is invisible when +``, an HTML comment that is invisible when rendered and distinct per writer, so sibling instances each own one comment on the same issue. The lookup is a `startswith` match on that full sentinel, so a body that merely quotes a sibling's sentinel is never adopted. Where the lookup finds nothing the script creates the comment; where it diff --git a/scripts/validate-plugin-contracts.mjs b/scripts/validate-plugin-contracts.mjs index 3ac29231f8..00a12d23a9 100755 --- a/scripts/validate-plugin-contracts.mjs +++ b/scripts/validate-plugin-contracts.mjs @@ -856,26 +856,6 @@ if (retirementsBaseRef === null) { ); continue; } - // A plugin renamed through the marketplace `renames` map carries its records to the new - // directory, with the id prefix that names the plugin rewritten to match. - const oldName = line.split("/")[1]; - // Follow the chain to its end; `claude plugin validate` rejects a cyclic map. - const renames = existsSync(marketplacePath) - ? (JSON.parse(read(marketplacePath)).renames ?? {}) - : {}; - let newName = renames[oldName]; - for (let hop = 0; typeof renames[newName] === "string" && hop < 32; hop++) { - newName = renames[newName]; - } - if (typeof newName === "string") { - for (const r of records) { - if (r.fields.id?.startsWith(`${oldName}-r`)) { - r.fields.id = newName + r.fields.id.slice(oldName.length); - } - } - retirementsAtBase.set(`plugins/${newName}/${RETIREMENTS_FILE}`, records); - continue; - } retirementsAtBase.set(line, records); } }