Commit Graph
1086 Commits
Author SHA1 Message Date
bchanot 64ca0f8e09 docs(skills): invalid autopush value is fail-closed everywhere; prose aligned
Run D3 of manual-push mode (BDR-114). With every reader now failing
closed, the skill prose stops saying the lib and hooks still push on an
invalid value:

- capitalize STEP 5C / STEP 6: the invalid outcome is split on the ahead
  count (nothing pushed vs pushed anyway by a stale fail-open hook or a
  manual push); the verb's stderr line is quoted verbatim; neighbouring
  closing lines carry push-mode qualifiers so none shadows the invalid
  case; the --no-push lines follow the same rule.
- client-handover: "COMMIT + PUSH" labels become "COMMIT + PUSH STATE
  READ"; the STEP 5 residual sentences no longer imply the pipeline
  pushes; the invalid value is named as a case where the user pushes.
- release-executor: prep span checks the version format by reading the
  string (never in a Bash command); manual mode and an invalid value
  both leave main/develop local.
2026-10-07 17:11:58 +02:00
bchanot 3c59333fcf fix(push-guard): single reader, whole-word dir tokens, payload fallback, bad-value banner
Run D2 of manual-push mode (BDR-114).

- push-guard sources lib/gitflow.sh once (absolute path) and reads each
  candidate dir through gitflow_push_mode; a missing lib denies.
- Dir tokens are extracted as whole shell words: a fully quoted token
  (inner apostrophe allowed) is resolved, a backslash-escaped space is
  unescaped deterministically, a token mixing quoted and unquoted parts
  is refused (fail closed) instead of resolving to its parent.
- A payload jq cannot parse is scanned as raw text with its JSON escapes
  folded; a push-looking one gets the static deny through the trap.
- The 20-token cap runs before any per-token classification (a flood of
  20 000 tokens is refused in 0.13 s; T58 locks it under 5 s).
- `case "$mode"` has a deny default; missing core tools warn and allow.
- T42 compares the deny list against main (the last release) instead of
  HEAD; literal-true, mixed-token, broken-payload, lib-missing and
  banner-on-bad-value cases added (98 checks).
- session-start banner reads the mode through the verb and shows
  `push : manual (autopush bad)` on an unparseable value.
- tour hints quote "<abs project>".
2026-10-07 17:11:56 +02:00
bchanot 472cccbc52 fix(gitflow): every autopush reader fails closed and names an invalid value
Run D1 of manual-push mode (BDR-114). `git config --bool --default true
gitflow.autopush` only covered a MISSING key: an unparseable value made
git die with empty output, the `= false` test failed, and every push ran
again. A typo on a work machine silently re-enabled the pushes it was
meant to stop.

- lib/gitflow.sh: `_gitflow_push_off` reads the mode through the lib
  verb (`push-mode`); anything but `auto` is push-off, and the verb's
  stderr line names an invalid value during start/finish.
