* ci: audit closed-issue delivery claims — cited PRs must actually be merged
Follow-up to #3223: an issue closed as 'fixed in #N' while #N was closed
without merging (or does not exist) leaves a completion claim that does not
hold. The weekly maintenance workflow now runs a read-only audit over issues
closed in the last 30 days, extracts delivery-claim PR references from issue
bodies and close-window comments, verifies each cited PR's merge state, and
reports findings to the step summary.
Design constraints:
- Read-only: no comments, no closes, no labels; findings are advisory and
the run always stays green so the weekly audit never blocks maintenance.
- Unverifiable counts as unmerged: a 404 PR or an API failure fails the
verdict, because an unverifiable claim is exactly the failure mode.
- Pure decision functions are exported for tests; a first live run against
this repository surfaced one real finding (#3408 cites #3060, which does
not resolve), included here as the motivating sample.
8 unit/contract tests: extraction, windowing (close comments included),
the #3223 unmerged-citation failure mode, the all-clear summary, workflow
contract, and a no-write guard.
* ci: rework audit evidence contract — claims vs context, four PR states, visible completeness
Revision per the #3522 review (reviewer ran the CLI through 19 controlled
cases plus a bounded live check of #3408):
- Bind findings to asserted delivery claims. Closing wording ('Delivered
through #N'), claim verbs (fixed/shipped/landed/resolved/...), and bare
'PR #N' inside closing comments are claims; historical mentions, quoted
lines, fenced examples, negated claims, and tracking-only references are
reported as contextual references and never produce findings. On the live
#3408 the audit now recognizes the closing claim #3500 (merged) instead of
flagging the historical #3060 text.
- Separate observed states: merged / open-unmerged / closed-unmerged /
unknown (404, rate limit, transient). Unknown claims are advisory manual-
verification items, not failed claims; each row carries the claim snippet,
its source link, and the observation time. Checked claims are listed even
when passing, so the report is traceable rather than an opaque all-clear.
- Paginate issue and comment collection (--paginate --slurp), bound comment
requests (200) and PR checks (100), and surface skipped pages, rate limits,
and retrieval failures as an explicit incomplete audit instead of an empty
all-clear. A live-run maxBuffer overflow on the paginated issue list was
caught and fixed (64MB); the audit reported it as incomplete before the fix.
- Scope the job to read access: job-level contents/issues/pull-requests read.
13 contract/unit tests including a CLI fixture (stub gh) that places the
relevant issue on page two and forces a comment-fetch failure.
* ci: report incomplete audit at PR check cap
---------
Co-authored-by: chenhz01 <chenhz01@users.noreply.github.com>
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
Prettier's config searcher also reads .prettierrc.json5, .prettierrc.ts/.mts/.cts and prettier.config.ts/.mts/.cts. resolve-formatter knew none of them, so a project configured only through prettier.config.ts was never formatted by post-edit-format, quality-gate or the Stop batch. config-protection's rc pattern stopped at .yaml/.toml, so an existing .prettierrc.ts or .prettierrc.json5 could be edited freely.
The Stop typecheck reported every tsc line that contained the edited path anywhere. A root index.ts has the relative path `index.ts`, which every `src/index.ts(...)` diagnostic contains, so a clean root file was reported with the errors of src/index.ts. The same happened to src/utils.ts with web/src/utils.ts, and to any file named in another file's message.
Compare the path that starts the diagnostic (`path(line,col): `), keeping the slash normalization for Windows.
With diff.relative=true, `git diff --name-only` reports paths relative to the working directory, so joining them to the repository root pointed at files that do not exist, and a session in a subdirectory again got no console.log warning. getGitModifiedFiles now runs `git -c diff.relative=false diff --name-only HEAD`. The -c form also works on Git versions older than --no-relative.
readToken dropped every backslash inside double quotes, while bash drops it only before $, `, ", \\ or a newline. `"echo ready\;"` therefore reached the tmux check as `echo ready;`, read as a command separator, and `tmux send-keys -t dev "echo ready\;" "npm run dev" Enter` was blocked although it types into a pane.
check-console-log took the paths from `git diff --name-only HEAD`, which are relative to the repository root, and checked them with fs.existsSync from the hook's working directory. In a session started in a subdirectory, such as a monorepo package, no path existed from there, so the hook never warned. Resolve each path from the repository root. The exclusion patterns and the warning still use the root-relative path.
tmux also separates commands at a `;` that ends an argument, so `new-session -d 'npm run dev;' run-shell 'npm run dev'` runs the second dev server without a pane. Split there as well. An escaped `\;` at the end of an argument stays literal, as in tmux.
Two forms got the pane exemption although they run the dev server without a pane.
`tmux -vL new-session run-shell ...`: in a cluster, the last option that takes a value (f, L, S, T) takes the next token, here the socket name `new-session`. A `c` anywhere in a cluster (`-c'npm run dev'`) runs a shell command without a pane.
`tmux new-session -d \; run-shell "npm run dev"`: only the first command of a sequence was checked. The sequence is now split at `;`, and it is exempt only when every command in it that runs the dev server is a pane command. This also closes the same hole for the plain `tmux new-session ... \; run-shell`, which the old regex exempted.
The block allowed a dev server only when the segment began with `tmux new`, `new-session`, `new-window` or `split-window`. It therefore blocked dev servers that do start in a tmux pane, the way its own message asks: `tmux -L work new-session ...` and `tmux -f conf new-session ...` (tmux options before the command), `/opt/homebrew/bin/tmux new-session ...` and `env TERM=... tmux new-session ...`, the aliases `neww` and `splitw`, and `tmux send-keys -t dev 'npm run dev' Enter` or `respawn-pane` for a session that exists.
Read the tmux command from the parsed segment instead, after the leading command word and tmux's own options, and allow the commands that run their shell command in a pane. `tmux run-shell` and `tmux -c` run it without one and stay blocked.
A .gitignore rule outranks .git/info/exclude, so a source project that ships !FORK_REPORT.md made git add -A stage the report. Remove both reports from the index after staging, in the forker's first commit and in every fold-in amend. The history test now runs both documented blocks against a negating .gitignore, prints the Passed/Failed summary for run-all.js, and skips on Windows like the publish-guard test.
(cherry picked from commit f493d9ff66)
The forker makes the only commit before the sanitizer and packager run,
and Step 7 publishes with `gh repo create --source=. --push`, which sends
commits, not the working tree. Packager output (LICENSE, README, CLAUDE.md,
setup.sh) never reached the public repo, and a secret the sanitizer caught
and the agent fixed in the working tree was still pushed in that commit.
- Fold sanitizer fixes and packager output into the single commit with
`git commit --amend`, keeping FORK_REPORT.md and SANITIZATION_REPORT.md
out through .git/info/exclude.
- Re-run the sanitizer after packaging (Step 5b) and refuse to push with
uncommitted changes or more than one commit.
- Sanitizer Step 6 now runs the Step 1 secret patterns and Step 3 home
path patterns over `git log -p --all`, not only a 4-keyword grep.
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit fe767e2099)
The "resolve output to absolute path" step only rebuilt
"$OUTPUT_DIR/$(basename ...)", so ./slides.pdf stayed relative. The
script then cd's into a temp dir, Playwright writes the PDF there, and
the temp dir is deleted. The script prints "PDF exported successfully!"
and then fails on du under set -e, with no PDF produced. This is the
script's own documented example.
Resolve the output directory with cd + pwd, as the input path already
does, and add a stubbed regression test (no network or browser).
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit f91c14749e)
- SKILL.md invoked every script through ~/.claude/skills/ck, which does
not exist when ck is installed as part of the ECC plugin, so every
/ck:* command failed with MODULE_NOT_FOUND. Use ${CLAUDE_SKILL_DIR}
and tell non-Claude hosts to substitute the skill directory. Scripts
still run from the project directory, since they use it as the
project path.
- The documented SessionStart command quoted "~/...", which the shell
never expands. Use $HOME and explain the plugin-install case.
- /ck:init and /ck:save piped JSON through echo '<json>'. Any apostrophe
ends the quoted string, and init JSON carries README/package.json
text. Pass it through a quoted heredoc instead.
- session-start.mjs and shared.mjs resolve SKILL.md next to the code
(import.meta.url), and the hook expands ${CLAUDE_SKILL_DIR} in the
text it injects.
- encodeProjectPath only replaced '/', while Claude Code maps every
non-alphanumeric character to '-'. Memory entries for projects whose
path has a space or dot went to a directory Claude Code never reads.
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit c920005c2b)
With no project memory directory the hook cannot check the learning libraries, so a complex task is warned, not blocked. Say so in the blocking-conditions paragraph and in Limitations, as CodeRabbit pointed out on #3575. Disk checks still apply.
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit 353cfa2b57)
quality-gate.py never read stop_hook_active, so a condition the agent
cannot fix (disk below 15 GB, an unwritable growth-log) blocked every
Stop and kept the session generating turns. Exit 0 when Claude Code
reports that the Stop hook already forced a continuation.
The memory dir lookup only mapped '/', '\' and ':' to '-'. Claude Code
maps every non-alphanumeric character, so for any project path with a
space the memory dir was never found and enforcement silently became
warn-only.
Align SKILL.md with the code: disk thresholds (remind 50 / warn 30 /
block 15), the disk block applies to every session, the learning-lib
rule and its examples, stderr messages, and the hook timeout, which
Claude Code reads in seconds (5000 meant 83 minutes).
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit a1385c4397)
apply creates real posts, but the workflow went straight from validate
to apply. Require showing every post and getting explicit approval
first. Also note that the CLI only takes the API key as an argument, so
the user should run login themselves instead of the agent.
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit 93dce707f1)
ECC ships no /safety-guard command, no PreToolUse hook for it and
nothing that writes ~/.claude/safety-guard.log. Describe the modes as
rules the agent follows and point to permission deny rules or hooks for
enforced blocking.
Signed-off-by: Wen <jwcastillo@gmail.com>
(cherry picked from commit b23d73b182)