Five additional review findings on top of the round-1 tokenizer fix.
Combined patch surface is small (one push branch, new switch branch,
exploded subshell handling); all six review issues are now closed.
P1 — --force --force-if-includes still destructive (Greptile, line 217):
Previous logic treated --force-if-includes as a safety guarantee
alongside --force-with-lease. Per git-scm.com/docs/git-push,
--force-if-includes is a no-op WITHOUT --force-with-lease, so a
combination of --force --force-if-includes is just --force. Push
branch now treats only --force-with-lease as a lease, and reports
force when --force / -f is present.
P2 — git switch destructive forms not detected (Greptile, line 234):
Added a switch branch to isDestructiveGit covering:
--discard-changes (explicit discard)
--force / -f (ignore conflicts, overwrite)
-C <branch> (force-create, overwrites existing branch)
P0 — backtick + $(...) subshell bypass (CodeRabbit, line 64):
Added explodeSubshells() that promotes `...` and $(...) contents
to top-level segment separators. Run on both the SQL/dd regex
input and the per-segment shell tokenizer input. Loops up to 4
passes to catch a layer of nesting. Without this,
`echo y | $(rm -rf /tmp)` slipped past the segment splitter
because the destructive command lived inside a sub-expression.
P0 — +refspec force push (CodeRabbit, line 217):
`git push origin +main`, `+refs/heads/main:refs/heads/main`, etc.
force a non-fast-forward update of that specific ref. Push branch
now also flags any positional arg starting with `+` that matches
a refspec shape. Excludes bare `+` and numeric-only tokens.
P2 — missing --force --force-if-includes regression test
(Greptile, line 1202): added.
Tests (+10 on top of the round-1 +10):
Bypass-now-blocked:
- git push --force --force-if-includes (force-if-includes is no-op
without lease — bare force is still in effect)
- git push origin +main (+refspec bare branch)
- git push origin +refs/heads/main:refs/heads/main (+refspec full)
- git switch --discard-changes
- git switch --force
- git switch -f (short form)
- git switch -C (force-create)
- echo y | `rm -rf /tmp` (backtick subshell)
- echo y | $(rm -rf /tmp) (dollar-paren subshell)
Still-allowed:
- git switch feature (plain)
67/67 in gateguard-fact-force.test.js. 2380/2380 across the full
suite. yarn lint clean. All seven CI validators pass.
Refs #1843.
Six classes of bypass in scripts/hooks/gateguard-fact-force.js
DESTRUCTIVE_BASH regex, plus a separate false-positive class.
Same shape of issue as the block-no-verify holes addressed in
#1843: a single-regex shell parser can never cover the flag-order
variations git and rm allow.
Real bypasses observed locally (all ALLOW today, should BLOCK):
git push -f origin main (short form of --force)
git -c core.foo=bar reset --hard (intervening -c global)
rm -fr /tmp/junk (reverse flag order)
rm -r -f /tmp/junk (split flag form)
git reset HEAD --hard (intervening ref token)
git clean -fd (combined -f + -d flag)
False positive observed locally (BLOCK today, should ALLOW):
git commit -m "fix: rm -rf race in worker" (destructive phrase
inside quoted message)
Behavior fix that comes along: --force-if-includes is now exempted
alongside --force-with-lease. Both are safety-checked variants;
the previous regex used a negative lookahead that only spelled out
--with-lease, so --force-if-includes blocked under the old code
even though it is the safer-not-harder choice.
Fix shape (mirrors block-no-verify #1843):
- DESTRUCTIVE_SQL_DD regex kept for `drop table`, `delete from`,
`truncate`, `dd if=` — these are stable keyword phrases. Quoted
strings are stripped before the regex runs so the phrase is not
matched inside a commit message body.
- isDestructiveBash() tokenizes the command into segments at
unquoted ; | & boundaries, then per segment:
* isDestructiveRm — detects `rm` with both r and f set
across combined or split flag tokens.
* isDestructiveGit — finds the git subcommand after skipping
global options (-c key=val, -C path, --git-dir=, etc.),
then handles reset, checkout --, clean -f*, push --force
(with --force-with-lease / --force-if-includes exemption),
commit --amend, and rm -r* preservation.
- Command tokens go through commandBasename() so /usr/bin/rm,
rm.exe, and RM all normalize to "rm".
Tests (+10 in tests/hooks/gateguard-fact-force.test.js):
Bypass-now-blocked (7):
- denies short-form git push -f
- denies git reset --hard with intervening -c global option
- denies rm -fr (reverse flag order)
- denies rm -r -f (split flag form)
- denies git reset HEAD --hard
- denies git clean -fd
- denies destructive command in second chained segment
False-positive-now-allowed (3):
- allows destructive phrase inside `-m` commit message (rm -rf)
- allows SQL phrase inside `-m` commit message (drop table)
- allows --force-if-includes as a safety-checked variant
Local verification:
yarn lint clean
scripts/ci/validate-* (agents/commands/rules/skills/hooks/
install-manifests/no-personal-paths) pass
node tests/run-all.js 2380/2380 pass
Caveat (unrelated): yarn test still fails at check-unicode-safety
on skills/windows-desktop-e2e/SKILL.md (U+2605) per #1843's
caveat — independent of this change.
Provenance: discovered during a security pass on ECC after PR
#1843 (block-no-verify shell-words rewrite) landed. Same class of
regex-based shell parser issue, same shape of fix.
Refs #1843.
Make the ECC 2.0 GitHub/Linear/handoff/roadmap progress-sync model part of the local observability readiness gate instead of leaving it as roadmap prose only.
- add `docs/architecture/progress-sync-contract.md` for GitHub, Linear, handoff, roadmap, and work-items sync
- add a `Tracker Sync` check to `scripts/observability-readiness.js`
- update observability tests with passing and missing-contract coverage
- update observability and GA roadmap docs so the local readiness gate is now 18/18 and records #1848 supply-chain hardening evidence
Validation:
- node tests/scripts/observability-readiness.test.js (9 passed, 0 failed)
- npm run observability:ready -- --format json (18/18, ready true)
- npx markdownlint-cli 'docs/architecture/progress-sync-contract.md' 'docs/architecture/observability-readiness.md' 'docs/ECC-2.0-GA-ROADMAP.md'
- git diff --check
- node tests/docs/ecc2-release-surface.test.js (18 passed)
- node tests/run-all.js (2378 passed, 0 failed)
- GitHub CI for #1849 green across Ubuntu, Windows, and macOS
No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
Add a repo-level supply-chain incident response playbook for npm/GitHub Actions package-registry incidents, anchored on the May 2026 TanStack compromise and prior Shai-Hulud-style npm incidents.
- add `docs/security/supply-chain-incident-response.md` with exposure checks, immediate response steps, workflow rules, publication rules, and escalation triggers
- link the playbook from `SECURITY.md`
- reject `pull_request_target` workflows that restore or save shared dependency caches
- add a regression test for the new `pull_request_target + actions/cache` guardrail
Validation:
- node tests/ci/validate-workflow-security.test.js (12 passed, 0 failed)
- node scripts/ci/validate-workflow-security.js (validated 7 workflow files)
- npx markdownlint-cli 'SECURITY.md' 'docs/security/supply-chain-incident-response.md'
- npx markdownlint-cli '**/*.md' --ignore node_modules
- git diff --check
- node tests/run-all.js (2377 passed, 0 failed)
- GitHub CI for #1848 green across Ubuntu, Windows, and macOS
No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
Require npm registry signature verification wherever workflow npm audit checks run.
- add npm audit signatures to CI Security Scan and maintenance security audit jobs
- teach the workflow security validator to reject npm audit without signature verification
- keep the repair and Copilot prompt tests portable across Windows path/case and CRLF frontmatter behavior
Validation:
- node tests/run-all.js (2376 passed, 0 failed)
- CI current-head matrix green on #1846
Adds GitHub Copilot VS Code instruction and prompt files for ECC workflows, with VS Code prompt frontmatter/settings aligned to current docs and tests covering the surface.
Co-authored-by: Girish Kanjiyani <girish.kanjiyani5040@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Backport Jamkris's fix for case-insensitive core.hooksPath overrides and the git commit -tn template-path false positive. Verified locally on current main with 25/25 block-no-verify tests and node tests/run-all.js passing 2369/2369.
- run non-test workflow installs with npm ci --ignore-scripts where lifecycle scripts are not needed\n- reject plain npm ci in workflows with write permissions\n- reject actions/cache in id-token: write workflows to reduce OIDC publish cache-poisoning risk
Salvages the useful statusline/context monitor work from stale PR #1504 while preserving the current continuous-learning hook runner wiring.
Adds the metrics bridge, context monitor, statusline script, shared cost/session bridge utilities, and tests. Fixes the reviewed false loop-detection hash collision for non-file tools, avoids default-session cost inflation, sanitizes statusline task lookup, and records hook payload session IDs in cost-tracker.
- add Vite and Redis pattern skills from closed stale PRs
- add frontend-slides support assets
- port skill-comply runner fixes and LLM prompt/provider regressions
- harden agent frontmatter validation and sync catalog counts
* fix(ci): flag SKILL.md frontmatter defects in validate-skills
Issue #1663 reported two SKILL.md frontmatter defects (missing `name:`
on skill-stocktake; literal block-scalar `description: |-` on
openclaw-persona-forge) that PR #1664 addresses at the data level.
This change is complementary: it extends `scripts/ci/validate-skills.js`
to catch the same class of defect statically going forward, so the
frontmatter-vs-renderer problems do not silently reappear as new skills
land.
## Checks added
- Frontmatter must declare a `name:` field.
- Frontmatter `description:` must not use a literal block scalar
(`|` / `|-` / `|+`) — these preserve internal newlines and break
flat-table renderers keyed off `description`. Folded (`>`) and inline
strings are accepted.
## Behavior
- Frontmatter findings default to WARN (exit 0) so this PR does not
break CI while the two known offenders are still on main. Pass
`--strict` or set `CI_STRICT_SKILLS=1` to promote them to ERROR
(exit 1). Structural findings (missing / empty SKILL.md) remain
errors as before.
- Today against main, the validator reports exactly two warnings —
the same two files called out in #1663 — and exits 0. When #1664
lands, the validator reports zero warnings, at which point strict
mode can be enabled in CI.
## Parser notes
- Bespoke frontmatter parser mirrors the style of `validate-agents.js`
(tolerant of UTF-8 BOM and CRLF; no new npm dependency).
- Block-scalar continuation lines are skipped so keys inside a block
scalar are not mistaken for top-level keys.
- Hidden directories (`.something/`) under skills/ are now skipped.
## Tests
Adds five focused tests to `tests/ci/validators.test.js`:
- warns when frontmatter is missing `name` (default mode)
- errors when frontmatter is missing `name` (--strict mode)
- warns on literal block-scalar description (|-)
- accepts folded (>) and inline descriptions under --strict
- skips hidden directories under skills/
## Docs
Adds two bullets to the `Skill Checklist` in CONTRIBUTING.md covering
the two rules now surfaced by the validator.
Refs #1663. Complements (does not compete with) #1664.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ci): harden SKILL.md frontmatter checks after bot review
Address findings from CodeRabbit, Greptile, and cubic on #1669:
- Guard empty or whitespace-only `name:` values. Previously
`name: ` silently passed because the presence check only
tested key-set membership; now inspectFrontmatter captures
trimmed values and validate flags an explicit 'name is empty'
WARN/ERROR.
- Broaden block-scalar detection to cover YAML 1.2 indent
indicators (`|2`, `|-2`, `>2-`) and trailing comments
(`|- # note`). The old regex required a bare `|`/`>` with
optional `+`/`-`, which let valid-but-disallowed forms slip
through.
- Update CONTRIBUTING.md checklist to list `|+` alongside `|`
and `|-` for parity with the validator.
- Extend runSkillsValidator to accept env overrides and add four
regression tests: empty name, |+ description, |-2 + comment, and
CI_STRICT_SKILLS=1.
* fix(ci): address round-2 review on validate-skills frontmatter
- Tighten extractFrontmatter closing delimiter to require a newline or
end-of-file after the closing `---`, so body lines beginning with
`---text` are not parsed as frontmatter (CodeRabbit).
- Strip both trailing and comment-only values in inspectFrontmatter, so
`name: # todo` is surfaced as empty rather than silently passing
(cubic P2).
- Extract validateSkillDir helper so the per-directory validation
block moves out of validateSkills, keeping both functions under the
50-line guideline (CodeRabbit nit).
- Hoist runSkillsValidator to module scope in the test harness and
share the spawnSync import with execFileSync so the helper stops
re-requiring child_process on every invocation (CodeRabbit nit).
- Add regression tests: comment-only `name:` values must fail strict
mode; `---trailing` body lines must not be parsed as frontmatter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update tests/ci/validators.test.js
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* fix(hooks): resolve MCP health-check spawn ENOENT on Windows
On Windows, commands like 'npx' are batch files (npx.cmd) that require
shell expansion to resolve via PATH. Without shell: true, Node.js
spawn() fails with ENOENT.
However, absolute paths (e.g. C:\Program Files\nodejs\node.exe) must
NOT use shell mode because cmd.exe misparses paths containing spaces.
Fix: enable shell mode only for non-absolute commands on Windows, using
path.isAbsolute() to distinguish. This matches how attemptReconnect()
already handles the shell option.
Fixes#1455
* fix(hooks): harden Windows shell spawn — validate command for metacharacters
Addresses bot review feedback on PR #1456:
- Add UNSAFE_SHELL_CHARS regex to guard against shell injection when
needsShell=true: cmd.exe operators (&, |, <, >, ^, %, !, (), ;,
whitespace) are rejected before shell mode is enabled
- Add typeof command === 'string' check so path.isAbsolute() cannot
throw on malformed non-string command values
- Rename test to 'via PATH resolution' (not Windows-only; runs all platforms)
- Fix misleading test comment: 'node' resolves via PATH like npx.cmd but
does not itself use .cmd; comment now accurately reflects the intent
* fix(hooks): kill full process tree on Windows when shell mode is used
When needsShell=true, the spawned child is cmd.exe. Calling child.kill()
only terminates the shell, leaving the real server process orphaned.
Use taskkill /PID <pid> /T /F on Windows+shell to kill the entire
process tree rooted at cmd.exe. Fall back to SIGTERM+SIGKILL on all
other platforms or when shell mode is not active.
* fix(hooks): fall back to child.kill() when taskkill fails
Windows taskkill can fail if it's not on PATH, the process already
exited, or permissions are denied. Previously the failure was silently
ignored and no kill signal reached the child.
Now: capture the spawnSync result and fall back to child.kill('SIGKILL')
on any taskkill error or non-zero status. This still may leak a
detached server process but at least guarantees the cmd.exe shell is
signaled.