- Emitted post-commit/post-merge hooks (POSIX sh, standalone): push only
  when the key reads `true` or is unset; `false` exits quietly; any
  other result prints one stderr line ("NOT pushed, treated as manual
  push mode") and exits 0. Mirrors gitflow_push_mode.
- .githooks/ and githooks/ regenerated files-only through `emit-hook`
  (no config read or write; .git/config hash unchanged).
- hooks/unpushed-guard.sh: mode from the lib verb (absolute lib path
  resolved before any cd, no temp file); anything but auto is manual;
  the SessionStart line names an invalid or unreadable value.
- Tests: gitflow-test T18q block (invalid → start, hook and finish push
  nothing and say so; `true` → the hook pushes; emitted hook is
  POSIX-clean), unpushed-guard T14 rewritten.
2026-10-07 16:45:31 +02:00
bchanot e4bc6212ef docs(gitflow): run C — push-mode verb, skills never push; CHANGELOG, SETTINGS, gitflow skill, README, USAGE 2026-10-07 14:48:27 +02:00
bchanot 0b08ceda97 chore(memory): BDR-113 + LRN-197..199 + journal — feat manual-push-mode run C 2026-10-07 14:48:07 +02:00
bchanot 3881f462c6 fix(gitflow): run C polish — 5C coherence, sanitized verb stderr, hermetic suite
Closes the non-gap observations the gates left on runs C1/C2:

- capitalize STEP 5C/6: heading no longer says "+ push"; the --no-push
  fact read is its own paragraph and scoped to that path; the
  auto-persisted line requires finish rc 0 AND ahead = 0; rc 5/2/6
  (merged, branch not deleted) still report the push state; the
  "not on origin" line carries the once-a-remote-exists hint.
- gitflow.sh push-mode: the raw config value echoed on stderr is reduced
  to printable characters (LC_ALL=C, BSD tr safe) and capped at 64.
- gitflow-test.sh exports the hermetic git config env in the file, so a
  bare run on a global-manual machine stays green.
- client-handover-writer: the branch allowlist refuses a leading dash.
2026-10-07 14:35:08 +02:00
bchanot 6104545e76 feat(skills): push state read from facts, never pushed by the skills
Run C2 of manual-push mode (BDR-111/BDR-112). The four flows that pushed
on their own, or claimed the branch was on origin, now read the truth
after the fact and hand the user the exact command:

- client-handover-writer: the "Push to origin now?" question and its
  push block are gone (the hooks had already pushed in auto-push mode;
  push-guard denies it in manual mode). A reusable PUSH STATE READ
  (branch, origin probe, `git rev-list --count origin/<br>..<br>`, the
  verb only to word the reason) runs after commit-change, at the top of
  the deploy pause, after "Deployed" and before each end report. The
  branch name is validated against an allowlist before it is placed in
  any command or hint (a hostile branch name is otherwise a shell
  injection). Pending → the user pushes BEFORE the deploy pause; the
  deploy brief says "after your push". `Push:` line in both reports.
- release-candidate STEP 6: two ahead counts + the verb; anything other
  than auto with both counts 0 prints one user command
  `! git push --atomic origin main develop v<X.Y.Z>` and stops; the tag
  gate stays for auto mode; `hold` notes --follow-tags; version regex.
- release-executor: push claims qualified (auto-push mode, best effort).
- tour: mode-agnostic rule; STEP 3 reads one `git -C <project>` fact per
  project (suffix-aware branch, --remotes=origin, origin probe) and the
  summary row says on origin / local only with the user command.
2026-10-07 14:02:51 +02:00
bchanot 5cf049d235 feat(gitflow): push-mode verb; /close reports the push state instead of pushing
Run C1 of manual-push mode (BDR-111/BDR-112).

- lib/gitflow.sh: `gitflow.sh push-mode` prints auto | manual | invalid
  (rc 0; an invalid value is named on stderr). It is the one reader a
  skill may call: the bare `git config … gitflow.*` read is denied to
  Claude since run B. Ignores GITFLOW_NO_PUSH by design (documented).
- skills/capitalize/SKILL.md STEP 5C: the explicit `git push origin
  develop` is gone — `finish` has pushed develop itself since BDR-095,
  mode-aware since run A. 5C is now three separate read-only calls
  (finish; push-mode; `git rev-list --count origin/develop..develop`)
  and prose outcomes keyed on the real ahead count: pushed / manual push
  mode, you push / not on origin / push FAILED / invalid value named,
  plus a finish-failure outcome (merge vs delete rc distinguished).
  STEP 6 closing lines and the recap carry every outcome; the
  `--no-push` line reads the branch's own ahead count ("this disk only"
  only when true). Invariant: no `git push` inside any Bash call; the
  user hints are prose.
- skills/close/SKILL.md, lib/gitflow-aiguillage.md: "push" claims
  qualified "in auto-push mode".
- lib/gitflow-test.sh T11b: six cases for the verb (default, true,
  false, non-boolean with stderr + rc 0, corrupt config, usage).

Polish items from the gates are listed in TODO.md (C1 polish).
2026-10-07 13:39:01 +02:00
bchanot 472168d432 docs(gitflow): push-guard — SETTINGS push discipline + guardrails table, gitflow skill, ARCHITECTURE, README, CHANGELOG 2026-10-07 12:42:15 +02:00
bchanot 4630b625f7 chore(memory): BDR-112 + LRN-194..196 + journal — feat manual-push-guard run B 2026-10-07 12:41:46 +02:00
bchanot 6468eda495 fix(push-guard): fail closed on token floods, git failures and quoted cd targets
Hardening after the security gate on a2ac018 (3 MEDIUM, all closed and
re-measured):

- dir tokens are deduplicated and capped: more than 20 distinct cd/-C
  targets in one command denies before any git fork (20000 tokens: 0.15 s
  against the 10 s hook timeout that used to turn a flood into an allow)
- a git or cd failure while reading gitflow.autopush denies instead of
  reading as auto (git absent, usage error, unenterable dir); the key
  being unset is the only "auto" answer; the decision is recorded only
  after one candidate was evaluated cleanly, else the EXIT trap denies
- cd/pushd/-C targets that follow a quote or backtick (bash -c '…') are
  extracted; quote characters are excluded from unquoted tokens

User decision (contract, gated): both fail-closed cases also fire in
auto mode on such pathological commands; silence in auto mode holds for
every ordinary push. Header limits list the residual misses (quotes or
backslashes inside a token, cumulative relative cd, unparseable payload)
backed by the soft_deny rule. Tests: 71 checks (T48–T50b added).
2026-10-07 12:34:56 +02:00
bchanot a2ac0189f7 feat(gitflow): push-guard hook denies Claude's git push in manual-push mode
Run B of manual-push mode (BDR-111). With `gitflow.autopush false`
nothing stops Claude from typing `git push` itself: the `ask` tier is
inert under auto mode. This adds the mechanical block the user chose.

- hooks/push-guard.sh (PreToolUse, matcher Bash|Monitor, timeout 10):
  detects a push in the command text (strict, quote-stripped loose and
  alias patterns; backslash-newline folded in bash, BSD sed/grep only),
  reads gitflow.autopush in the payload cwd and in every literal -C/cd
  dir the command names (global config counts outside a repo), denies
  with the documented JSON form and a reason that tells the user to run
  the command with `!`. Unparseable value = manual (fail closed); once
  a push is detected an EXIT trap emits a static deny on any internal
  error. jq missing = one stderr warning, allow (sibling-hook policy).
- settings.json: hook wiring; 18 deny entries closing the write forms
  of the human-only toggle (any `git … config` spelling, section
  removal/rename, `-c`, config env overrides, direct edits of git config
  files); one soft_deny on pushing in manual mode in any form, with no
  per-turn clearance; the routing-around rule names hook refusals.
- hooks/session-start.sh: `🔒 push : manual (autopush=false) — ! git push`
  banner line when the key reads false (padding in bytes).
- lib/tests/push-guard.test.sh: 61 checks (push forms, over-blocks,
  invalid value, global key, fail-closed trap, no-jq, wiring, banner).

Known limits are listed in the hook header; the soft_deny rule is the
backstop. Run C (skills that push on their own) and run D (fail-closed
readers everywhere) follow. Do not enable manual mode at work before C.
2026-10-07 12:24:35 +02:00
bchanot 16c3dc8fbb chore(memory): BDR-111 + LRN-191..193 — feat manual-push-mode run A 2026-10-06 18:03:36 +02:00
bchanot afd6073371 docs(gitflow): manual-push mode — SETTINGS push discipline, gitflow skill rows, CHANGELOG 2026-10-06 18:03:35 +02:00
bchanot e6cccc1740 chore(memory): journal + contract/plan — feat manual-push-mode run A 2026-10-06 17:50:24 +02:00
bchanot 2fc88304ac feat(gitflow): manual-push mode honoured by the lib, quiet unpushed-guard
`gitflow.autopush false` (human-set git config) now means "nothing is
pushed" end to end, not only in the post-commit/post-merge hooks:

- lib/gitflow.sh: `_gitflow_push_off` is the single reader of
  GITFLOW_NO_PUSH / gitflow.autopush for the lib's push sites; `start`
  and `finish` stop pushing in manual mode. `gitflow_delete` checks out
  the base that contains the branch and drops a lagging upstream before
  `git branch -d` (LRN-161: `-d` judges against the upstream when set).
  Skipped remote deletes say `left in place`; `_gitflow_sync_base`
  replaces the silent `pull --ff-only || true` and warns when a base is
  behind origin and cannot fast-forward.
- hooks/unpushed-guard.sh: manual mode is silent at Stop and gives one
  `ℹ manual push mode:` line at SessionStart counting every local
  branch; an unparseable value is named and treated as auto.
- CLAUDE.global.md: manual-push mode doctrine, "ahead = defect" scoped
  to auto mode.
- Tests: gitflow-test T18m block (T18m0, T18i-T18o, 7 cases),
  unpushed-guard T10-T16.

Follow-ups (TODO.md): run B push-guard hook + settings deny widening +
banner; run C skills that push on their own (/close STEP 5C, …).
Do not enable manual mode on the work machine before B and C land.
2026-10-06 17:49:20 +02:00
bchanot fa67664bac chore(memory): journal — release 2.0.0 cut and tagged 2026-10-06 2026-10-06 16:15:10 +02:00
bchanot 4d81f5a8fc Merge release/2.0.0 into develop 2026-10-06 16:13:13 +02:00
bchanot 9ef66e239b chore(release): 2.0.0 — MIT LICENSE, README License section, Linux note reworded 2026-10-06 16:12:52 +02:00
bchanot b47bba7faf Merge develop into release/2.0.0 2026-10-06 16:07:22 +02:00
bchanot 370f35a5a1 Merge bugfix/macos-portability into develop 2026-10-06 16:06:32 +02:00
bchanot b3f3ae2441 chore(memory): doc-sync deferred items + journal 2026-10-06 2026-10-06 16:00:54 +02:00
bchanot c6fb2e4219 docs: global sync before 2.0.0 — components, slash table, profiles, GSD 3.0.0, migration guide, package-install guard 2026-10-06 16:00:41 +02:00
bchanot a84aaaabbd chore(memory): prune 2026-10-06 — BLK-028 merge, bounded caveman pass on 37 entries 2026-10-06 15:40:26 +02:00
bchanot 6bc7c12bf3 chore(memory): reconcile 2026-10-06 — npm deny items closed, Makefile help re-verified open 2026-10-06 15:24:59 +02:00
bchanot 276fa67c80 chore(memory): BDR-110 + BLK-026/027 + LRN-189/190 + journal — macOS portability bugfix, contract + plan 2026-10-06 15:19:53 +02:00
bchanot e5b6cc5ef4 docs(changelog): macOS portability fix + deferred Linux run under [Unreleased] 2026-10-06 15:19:22 +02:00
bchanot 0efdff0d55 fix(portability): make test green on macOS, GNU-only idioms replaced
The suite and seven libs assumed a GNU userland: `cmd | grep -q` under
pipefail (grep exits at the first match, the producer takes SIGPIPE,
rc 141 → 15 false "merged into" FAILs in gitflow-test), `sed -i` with no
suffix, `wc -l` padding compared as a string, `stat -c`, `touch -d`,
`realpath -m` (gstack-links refusal never fired), bare `timeout` off the
sanitized PATH (design gate READY BUT UNVERIFIED), `grep -oP` (update-all
emptied the plugin list). Thirteen suites were red on this machine.

Portable forms on the native userland of both OS: producer captured out
of the pipeline, `sed -i.bak` in tests and a temp-sibling `sed_profile`
on the user dotfile, `tr -d ' '`, python3 perms, `touch -t`, a
`realpath -m` emulation that refuses `..`, perl `alarm` for the 15 s
bound, `sed -n` token extraction. Regression tests: gstack-links T4b,
doctrine-citers Alphabet/Alpha flip, profile-set-managed T18 (failing CLI
no longer aborts `set`), new portability-census suite over the tracked
shell files. Linux run deferred (see TODO).
2026-10-06 15:10:20 +02:00
bchanot 9d421e4493 chore(release): 2.0.0 — version.txt + CHANGELOG 2026-10-06 12:12:02 +02:00
bastien 4258a092e8 chore(memory): journal + TODO — npm soft-deny merged 2026-09-30 2026-09-30 22:53:10 +02:00
bastien 95168c1d1e Merge chore/npm-global-soft-deny into develop 2026-09-30 22:53:09 +02:00
bastien 29f4dc7ab4 feat(settings): soft-deny global npm installs until the user names the package
The literal deny patterns miss spellings such as `npm i <pkg> -g`.
The classifier entry covers every form and asks for a vetting summary
(publisher, age, downloads, install scripts, advisories) first.
2026-09-30 19:23:27 +02:00
bastien 18c8960adc chore(memory): journal + TODO — higgsfield pack merged to develop 2026-09-30 2026-09-30 19:07:10 +02:00
bastien df6dbce774 Merge feature/higgsfield-pack into develop 2026-09-30 19:06:55 +02:00
bastien f39c5d3bf8 chore: purge transient planning artifacts (BDR-065) 2026-09-30 19:06:54 +02:00
bastien 776613a570 chore(memory): BDR-109 + LRN-183..188 + BLK-025 + EVAL-039 — ship-feature higgsfield pack 2026-09-30 18:19:34 +02:00
bastien 535022186c docs(plugin-advisor): never recommend the Higgsfield toggles from project signals 2026-09-30 18:17:32 +02:00
bastien 2560905be2 docs(toggle): higgsfield header line names the login too 2026-09-30 18:17:17 +02:00
bastien d9617d8eb8 docs: Higgsfield README + CHANGELOG match the npm-only CLI refresh and the two disables — ship-feature higgsfield-pack 2026-09-30 18:17:16 +02:00
bastien 4c7db8893b fix(higgsfield): report upstream drift on every enable; final review fixes 2026-09-30 16:35:45 +02:00
bastien 142f73b08e docs(plan): mirror the final suite in the plan copies 2026-09-30 16:17:58 +02:00
bastien a1f7893786 test(higgsfield): drop the two shellcheck suppressions in the suite 2026-09-30 16:17:34 +02:00
bastien a53d66af98 docs(global): route media generation to the Higgsfield pack 2026-09-30 16:12:58 +02:00
bastien 0a52ca677d docs: Higgsfield pack in README and CHANGELOG 2026-09-30 16:11:41 +02:00
bastien 852ccdcc0f feat(doctor): report the Higgsfield CLI and its session 2026-09-30 16:11:33 +02:00
bastien 51a3353360 chore(higgsfield): lock entry and gitignore for the skill pack 2026-09-30 16:11:29 +02:00
bastien bbd3d209d3 feat(update): refresh the Higgsfield CLI and skill pack 2026-09-30 16:10:08 +02:00
bastien 83043412d0 feat(install): Higgsfield step 8.6; login offers test stdin alone 2026-09-30 16:08:34 +02:00
bastien acb6cd7cb4 feat(toggle): higgsfield and higgsfield-websites toggles 2026-09-30 16:06:43 +02:00
bastien 21df604eb0 fix(higgsfield): guard the suite's cleanup trap 2026-09-30 16:05:37 +02:00