Compare commits
8
Commits
3e52636da6
...
5e3a1376be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e3a1376be | ||
|
|
e43fbb4bdf | ||
|
|
c0e54a11fa | ||
|
|
5da0d34690 | ||
|
|
5b8b6f529c | ||
|
|
4cbc993af4 | ||
|
|
a18b7b46ac | ||
|
|
d98d0dae18 |
No files matched your search
@@ -24,6 +24,3 @@ node_modules/
|
||||
|
||||
# Claude Code local settings (machine-specific)
|
||||
.claude/settings.local.json
|
||||
|
||||
graphify-out/cost.json # local only
|
||||
graphify-out/cache/ # optional: commit for speed, skip to keep repo small
|
||||
@@ -0,0 +1,72 @@
|
||||
repos:
|
||||
- repo: https://github.com/compilerla/conventional-pre-commit
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: conventional-pre-commit
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.21.2
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
stages: ['pre-commit']
|
||||
|
||||
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||
rev: 3.0.0
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: [--severity=warning]
|
||||
stages: ['pre-commit']
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
stages: ['pre-commit']
|
||||
- id: check-json
|
||||
stages: ['pre-commit']
|
||||
- id: pretty-format-json
|
||||
stages: ['pre-commit']
|
||||
- id: check-yaml
|
||||
stages: ['pre-commit']
|
||||
- id: trailing-whitespace
|
||||
stages: ['pre-commit']
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: run-tests
|
||||
name: Run test suite
|
||||
description: Run all test-*.sh files and bats suite
|
||||
entry: bash tests/run-tests.sh
|
||||
language: system
|
||||
stages: [pre-push]
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
|
||||
- id: check-manifests
|
||||
name: Check plugin manifests
|
||||
description: Validate marketplace.json and plugin.json paths
|
||||
entry: bash scripts/check-manifests.sh
|
||||
language: system
|
||||
stages: [pre-push]
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
|
||||
- id: skill-frontmatter
|
||||
stages: ['pre-commit']
|
||||
name: SKILL.md frontmatter validation
|
||||
description: Ensure SKILL.md files have required frontmatter fields
|
||||
entry: bash
|
||||
language: system
|
||||
files: 'SKILL\.md$'
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
for f in "$@"; do
|
||||
if [[ -f "$f" ]]; then
|
||||
if ! grep -q "^name:" "$f" || ! grep -q "^description:" "$f"; then
|
||||
echo "ERROR: $f is missing required frontmatter fields (name: and description:)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
+6
-2
@@ -94,9 +94,13 @@ When running a full test audit, `claude plugin validate --strict` was not includ
|
||||
|
||||
`scripts/gitleaks.toml` (source, in git, deployed to repo root by `setup-gitleaks.sh`) and `.gitleaks.toml` (deployed root copy, read by the hook, also tracked in git) were found with different allowlist states — someone had updated the deployed file directly without updating the source. Running `setup-gitleaks.sh` again would overwrite the deployed file with the stale source, silently deleting the existing allowlist and re-exposing a known false positive as a blocking pre-commit failure. Fix: treat `scripts/gitleaks.toml` as the single source of truth; never edit `.gitleaks.toml` directly. When making allowlist changes, always update source and deployed copy together in the same commit. Longer-term fix: `setup-gitleaks.sh` should merge rather than overwrite, or detect divergence and warn when `.gitleaks.toml` is tracked in git.
|
||||
|
||||
## 2026-06-21 — `shellcheck` without `-x` blocks pre-commit on any script using `source`
|
||||
## 2026-06-21 — `shellcheck` without `-x` blocks pre-commit on any script using `source` (LEGACY SHELL HOOKS)
|
||||
|
||||
The pre-commit hook ran `shellcheck "$f"` without `-x`. Without `-x`, shellcheck fires SC1091 for every `source` statement and exits non-zero, blocking the commit. This was a latent bug since the hook was written, only triggered when `install.sh` (which sources `deploy-manifest.sh`) was staged for the first time. Compounding it: the `# shellcheck source=` directive in `install.sh` pointed to `deploy-manifest.sh` (bare filename, resolved from CWD = repo root) rather than `scripts/deploy-manifest.sh` (correct repo-root-relative path), so even with `-x` the file wasn't found on the first attempt. Fix: always pass `-x` to shellcheck in hooks. When writing a `source=` directive, use a path that resolves correctly from the CWD where shellcheck will be invoked — verify with `shellcheck -x <file>` before committing.
|
||||
**Status:** Historical. Shell-hook-based pre-commit was replaced by pre-commit framework (Chunk 5, .pre-commit-config.yaml). Modern repos no longer affected. Documented for reference when supporting legacy repos.
|
||||
|
||||
The pre-commit hook ran `shellcheck "$f"` without `-x`. Without `-x`, shellcheck fires SC1091 for every `source` statement and exits non-zero, blocking the commit. This was a latent bug in legacy shell hooks, only triggered when `install.sh` (which sources `deploy-manifest.sh`) was staged for the first time. Compounding it: the `# shellcheck source=` directive in `install.sh` pointed to `deploy-manifest.sh` (bare filename, resolved from CWD = repo root) rather than `scripts/deploy-manifest.sh` (correct repo-root-relative path), so even with `-x` the file wasn't found on the first attempt.
|
||||
|
||||
**Lesson for future work:** When writing a `source=` directive, use a path that resolves correctly from the CWD where shellcheck will be invoked — verify with `shellcheck -x <file>` before committing. Pre-commit framework hooks include `-x` by default in the ecosystem's shellcheck integration.
|
||||
|
||||
## 2026-06-22 — Plugin cache isolation rules out shared/ directories between skills
|
||||
|
||||
|
||||
+2
-3
@@ -44,11 +44,10 @@ A parallel workstream (not a numbered chunk) that runs alongside the chunk seque
|
||||
**Pre-Chunk 6 test suite work** (no CI required — can be done now; see Gitea issue #2 for full context):
|
||||
- Fix U1 first: add gitleaks.toml allowlist entry for `docs/research/ai-coding-factory/ai-coding-factory-session.md:90` (`Token routing: Haiku/Sonnet/Opus` triggers `generic-api-key` false positive; pre-commit hook blocks commits on all machines with gitleaks installed)
|
||||
- `tests/test-plugin-validate.sh` — run `claude plugin validate --strict` on all plugins and marketplace manifests; add the same check to the pre-push hook alongside `check-manifests.sh`
|
||||
- `tests/test-hook-integrity.sh` — verify `.git/hooks/pre-commit` is installed, executable, and contains the expected idempotency markers; distinct from `test-setup-hooks.sh` which tests the setup script, not the installed artifact
|
||||
- `tests/test-gitleaks-scan.sh` — run `gitleaks detect` against the repo and assert exit 0; validates `gitleaks.toml` allowlist correctly suppresses known false positives (requires U1 fix first)
|
||||
- `tests/test-pre-commit-installed.sh` — verify `.pre-commit-config.yaml` is present and hooks run successfully; validate pre-commit framework integration
|
||||
- `tests/test-inventory-crossrefs.sh` — run `inventory.sh` against the live repo; assert zero `../` cross-reference warnings in post-refactor skills; triage the 11 current warnings (U4: determine which are in Chunk 3 rebuild targets vs. post-refactor skills that should be self-contained)
|
||||
- `tests/run-all-tests.sh` — single entry point that runs every test in `tests/`; needed for both developer use and future CI integration
|
||||
- Extend `test-governance-layer.sh` — add structural checks that CONTROLS.md-required controls are in place (gitleaks wired to pre-commit, hook executable, plugin validation passes strict mode); current checks verify governance files exist but not that controls are enforced
|
||||
- Extend `test-governance-layer.sh` — add structural checks that CONTROLS.md-required controls are in place (.pre-commit-config.yaml present with gitleaks hook, pre-commit framework installed, plugin validation passes strict mode); current checks verify governance files exist but not that controls are enforced
|
||||
|
||||
**Chunk 6 CI gaps** (require CI pipeline; implement during Chunk 6 grill):
|
||||
- Secret scanning in CI — CONTROLS.md: "Pre-commit hooks can be bypassed; CI cannot. Both layers are required."
|
||||
|
||||
@@ -709,4 +709,3 @@ Output: a structured decision record suitable for converting to an ADR.
|
||||
---
|
||||
|
||||
*This is a living guidance document. Update it as grill-me sessions produce decisions. When an ADR supersedes a recommendation, mark the section with `> Superseded by ADR-NNN` and link the ADR. Version in git alongside the rest of the factory.*
|
||||
|
||||
@@ -47,7 +47,7 @@ Current skills (direct): `caveman`, `diagnose`, `gitleaks`, `grill-me`, `grill-w
|
||||
- `init-project.sh` — bootstraps a new project (Chunk 6)
|
||||
- Copilot provider adapter (Chunk 7)
|
||||
- Formal CI/pre-commit enforcement of governance rules (Chunk 6)
|
||||
- `setup-gitleaks.sh` not yet wired into `init-project.sh` (Chunk 6) — run manually against new repos
|
||||
- `init-project.sh` scaffolding (Chunk 6) — will seed `.pre-commit-config.yaml` for new projects
|
||||
|
||||
For chunk planning and open questions, see `docs/ROADMAP.md`.
|
||||
|
||||
@@ -57,7 +57,7 @@ For chunk planning and open questions, see `docs/ROADMAP.md`.
|
||||
|
||||
- 2026-06-20 — kyberforge plugin created and registered in `holocron` marketplace. Consolidates `create-plugin`, `marketplace-architect`, `write-skill`, and `write-eval` skills (previously in `.agents/skills/`) plus their evals, bundled scripts, references, and the plugin-marketplace-architecture research doc into a single installable plugin at `plugins/kyberforge/`. Plugin template (`templates/plugin/`) bundled into `plugins/kyberforge/skills/create-plugin/assets/plugin-template/` and removed from repo root. Evals moved from `.agents/evals/marketplace/` and `.agents/evals/factory/write-eval/` into `plugins/kyberforge/tests/evals/`. These four skills are no longer available as standalone slash commands — install the plugin to use them.
|
||||
|
||||
- 2026-06-20 — Gitleaks secret scanning added. `scripts/setup-gitleaks.sh` installs gitleaks v8.24.2, seeds `.gitleaks.toml` (first run only — project-owned after that), and writes a managed pre-commit hook block that is replaced on re-run. `scripts/gitleaks.toml` is the base config template extending gitleaks defaults. `tests/test-setup-gitleaks.sh` covers 6 behaviors (reject non-git dir, config deploy, hook create, append, stale-block replace, idempotency). `.gitleaks.toml` in repo root adds path allowlist for `docs/research/` (high-entropy terminal captures). `gitleaks` skill added (`cross-cutting`) covering full lifecycle: install, update, tune allowlist, scan modes, resolve real findings. Key lesson: v8.24.2 uses `[allowlist]` (singular); v8.25.0+ uses `[[allowlists]]` — wrong syntax silently does nothing.
|
||||
- 2026-06-20 — Gitleaks secret scanning added via pre-commit framework. `.gitleaks.toml` in repo root is the base config extending gitleaks defaults and adds path allowlist for `docs/research/` (high-entropy terminal captures). `gitleaks` skill added (`cross-cutting`) covering full lifecycle: install, update, tune allowlist, scan modes, resolve real findings. Supports both modern repos (pre-commit-based) and legacy repos (shell hook-based setup). Key lesson: v8.24.2 uses `[allowlist]` (singular); v8.25.0+ uses `[[allowlists]]` — wrong syntax silently does nothing.
|
||||
|
||||
- 2026-05-18 — Issue 0018 phase 1 refactor complete: `write-skill` redesigned from scratch. New files added to skill directory: `SKILL-TEMPLATE.md` (authoritative 6-section template with XML blocks, human-usable), `META-TEMPLATE.md` (provenance schema with inline-commented YAML), `CATEGORIES.md` (self-contained category table), `META.md` (write-skill's own provenance). SKILL.md rewritten: 6 sections replacing 8 (Role and When/When not dropped — not in agentskills.io spec); frontmatter reduced to 3 fields (`name`, `description`, `metadata.category`); provenance fields (`version`, `updated`, `when`, `source`, `references`) moved to META.md (progressive disclosure — not loaded at startup). `docs/notes/skill-implementation-workflow.md` updated to reference SKILL-TEMPLATE.md as the authoritative template.
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
skill_name: gitleaks
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-trigger-install
|
||||
name: Explicit trigger — install and configure
|
||||
query: "set up gitleaks in this repo"
|
||||
should_trigger: true
|
||||
|
||||
- id: explicit-trigger-update-hook
|
||||
name: Explicit trigger — update hook
|
||||
query: "update the gitleaks pre-commit hook"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-false-positive
|
||||
name: Implicit trigger — suppress false positive in pre-commit hook
|
||||
query: "my pre-commit hook keeps blocking commits because it thinks my test fixture has an API key, how do I suppress it?"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-scan-history
|
||||
name: Implicit trigger — audit repo history for secrets
|
||||
query: "I want to scan my entire git history to make sure no credentials were ever committed"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-trigger-security-review
|
||||
name: Negative trigger — general code security review
|
||||
query: "do a security review of this pull request"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-ci-scan
|
||||
name: Negative trigger — CI pipeline secret scanning
|
||||
query: "add secret scanning to my GitHub Actions CI pipeline"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-other-tool
|
||||
name: Negative trigger — different secret scanning tool
|
||||
query: "trufflehog is flagging false positives in my repo, how do I configure it?"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: output-states-intent-before-running
|
||||
name: Deterministic — states intent and waits for confirmation before running setup script
|
||||
type: deterministic
|
||||
prompt: "set up gitleaks in /home/user/myproject"
|
||||
expected_output: >
|
||||
The skill states what it is about to do (run setup-gitleaks.sh, which will modify
|
||||
.git/hooks/pre-commit and seed .gitleaks.toml) and asks for confirmation before
|
||||
executing the script. The confirmation request appears before any Bash command
|
||||
that runs the script.
|
||||
assertions:
|
||||
- "Output contains 'setup-gitleaks.sh' before any Bash tool invocation that runs it"
|
||||
- "Output contains a confirmation prompt (e.g. 'Proceed?') before running the setup script"
|
||||
- "Output does not run the setup script without an explicit confirmation step"
|
||||
|
||||
- id: output-redacts-secret-values
|
||||
name: Deterministic — redacts secret value and prescribes rotation before history rewrite
|
||||
type: deterministic
|
||||
prompt: "gitleaks found an AWS secret key on line 42 of config/settings.py — it says the value is AKIAIOSFODNN7EXAMPLE. What do I do?"
|
||||
expected_output: >
|
||||
The skill does not echo or repeat the secret value. It references the finding by
|
||||
rule ID, file path, and line number only. It states that the secret must be rotated
|
||||
immediately. It mentions git filter-repo (not git filter-branch) for history rewrite
|
||||
and asks for user confirmation before running the rewrite.
|
||||
assertions:
|
||||
- "Output does not contain the literal string 'AKIAIOSFODNN7EXAMPLE'"
|
||||
- "Output mentions rotating or revoking the credential as the first action"
|
||||
- "Output references 'git filter-repo' for history rewrite, not 'git filter-branch'"
|
||||
- "Output states that user confirmation is required before running the history rewrite"
|
||||
|
||||
- id: output-quality-allowlist-guidance
|
||||
name: LLM-rubric — allowlist guidance is correct, version-aware, and minimal
|
||||
type: llm-rubric
|
||||
prompt: "gitleaks keeps flagging my docs/research/ directory as containing secrets, how do I suppress it?"
|
||||
expected_output: >
|
||||
High-quality output checks the installed gitleaks version before prescribing any
|
||||
TOML syntax, recommends a path-based allowlist entry in .gitleaks.toml (not a
|
||||
.gitleaksignore fingerprint), uses the correct TOML syntax for the detected version,
|
||||
adds only the minimal allowlist entry needed for the identified false positive, and
|
||||
includes a verification step (re-run gitleaks dir -v or gitleaks dir --log-level debug)
|
||||
after making the change.
|
||||
assertions:
|
||||
- "Output checks or asks about the gitleaks version before writing TOML syntax"
|
||||
- "Output recommends a path-based allowlist entry in .gitleaks.toml rather than .gitleaksignore"
|
||||
- "Output includes a command to verify the suppression works after the change"
|
||||
- "Output explains why .gitleaksignore fingerprints are fragile (line numbers shift)"
|
||||
@@ -1,97 +0,0 @@
|
||||
skill_name: neuledge-context
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-install-register
|
||||
name: "Explicit trigger — install and register"
|
||||
query: "install @neuledge/context and register it as an MCP server in Claude Code"
|
||||
should_trigger: true
|
||||
|
||||
- id: explicit-package-management
|
||||
name: "Explicit trigger — package management"
|
||||
query: "install the react documentation package using neuledge context"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-offline-docs
|
||||
name: "Implicit trigger — offline docs for AI agent"
|
||||
query: "I need React and Next.js docs available to my AI agent without web searches"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-different-mcp
|
||||
name: "Negative — different MCP server"
|
||||
query: "Add the Gitea MCP server to Claude Code"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-query-existing
|
||||
name: "Negative — querying an already-running server"
|
||||
query: "How do I query React docs using the context server that's already running?"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-cursor-setup
|
||||
name: "Negative — different provider"
|
||||
query: "Set up context serve for Cursor"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: install-script-announced
|
||||
name: "Install — script announced before running, version verified after"
|
||||
type: deterministic
|
||||
prompt: "install @neuledge/context"
|
||||
expected_output: >
|
||||
The skill announces that it will run scripts/setup-neuledge-context.sh before executing it,
|
||||
then verifies the installation by running `context --version`.
|
||||
assertions:
|
||||
- "Output mentions 'scripts/setup-neuledge-context.sh' before any install command is run"
|
||||
- "Output includes a `context --version` call after the install step"
|
||||
- "Output does not contain `npm install -g @neuledge/context@latest` (no floating @latest)"
|
||||
|
||||
- id: mcp-list-before-add
|
||||
name: "MCP registration — list checked before add, skipped if present"
|
||||
type: deterministic
|
||||
prompt: "register @neuledge/context as a Claude Code MCP server"
|
||||
expected_output: >
|
||||
The skill runs `claude mcp list` and checks for an existing 'context' entry before
|
||||
running `claude mcp add`. If already registered, the add step is skipped.
|
||||
assertions:
|
||||
- "Output includes `claude mcp list` before `claude mcp add context`"
|
||||
- "Output states that registration is skipped when the server is already present"
|
||||
- "The `claude mcp add` command uses stdio form: `claude mcp add context -- context serve`"
|
||||
|
||||
- id: auth-chmod-paired
|
||||
name: "Auth — secure-context-config.sh run immediately after auth add"
|
||||
type: deterministic
|
||||
prompt: "add auth credentials for docs.example.com to neuledge context"
|
||||
expected_output: >
|
||||
The skill runs `context auth add docs.example.com` with an environment variable reference
|
||||
for the credential, then immediately runs scripts/secure-context-config.sh.
|
||||
No credential value appears in the output.
|
||||
assertions:
|
||||
- "Output references an environment variable (e.g. $TOKEN) rather than a literal credential value"
|
||||
- "Output runs `scripts/secure-context-config.sh` in the same step as or immediately after `context auth add`"
|
||||
- "No bearer token, cookie value, or other credential string appears in the output"
|
||||
|
||||
- id: git-check-url-source
|
||||
name: "context add — git prerequisite checked for URL sources only"
|
||||
type: deterministic
|
||||
prompt: "add documentation from https://github.com/prisma/prisma using context add"
|
||||
expected_output: >
|
||||
Before running `context add`, the skill checks `git --version` because the source is a
|
||||
GitHub URL. The check is present for URL/repo sources and absent for local .db file paths.
|
||||
assertions:
|
||||
- "Output includes `git --version` before the `context add https://github.com/...` command"
|
||||
- "If given a local .db file path instead, the git check is absent"
|
||||
|
||||
- id: install-flow-quality
|
||||
name: "Full install + register flow quality"
|
||||
type: llm-rubric
|
||||
prompt: "install @neuledge/context and set it up as my Claude Code MCP server"
|
||||
expected_output: >
|
||||
A complete, ordered install-then-register flow: (1) announce the install script,
|
||||
(2) run setup-neuledge-context.sh, (3) verify with context --version,
|
||||
(4) check claude mcp list, (5) run claude mcp add if not already registered,
|
||||
(6) confirm with claude mcp list. Steps are in the correct order with verification
|
||||
between install and registration.
|
||||
assertions:
|
||||
- "Install step comes before MCP registration step"
|
||||
- "A verification command (context --version) appears between install and registration"
|
||||
- "The output would leave a user with a working @neuledge/context MCP server in Claude Code"
|
||||
- "No step is skipped without an explanation of why it was skipped"
|
||||
@@ -1,61 +0,0 @@
|
||||
skill_name: write-docs
|
||||
|
||||
trigger_tests:
|
||||
- id: explicit-trigger-document-module
|
||||
name: "Explicit trigger — document a script"
|
||||
query: "Write documentation for the install.sh script"
|
||||
should_trigger: true
|
||||
|
||||
- id: explicit-trigger-create-docs
|
||||
name: "Explicit trigger — create docs for a feature"
|
||||
query: "Create docs for this feature"
|
||||
should_trigger: true
|
||||
|
||||
- id: implicit-trigger-readme-update
|
||||
name: "Implicit trigger — outdated README section, no trigger phrase"
|
||||
query: "We need to update the README section for the auth module, the current one is outdated"
|
||||
should_trigger: true
|
||||
|
||||
- id: negative-trigger-prd
|
||||
name: "Negative — PRD request should route to to-prd"
|
||||
query: "Write a PRD for the new logging feature"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-write-skill
|
||||
name: "Negative — skill authoring request should route to write-skill"
|
||||
query: "Write a skill for generating documentation automatically"
|
||||
should_trigger: false
|
||||
|
||||
- id: negative-trigger-skill-file
|
||||
name: "Negative — SKILL.md update (skill files are self-describing)"
|
||||
query: "Document how the write-docs skill works by updating its SKILL.md"
|
||||
should_trigger: false
|
||||
|
||||
output_tests:
|
||||
- id: output-proposes-files-before-reading
|
||||
name: "Deterministic — candidates proposed or approval sought before reading files"
|
||||
type: deterministic
|
||||
prompt: "Write documentation for the config module"
|
||||
expected_output: "Skill proposes candidate files or asks the user to name specific files before reading any file content"
|
||||
assertions:
|
||||
- "Response proposes candidate file paths or asks the user to confirm which files to read before showing any extracted content"
|
||||
- "Response does not display extracted code content or API surface without first receiving file approval"
|
||||
|
||||
- id: output-gap-check-present
|
||||
name: "Deterministic — gap check step present before drafting"
|
||||
type: deterministic
|
||||
prompt: "Write documentation for the install.sh script, audience: developer"
|
||||
expected_output: "Skill presents extracted behaviour to the user and asks them to fill gaps before drafting any section"
|
||||
assertions:
|
||||
- "Response includes a gap check step that presents extracted behaviour and asks what the code does not explain"
|
||||
- "Response does not skip directly to a drafted documentation section without presenting extracted content first"
|
||||
|
||||
- id: output-never-invents-behaviour
|
||||
name: "LLM rubric — no invented behaviour, all claims sourced"
|
||||
type: llm-rubric
|
||||
prompt: "Document the src/config.py file for internal developers"
|
||||
expected_output: "Documentation where every claim is attributed to code content or explicit user input, with no invented explanations, assumptions about intent, or unverifiable behaviour claims."
|
||||
assertions:
|
||||
- "The skill explicitly derives each documented claim from a named source — a code line, spec section, or user statement — and does not add claims without attribution"
|
||||
- "The skill does not include descriptions of caller intent, design rationale, or future behaviour that are not present in the source material"
|
||||
- "If a behaviour is undocumentable (internal detail with no public spec), the skill notes it as out-of-scope rather than inventing an explanation"
|
||||
@@ -1,15 +0,0 @@
|
||||
```yaml
|
||||
version: "1.0"
|
||||
updated: 2026-06-20
|
||||
|
||||
when: >
|
||||
Invoked when the user wants to install gitleaks and wire it as a git pre-commit secret
|
||||
scanner, update the hook in an existing repo, tune allowlist rules to suppress false
|
||||
positives, debug a scan finding, or rotate a real secret that was found. Covers the full
|
||||
lifecycle: install → configure → maintain → remediate. Not invoked for general code
|
||||
security review (security-review skill) or CI pipeline secret scanning (write-ci-pipeline skill).
|
||||
|
||||
references:
|
||||
- https://github.com/gitleaks/gitleaks/releases/tag/v8.24.2
|
||||
- https://github.com/gitleaks/gitleaks/blob/main/README.md
|
||||
```
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
name: gitleaks
|
||||
description: Use when the user wants to install gitleaks, wire it as a git pre-commit secret scanner, update the hook in an existing repo, tune allowlist rules, resolve false positives, or debug a gitleaks scan finding. Do NOT use when the user wants a general security review of code (use security-review), wants to add secret scanning to a CI pipeline (use write-ci-pipeline), or is asking about a different secret scanning tool such as trufflehog or git-secrets.
|
||||
metadata:
|
||||
category: cross-cutting
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Edit
|
||||
---
|
||||
|
||||
<requirements>
|
||||
|
||||
## Required inputs
|
||||
|
||||
- **Target repo path** — absolute path to the git repository to configure; inferred from current working directory if not stated, ask if ambiguous
|
||||
- **Task type** — install/configure, update hook, tune allowlist, debug finding; inferred from the user's request
|
||||
|
||||
## Constraints
|
||||
|
||||
- Always state what you are about to do before running `setup-gitleaks.sh` — the script modifies `.git/hooks/pre-commit` and seeds `.gitleaks.toml`
|
||||
- Never modify `.gitleaks.toml` if the user has not asked for allowlist changes — it is project-owned once seeded; treat it as user-controlled config
|
||||
- Never run `gitleaks git` or `gitleaks dir` across the full history without warning the user it may be slow on large repos
|
||||
- Redact any secret values that appear in gitleaks output before showing them to the user — show the rule ID, file, and line number only
|
||||
- When the installed gitleaks version is unknown, check it with `gitleaks version` before suggesting config syntax — v8.24.2 uses `[allowlist]`; v8.25.0+ uses `[[allowlists]]`
|
||||
- False positive suppression: prefer path-based allowlists in `.gitleaks.toml` over fingerprint-based entries in `.gitleaksignore` — fingerprints are line-number-sensitive and break on file edits
|
||||
|
||||
</requirements>
|
||||
|
||||
<steps>
|
||||
|
||||
## Process
|
||||
|
||||
### Install and configure
|
||||
|
||||
1. **Confirm target.** State: "I will run `scripts/setup-gitleaks.sh <path>` which will install gitleaks (if absent), seed `.gitleaks.toml` (first run only), and write the pre-commit hook. Proceed?" Wait for confirmation — this modifies the repo's git hook.
|
||||
|
||||
2. **Run setup script.** Execute from the ai-development repo root:
|
||||
```
|
||||
bash scripts/setup-gitleaks.sh <TARGET_REPO>
|
||||
```
|
||||
The script is idempotent — it replaces the gitleaks block in the hook on every run without disturbing other hook content.
|
||||
|
||||
3. **Verify installation.** Run `gitleaks version` to confirm the binary is available. Run `gitleaks git --staged --redact -v` in the target repo to confirm the hook would work on a staged commit (add a dummy change if needed to test).
|
||||
|
||||
4. **Commit `.gitleaks.toml`.** Remind the user that `.gitleaks.toml` belongs in version control so all contributors share the same allowlist rules.
|
||||
|
||||
### Update hook
|
||||
|
||||
Re-run `bash scripts/setup-gitleaks.sh <TARGET_REPO>` from the ai-development repo root. The managed block (delimited by `# managed by setup-gitleaks.sh` / `# end gitleaks` markers) is always replaced with the current version. Non-gitleaks hook content is preserved.
|
||||
|
||||
### Tune allowlist / resolve false positives
|
||||
|
||||
1. **Identify the false positive.** Run `gitleaks dir --log-level debug <path>` to see which rule fired and which allowlist entries (if any) are already active.
|
||||
|
||||
2. **Check the gitleaks version.** Run `gitleaks version`. Use `[allowlist]` syntax for v8.24.2; use `[[allowlists]]` syntax for v8.25.0+. Using the wrong syntax silently produces no errors but the allowlist does nothing — this is the most common configuration trap.
|
||||
|
||||
3. **Choose suppression strategy.** Read `.gitleaks.toml` first. See `references/allowlist-patterns.md` for syntax examples and when to use each approach:
|
||||
- Path regex in `[allowlist]` — for files that can never contain real secrets (research notes, terminal captures, test fixtures). Preferred.
|
||||
- Stopwords in `[allowlist]` — for placeholder patterns like "example", "changeme".
|
||||
- `disabledRules` in `[extend]` — to disable a noisy default rule entirely. Use only when the rule has no value for this repo.
|
||||
- `.gitleaksignore` fingerprint — last resort; breaks when the file is edited because line numbers shift.
|
||||
|
||||
4. **Edit `.gitleaks.toml`.** Add the minimal allowlist entry needed. Do not suppress more than the identified false positive.
|
||||
|
||||
5. **Verify.** Re-run `gitleaks dir -v <path>` or `gitleaks git -v` to confirm the false positive is suppressed and no real findings are hidden.
|
||||
|
||||
### Scan modes
|
||||
|
||||
| Mode | Command | When to use |
|
||||
|---|---|---|
|
||||
| Staged changes (pre-commit) | `gitleaks git --staged --redact -v` | What the hook runs |
|
||||
| Full commit history | `gitleaks git -v` | Audit existing repo history |
|
||||
| Working directory files | `gitleaks dir -v <path>` | Scan uncommitted files |
|
||||
| Debug allowlists | `gitleaks dir --log-level debug <path>` | See which files are skipped and which allowlists fire |
|
||||
|
||||
### Resolve a real finding
|
||||
|
||||
1. Do not redact or show the secret value. Reference the rule ID, file, and line number only.
|
||||
2. The secret is compromised the moment it was committed — rotate it immediately, regardless of whether the commit is reachable from the public remote.
|
||||
3. Remove the secret from history using `git filter-repo` (not `git filter-branch`). This is a history-rewrite — confirm with the user before running. Force-push to all remotes after rewriting.
|
||||
4. Add the file path to the `.gitleaks.toml` allowlist only if the file is known to be a false-positive source going forward (e.g. a test fixture). Do not add an allowlist entry to suppress a real finding that has been removed.
|
||||
|
||||
## Output format
|
||||
|
||||
No structured output file. The skill produces:
|
||||
- Modified `.git/hooks/pre-commit` in the target repo (via the setup script)
|
||||
- Modified `.gitleaks.toml` in the target repo (allowlist changes only, when requested)
|
||||
- Terminal confirmation of what was changed and what to do next
|
||||
|
||||
</steps>
|
||||
|
||||
<checks>
|
||||
|
||||
## Failure handling
|
||||
|
||||
- `setup-gitleaks.sh` not found — stop; instruct the user to run from the ai-development repo root at `/root/ai-development/`
|
||||
- Target path is not a git repository — report the error from the script and ask the user to confirm the correct path
|
||||
- `gitleaks` binary not installed and download fails — report the curl/network error; direct the user to manual install at `https://github.com/gitleaks/gitleaks/releases`
|
||||
- Wrong TOML syntax for installed version — detect via `gitleaks version`, show the correct syntax for that version, do not guess
|
||||
|
||||
## Self-check
|
||||
|
||||
- [ ] Target repo confirmed before running the setup script
|
||||
- [ ] `gitleaks version` checked before writing any `.gitleaks.toml` allowlist syntax
|
||||
- [ ] Secret values in scan output redacted before displaying to the user
|
||||
- [ ] `.gitleaks.toml` edits are minimal — only the identified false positive suppressed
|
||||
- [ ] After any allowlist change: re-ran scan to verify suppression works and no real findings are hidden
|
||||
- [ ] For real findings: rotation step stated before history rewrite, user confirmed history rewrite before running `git filter-repo`
|
||||
|
||||
</checks>
|
||||
@@ -1,83 +0,0 @@
|
||||
# Gitleaks allowlist patterns
|
||||
|
||||
## Version syntax
|
||||
|
||||
| Version | Allowlist syntax |
|
||||
|---|---|
|
||||
| v8.24.2 and earlier | `[allowlist]` (singular table) |
|
||||
| v8.25.0 and later | `[[allowlists]]` (array of tables) |
|
||||
|
||||
**Critical**: using the wrong syntax produces no error but the allowlist silently does nothing. Always check `gitleaks version` first.
|
||||
|
||||
## v8.24.2 syntax (this repo uses 8.24.2)
|
||||
|
||||
### Suppress by path regex
|
||||
|
||||
Use for files that can never contain real secrets (research notes, terminal captures, test fixtures, generated docs).
|
||||
|
||||
```toml
|
||||
[allowlist]
|
||||
description = "research notes and terminal captures"
|
||||
paths = [
|
||||
'''docs/research/.*''',
|
||||
'''tests/fixtures/.*''',
|
||||
]
|
||||
```
|
||||
|
||||
### Suppress by stopword
|
||||
|
||||
Use for placeholder values that match secret patterns but are clearly not real.
|
||||
|
||||
```toml
|
||||
[allowlist]
|
||||
description = "placeholder values"
|
||||
stopwords = ["example", "placeholder", "changeme", "your-api-key-here"]
|
||||
```
|
||||
|
||||
### Disable a default rule entirely
|
||||
|
||||
Use only when a rule has no value for this repo and produces pervasive false positives.
|
||||
|
||||
```toml
|
||||
[extend]
|
||||
useDefault = true
|
||||
disabledRules = ["generic-api-key"]
|
||||
```
|
||||
|
||||
## v8.25.0+ syntax (for reference)
|
||||
|
||||
```toml
|
||||
[[allowlists]]
|
||||
description = "research notes"
|
||||
paths = ['''docs/research/.*''']
|
||||
|
||||
[[allowlists]]
|
||||
description = "placeholder values"
|
||||
stopwords = ["example", "placeholder"]
|
||||
```
|
||||
|
||||
## .gitleaksignore (fingerprint-based — last resort)
|
||||
|
||||
```
|
||||
# Format: <fingerprint>:<line-number>
|
||||
# Generated by: gitleaks git -v --report-format json | jq -r '.[] | "\(.Fingerprint):\(.StartLine)"'
|
||||
abc123def456:42
|
||||
```
|
||||
|
||||
Avoid this approach: fingerprints embed line numbers. Any edit to the file shifts line numbers and invalidates the entry, re-surfacing the false positive.
|
||||
|
||||
## Verification after any change
|
||||
|
||||
```bash
|
||||
# Scan current files
|
||||
gitleaks dir -v .
|
||||
|
||||
# Scan with debug output to see which allowlists fired
|
||||
gitleaks dir --log-level debug .
|
||||
|
||||
# Scan commit history
|
||||
gitleaks git -v
|
||||
|
||||
# Scan only staged changes (what the pre-commit hook runs)
|
||||
gitleaks git --staged --redact -v
|
||||
```
|
||||
@@ -27,7 +27,6 @@ fi
|
||||
COLOR_RESET=$'\033[0m'
|
||||
COLOR_BOLD_BLUE=$'\033[1;34m' # dir — identity, always stable
|
||||
COLOR_BLUE=$'\033[0;34m' # tokens — informational, no urgency
|
||||
COLOR_CYAN=$'\033[0;36m' # model — configuration, always stable
|
||||
COLOR_GREEN=$'\033[0;32m' # healthy / within limits
|
||||
COLOR_AMBER=$'\033[0;33m' # attention / approaching a limit
|
||||
COLOR_RED=$'\033[0;31m' # urgent / act now
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# All paths: src relative to REPO_ROOT, dest relative to HOME.
|
||||
# Format: "src:dest"
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
# Individual files — copied verbatim
|
||||
DEPLOY_FILES=(
|
||||
"providers/claude-code/CLAUDE.md:.claude/CLAUDE.md"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
title = "gitleaks config"
|
||||
|
||||
[extend]
|
||||
# Extends the default ruleset built into gitleaks.
|
||||
# Remove useDefault and define [[rules]] from scratch if you want full control.
|
||||
useDefault = true
|
||||
|
||||
# Rules to disable from the default set — uncomment and add IDs for known false positives.
|
||||
# Run `gitleaks git -v` on your repo first to discover which rules fire.
|
||||
# disabledRules = ["generic-api-key"]
|
||||
|
||||
# Global allowlist — applies to all rules.
|
||||
# Note: uses [allowlist] (v8 syntax). v8.25.0+ uses [[allowlists]] (array of tables).
|
||||
# Add path regexes or stopwords to suppress known false positives.
|
||||
|
||||
[allowlist]
|
||||
description = "Known false positives — prose patterns and research session notes"
|
||||
# docs/research/: high-entropy text from terminal captures in session notes
|
||||
# docs/ROADMAP.md: documents known false positives, triggering the same rules
|
||||
# ai-coding-factory-session.md:90 specifically: 'Token routing: Haiku/Sonnet/Opus'
|
||||
paths = [
|
||||
'''docs/research/.*''',
|
||||
'''docs/ROADMAP\.md''',
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Sets secure file permissions on ~/.context/config.json.
|
||||
# Run after every `context auth add` to protect stored credentials.
|
||||
# Safe to run when the file does not exist yet.
|
||||
|
||||
CONFIG="${HOME}/.context/config.json"
|
||||
|
||||
if [ ! -f "$CONFIG" ]; then
|
||||
echo "Skipped: ${CONFIG} does not exist — no permissions to set."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
chmod 600 "$CONFIG"
|
||||
echo "Secured: ${CONFIG} set to 600 (owner read/write only)."
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Sets up gitleaks as a git pre-commit hook in a target repository.
|
||||
# Usage: setup-gitleaks.sh [TARGET_REPO]
|
||||
# TARGET_REPO — path to the git repo to configure (default: current directory)
|
||||
# Idempotent: safe to re-run; always replaces the hook block with the current version.
|
||||
|
||||
GITLEAKS_VERSION="8.24.2"
|
||||
GITLEAKS_INSTALL_DIR="${GITLEAKS_INSTALL_DIR:-/usr/local/bin}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET="${1:-$(pwd)}"
|
||||
HOOK_FILE="$TARGET/.git/hooks/pre-commit"
|
||||
CONFIG_SRC="$SCRIPT_DIR/gitleaks.toml"
|
||||
CONFIG_DEST="$TARGET/.gitleaks.toml"
|
||||
MARKER="# managed by setup-gitleaks.sh"
|
||||
END_MARKER="# end gitleaks"
|
||||
|
||||
# --- Install gitleaks if not present ---
|
||||
|
||||
install_gitleaks() {
|
||||
local os arch tarball url tmp_dir
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux) os="linux" ;;
|
||||
Darwin) os="darwin" ;;
|
||||
*)
|
||||
echo "Error: unsupported OS '$(uname -s)' — install gitleaks manually from https://github.com/gitleaks/gitleaks/releases" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64) arch="x64" ;;
|
||||
aarch64 | arm64) arch="arm64" ;;
|
||||
*)
|
||||
echo "Error: unsupported architecture '$(uname -m)' — install gitleaks manually from https://github.com/gitleaks/gitleaks/releases" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
tarball="gitleaks_${GITLEAKS_VERSION}_${os}_${arch}.tar.gz"
|
||||
url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${tarball}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' RETURN
|
||||
|
||||
echo "Installing gitleaks v${GITLEAKS_VERSION}..."
|
||||
curl -fsSL "$url" -o "$tmp_dir/$tarball"
|
||||
tar -xzf "$tmp_dir/$tarball" -C "$tmp_dir" gitleaks
|
||||
install -m 755 "$tmp_dir/gitleaks" "$GITLEAKS_INSTALL_DIR/gitleaks"
|
||||
echo "Installed: $GITLEAKS_INSTALL_DIR/gitleaks"
|
||||
}
|
||||
|
||||
if ! command -v gitleaks &>/dev/null; then
|
||||
install_gitleaks
|
||||
fi
|
||||
|
||||
# --- Validate ---
|
||||
|
||||
if [ ! -d "$TARGET/.git" ]; then
|
||||
echo "Error: $TARGET is not a git repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG_SRC" ]; then
|
||||
echo "Error: config template not found at $CONFIG_SRC" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Deploy config ---
|
||||
|
||||
if [ -f "$CONFIG_DEST" ]; then
|
||||
echo "Skipped: $CONFIG_DEST already exists — edit it directly to customise rules."
|
||||
else
|
||||
cp "$CONFIG_SRC" "$CONFIG_DEST"
|
||||
echo "Wrote: $CONFIG_DEST"
|
||||
echo " Commit this file — it belongs in version control."
|
||||
fi
|
||||
|
||||
# --- Deploy hook ---
|
||||
|
||||
hook_block() {
|
||||
cat <<BLOCK
|
||||
|
||||
$MARKER
|
||||
if command -v gitleaks &>/dev/null; then
|
||||
gitleaks git --staged --redact -v
|
||||
else
|
||||
echo "Warning: gitleaks not installed — secret scan skipped (https://github.com/gitleaks/gitleaks/releases)" >&2
|
||||
fi
|
||||
$END_MARKER
|
||||
BLOCK
|
||||
}
|
||||
|
||||
write_hook() {
|
||||
local hook_file="$1"
|
||||
|
||||
if grep -qF "$MARKER" "$hook_file"; then
|
||||
# Remove old block (start marker through end marker inclusive) then append current version
|
||||
awk -v start="$MARKER" -v end="$END_MARKER" '
|
||||
$0 == start { skip=1; next }
|
||||
skip && $0 == end { skip=0; next }
|
||||
!skip { print }
|
||||
' "$hook_file" > "${hook_file}.tmp" && mv "${hook_file}.tmp" "$hook_file"
|
||||
hook_block >> "$hook_file"
|
||||
echo "Updated: $hook_file (gitleaks block replaced)"
|
||||
else
|
||||
hook_block >> "$hook_file"
|
||||
echo "Updated: $hook_file (gitleaks block appended to existing hook)"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f "$HOOK_FILE" ]; then
|
||||
write_hook "$HOOK_FILE"
|
||||
else
|
||||
{ echo '#!/usr/bin/env bash'; echo 'set -euo pipefail'; hook_block; } > "$HOOK_FILE"
|
||||
chmod +x "$HOOK_FILE"
|
||||
echo "Created: $HOOK_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. Staged secrets will be scanned on every commit in $TARGET."
|
||||
echo "To skip on a single commit: SKIP=gitleaks git commit ..."
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Installs git hook blocks for validation into a target repository.
|
||||
# Usage: setup-hooks.sh [TARGET_REPO]
|
||||
# TARGET_REPO — path to the git repo to configure (default: current directory)
|
||||
# Idempotent: safe to re-run; always replaces each managed block with the current version.
|
||||
|
||||
SHELLCHECK_VERSION="0.10.0"
|
||||
JQ_VERSION="1.7.1"
|
||||
YQ_VERSION="4.44.3"
|
||||
TOOL_INSTALL_DIR="${TOOL_INSTALL_DIR:-/usr/local/bin}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET="${1:-$(pwd)}"
|
||||
MARKER="# managed by setup-hooks.sh"
|
||||
END_MARKER="# end setup-hooks"
|
||||
|
||||
if [[ ! -d "$TARGET/.git" ]]; then
|
||||
echo "Error: $TARGET is not a git repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Tool installation ---
|
||||
|
||||
_os() {
|
||||
case "$(uname -s)" in
|
||||
Linux) echo "linux" ;;
|
||||
Darwin) echo "darwin" ;;
|
||||
*) echo "Error: unsupported OS '$(uname -s)'" >&2; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64) echo "x86_64" ;;
|
||||
aarch64 | arm64) echo "aarch64" ;;
|
||||
*) echo "Error: unsupported architecture '$(uname -m)'" >&2; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_shellcheck() (
|
||||
os="$(_os)"
|
||||
arch="$(_arch)"
|
||||
tarball="shellcheck-v${SHELLCHECK_VERSION}.${os}.${arch}.tar.xz"
|
||||
url="https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/${tarball}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
echo "Installing shellcheck v${SHELLCHECK_VERSION}..."
|
||||
curl -fsSL "$url" -o "$tmp_dir/$tarball"
|
||||
tar -xJf "$tmp_dir/$tarball" -C "$tmp_dir" --strip-components=1
|
||||
install -m 755 "$tmp_dir/shellcheck" "$TOOL_INSTALL_DIR/shellcheck"
|
||||
echo "Installed: $TOOL_INSTALL_DIR/shellcheck"
|
||||
)
|
||||
|
||||
install_jq() (
|
||||
os="$(_os)"
|
||||
[[ "$os" == "darwin" ]] && os="macos"
|
||||
arch="$(_arch | sed 's/x86_64/amd64/; s/aarch64/arm64/')"
|
||||
url="https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/jq-${os}-${arch}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
echo "Installing jq v${JQ_VERSION}..."
|
||||
curl -fsSL "$url" -o "$tmp_dir/jq"
|
||||
install -m 755 "$tmp_dir/jq" "$TOOL_INSTALL_DIR/jq"
|
||||
echo "Installed: $TOOL_INSTALL_DIR/jq"
|
||||
)
|
||||
|
||||
install_yq() (
|
||||
os="$(_os)"
|
||||
arch="$(_arch | sed 's/x86_64/amd64/; s/aarch64/arm64/')"
|
||||
url="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_${os}_${arch}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
echo "Installing yq v${YQ_VERSION}..."
|
||||
curl -fsSL "$url" -o "$tmp_dir/yq"
|
||||
install -m 755 "$tmp_dir/yq" "$TOOL_INSTALL_DIR/yq"
|
||||
echo "Installed: $TOOL_INSTALL_DIR/yq"
|
||||
)
|
||||
|
||||
ensure_tool() {
|
||||
local tool="$1"
|
||||
if ! command -v "$tool" &>/dev/null; then
|
||||
"install_${tool}"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_tool shellcheck
|
||||
ensure_tool jq
|
||||
ensure_tool yq
|
||||
|
||||
# --- Marker-block helpers ---
|
||||
|
||||
write_block() {
|
||||
local hook_file="$1"
|
||||
local block_content="$2"
|
||||
|
||||
if grep -qF "$MARKER" "$hook_file"; then
|
||||
awk -v start="$MARKER" -v end="$END_MARKER" '
|
||||
$0 == start { skip=1; next }
|
||||
skip && $0 == end { skip=0; next }
|
||||
!skip { print }
|
||||
' "$hook_file" > "${hook_file}.tmp" && mv "${hook_file}.tmp" "$hook_file"
|
||||
fi
|
||||
|
||||
printf '\n%s\n%s\n%s\n' "$MARKER" "$block_content" "$END_MARKER" >> "$hook_file"
|
||||
chmod +x "$hook_file"
|
||||
}
|
||||
|
||||
ensure_hook() {
|
||||
local hook_file="$1"
|
||||
if [[ ! -f "$hook_file" ]]; then
|
||||
printf '#!/usr/bin/env bash\nset -euo pipefail\n' > "$hook_file"
|
||||
chmod +x "$hook_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- commit-msg: conventional commits ---
|
||||
|
||||
COMMIT_MSG_HOOK="$TARGET/.git/hooks/commit-msg"
|
||||
ensure_hook "$COMMIT_MSG_HOOK"
|
||||
|
||||
commit_msg_block() {
|
||||
cat <<'BLOCK'
|
||||
msg=$(cat "$1")
|
||||
pattern='^(feat|fix|docs|chore|refactor|test|perf|ci|build|revert)(\(.+\))?!?: .+'
|
||||
if ! echo "$msg" | grep -qE "$pattern"; then
|
||||
echo "ERROR: Commit message must follow Conventional Commits format." >&2
|
||||
echo " Examples: feat: add login, fix(auth): correct token expiry, chore!: drop python dep" >&2
|
||||
exit 1
|
||||
fi
|
||||
BLOCK
|
||||
}
|
||||
|
||||
write_block "$COMMIT_MSG_HOOK" "$(commit_msg_block)"
|
||||
echo "Updated: $COMMIT_MSG_HOOK (conventional commits check)"
|
||||
|
||||
# --- pre-commit: shellcheck + jq/yq + SKILL.md frontmatter ---
|
||||
|
||||
PRE_COMMIT_HOOK="$TARGET/.git/hooks/pre-commit"
|
||||
ensure_hook "$PRE_COMMIT_HOOK"
|
||||
|
||||
pre_commit_block() {
|
||||
cat <<'BLOCK'
|
||||
staged=$(git diff --cached --name-only --diff-filter=ACM)
|
||||
|
||||
# shellcheck on staged .sh files
|
||||
if command -v shellcheck &>/dev/null; then
|
||||
while IFS= read -r f; do
|
||||
[[ -f "$f" ]] && shellcheck -x "$f"
|
||||
done < <(echo "$staged" | grep '\.sh$' || true)
|
||||
else
|
||||
echo "Warning: shellcheck not installed — shell script linting skipped" >&2
|
||||
fi
|
||||
|
||||
# jq validation on staged .json files
|
||||
if command -v jq &>/dev/null; then
|
||||
while IFS= read -r f; do
|
||||
[[ -f "$f" ]] && jq . "$f" > /dev/null
|
||||
done < <(echo "$staged" | grep '\.json$' || true)
|
||||
else
|
||||
echo "Warning: jq not installed — JSON validation skipped" >&2
|
||||
fi
|
||||
|
||||
# yq validation on staged .yaml/.yml files
|
||||
if command -v yq &>/dev/null; then
|
||||
while IFS= read -r f; do
|
||||
[[ -f "$f" ]] && yq eval '.' "$f" > /dev/null
|
||||
done < <(echo "$staged" | grep -E '\.(yaml|yml)$' || true)
|
||||
else
|
||||
echo "Warning: yq not installed — YAML validation skipped" >&2
|
||||
fi
|
||||
|
||||
# SKILL.md frontmatter: must have name: and description:
|
||||
while IFS= read -r f; do
|
||||
if [[ -f "$f" ]]; then
|
||||
if ! grep -q '^name:' "$f" || ! grep -q '^description' "$f"; then
|
||||
echo "ERROR: $f is missing required frontmatter fields (name: and description:)" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done < <(echo "$staged" | grep 'SKILL\.md$' || true)
|
||||
BLOCK
|
||||
}
|
||||
|
||||
write_block "$PRE_COMMIT_HOOK" "$(pre_commit_block)"
|
||||
echo "Updated: $PRE_COMMIT_HOOK (shellcheck + jq/yq + SKILL.md validation)"
|
||||
|
||||
# --- pre-push: test suite + manifest cross-reference ---
|
||||
|
||||
PRE_PUSH_HOOK="$TARGET/.git/hooks/pre-push"
|
||||
ensure_hook "$PRE_PUSH_HOOK"
|
||||
|
||||
pre_push_block() {
|
||||
local script_dir="$SCRIPT_DIR"
|
||||
cat <<BLOCK
|
||||
HOOKS_SCRIPT_DIR="$script_dir"
|
||||
REPO_ROOT="\$(git rev-parse --show-toplevel)"
|
||||
|
||||
bash "\$REPO_ROOT/tests/run-tests.sh"
|
||||
|
||||
echo "Checking manifests..."
|
||||
bash "\$HOOKS_SCRIPT_DIR/check-manifests.sh" "\$REPO_ROOT"
|
||||
BLOCK
|
||||
}
|
||||
|
||||
write_block "$PRE_PUSH_HOOK" "$(pre_push_block)"
|
||||
echo "Updated: $PRE_PUSH_HOOK (test suite + manifest check)"
|
||||
|
||||
echo ""
|
||||
echo "Done. Hooks installed in $TARGET/.git/hooks/"
|
||||
echo "To skip on a single push: git push --no-verify"
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Installs @neuledge/context globally at a pinned version.
|
||||
# Usage: setup-neuledge-context.sh [VERSION]
|
||||
# VERSION — npm version string (default: 1.2.0)
|
||||
# Idempotent: skips install if already at the target version.
|
||||
|
||||
TARGET_VERSION="${1:-1.2.0}"
|
||||
|
||||
current_version() {
|
||||
context --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo ""
|
||||
}
|
||||
|
||||
CURRENT="$(current_version)"
|
||||
|
||||
if [ "$CURRENT" = "$TARGET_VERSION" ]; then
|
||||
echo "Already at @neuledge/context@${TARGET_VERSION} — nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$CURRENT" ]; then
|
||||
echo "Upgrading @neuledge/context from ${CURRENT} to ${TARGET_VERSION}..."
|
||||
else
|
||||
echo "Installing @neuledge/context@${TARGET_VERSION}..."
|
||||
fi
|
||||
|
||||
npm install -g "@neuledge/context@${TARGET_VERSION}"
|
||||
|
||||
INSTALLED="$(current_version)"
|
||||
if [ "$INSTALLED" = "$TARGET_VERSION" ]; then
|
||||
echo "Done: @neuledge/context@${TARGET_VERSION} installed."
|
||||
else
|
||||
echo "Error: expected version ${TARGET_VERSION} but got '${INSTALLED}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SCRIPT="$REPO_ROOT/scripts/setup-gitleaks.sh"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
# Fake gitleaks binary — prevents the install step from running during tests
|
||||
FAKE_BIN="$(mktemp -d)"
|
||||
trap 'rm -rf "$FAKE_BIN"' EXIT
|
||||
printf '#!/bin/sh\necho "gitleaks fake"\n' > "$FAKE_BIN/gitleaks"
|
||||
chmod +x "$FAKE_BIN/gitleaks"
|
||||
export PATH="$FAKE_BIN:$PATH"
|
||||
|
||||
# Helper: create an isolated git repo in a temp dir
|
||||
make_repo() {
|
||||
local dir
|
||||
dir="$(mktemp -d)"
|
||||
git -C "$dir" init -q
|
||||
echo "$dir"
|
||||
}
|
||||
|
||||
# Helper: run setup script against a target repo; capture output; always return exit code
|
||||
run_setup() {
|
||||
local target="$1"
|
||||
bash "$SCRIPT" "$target" 2>&1
|
||||
}
|
||||
|
||||
# --- 1. Rejects non-git directory ---
|
||||
echo ""
|
||||
echo "--- rejects non-git directory ---"
|
||||
NON_GIT="$(mktemp -d)"
|
||||
trap 'rm -rf "$NON_GIT"' EXIT
|
||||
if bash "$SCRIPT" "$NON_GIT" >/dev/null 2>&1; then
|
||||
fail "exited 0 for non-git directory — expected exit 1"
|
||||
else
|
||||
pass "exits non-zero for non-git directory"
|
||||
fi
|
||||
|
||||
# --- 2. Config deployed ---
|
||||
echo ""
|
||||
echo "--- config deployed to target repo ---"
|
||||
REPO="$(make_repo)"
|
||||
trap 'rm -rf "$REPO"' EXIT
|
||||
run_setup "$REPO" > /dev/null
|
||||
if [ -f "$REPO/.gitleaks.toml" ]; then
|
||||
pass ".gitleaks.toml created in target repo"
|
||||
else
|
||||
fail ".gitleaks.toml missing from target repo"
|
||||
fi
|
||||
if diff -q "$REPO_ROOT/scripts/gitleaks.toml" "$REPO/.gitleaks.toml" > /dev/null 2>&1; then
|
||||
pass ".gitleaks.toml matches the template"
|
||||
else
|
||||
fail ".gitleaks.toml content differs from template"
|
||||
fi
|
||||
|
||||
# --- 3. Hook created from scratch ---
|
||||
echo ""
|
||||
echo "--- hook created when none exists ---"
|
||||
REPO2="$(make_repo)"
|
||||
trap 'rm -rf "$REPO2"' EXIT
|
||||
run_setup "$REPO2" > /dev/null
|
||||
HOOK="$REPO2/.git/hooks/pre-commit"
|
||||
if [ -f "$HOOK" ]; then
|
||||
pass "pre-commit hook created"
|
||||
else
|
||||
fail "pre-commit hook not created"
|
||||
fi
|
||||
if [ -x "$HOOK" ]; then
|
||||
pass "pre-commit hook is executable"
|
||||
else
|
||||
fail "pre-commit hook is not executable"
|
||||
fi
|
||||
if head -1 "$HOOK" | grep -q "^#!"; then
|
||||
pass "pre-commit hook has a shebang"
|
||||
else
|
||||
fail "pre-commit hook missing shebang"
|
||||
fi
|
||||
if grep -q "gitleaks git --staged" "$HOOK"; then
|
||||
pass "pre-commit hook contains gitleaks command"
|
||||
else
|
||||
fail "pre-commit hook missing gitleaks command"
|
||||
fi
|
||||
if grep -q "# managed by setup-gitleaks.sh" "$HOOK"; then
|
||||
pass "pre-commit hook contains idempotency marker"
|
||||
else
|
||||
fail "pre-commit hook missing idempotency marker"
|
||||
fi
|
||||
|
||||
# --- 4. Appends to existing hook; existing content retained ---
|
||||
echo ""
|
||||
echo "--- appends to existing hook; prior content retained ---"
|
||||
REPO3="$(make_repo)"
|
||||
trap 'rm -rf "$REPO3"' EXIT
|
||||
HOOK3="$REPO3/.git/hooks/pre-commit"
|
||||
printf '#!/usr/bin/env bash\nnpm test\n' > "$HOOK3"
|
||||
chmod +x "$HOOK3"
|
||||
run_setup "$REPO3" > /dev/null
|
||||
if grep -q "npm test" "$HOOK3"; then
|
||||
pass "existing hook content retained after append"
|
||||
else
|
||||
fail "existing hook content lost after append"
|
||||
fi
|
||||
if grep -q "gitleaks git --staged" "$HOOK3"; then
|
||||
pass "gitleaks block appended to existing hook"
|
||||
else
|
||||
fail "gitleaks block missing after append"
|
||||
fi
|
||||
|
||||
# --- 5. Second run replaces stale block; existing content still retained ---
|
||||
echo ""
|
||||
echo "--- second run replaces stale block; existing content still retained ---"
|
||||
# Corrupt the gitleaks block to simulate stale content from an older version
|
||||
sed -i 's/gitleaks git --staged/gitleaks protect --staged/' "$HOOK3"
|
||||
run_setup "$REPO3" > /dev/null
|
||||
if grep -q "npm test" "$HOOK3"; then
|
||||
pass "existing content retained after block replacement"
|
||||
else
|
||||
fail "existing content lost after block replacement"
|
||||
fi
|
||||
marker_count="$(grep -c "# managed by setup-gitleaks.sh" "$HOOK3")"
|
||||
if [ "$marker_count" -eq 1 ]; then
|
||||
pass "gitleaks block appears exactly once after second run"
|
||||
else
|
||||
fail "gitleaks block duplicated — found $marker_count occurrences of marker"
|
||||
fi
|
||||
if grep -q "gitleaks git --staged" "$HOOK3"; then
|
||||
pass "stale gitleaks command replaced with current command"
|
||||
else
|
||||
fail "stale gitleaks command not replaced — block was skipped, not updated"
|
||||
fi
|
||||
|
||||
# --- 6. Third run still idempotent ---
|
||||
echo ""
|
||||
echo "--- repeated runs stay idempotent ---"
|
||||
run_setup "$REPO3" > /dev/null
|
||||
run_setup "$REPO3" > /dev/null
|
||||
marker_count="$(grep -c "# managed by setup-gitleaks.sh" "$HOOK3")"
|
||||
if [ "$marker_count" -eq 1 ]; then
|
||||
pass "gitleaks block still appears exactly once after four total runs"
|
||||
else
|
||||
fail "gitleaks block duplicated — found $marker_count occurrences after four runs"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
@@ -1,210 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SCRIPT="$REPO_ROOT/scripts/setup-hooks.sh"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
# Helper: make a bare git repo with no hooks yet
|
||||
make_repo() {
|
||||
local dir
|
||||
dir="$(mktemp -d)"
|
||||
git -C "$dir" init -q
|
||||
echo "$dir"
|
||||
}
|
||||
|
||||
# Fake binaries for tools we don't want to install-check during tests
|
||||
FAKE_BIN="$(mktemp -d)"
|
||||
trap 'rm -rf "$FAKE_BIN"' EXIT
|
||||
for tool in shellcheck jq yq; do
|
||||
printf '#!/bin/sh\necho "fake %s"\n' "$tool" > "$FAKE_BIN/$tool"
|
||||
chmod +x "$FAKE_BIN/$tool"
|
||||
done
|
||||
export PATH="$FAKE_BIN:$PATH"
|
||||
|
||||
# --- 1. Rejects non-git directory ---
|
||||
echo ""
|
||||
echo "--- rejects non-git directory ---"
|
||||
NON_GIT="$(mktemp -d)"
|
||||
trap 'rm -rf "$NON_GIT"' EXIT
|
||||
if bash "$SCRIPT" "$NON_GIT" > /dev/null 2>&1; then
|
||||
fail "exited 0 for non-git directory — expected exit 1"
|
||||
else
|
||||
pass "exits non-zero for non-git directory"
|
||||
fi
|
||||
|
||||
# --- 2. Creates commit-msg hook ---
|
||||
echo ""
|
||||
echo "--- creates commit-msg hook ---"
|
||||
REPO="$(make_repo)"
|
||||
trap 'rm -rf "$REPO"' EXIT
|
||||
bash "$SCRIPT" "$REPO" > /dev/null 2>&1
|
||||
HOOK="$REPO/.git/hooks/commit-msg"
|
||||
if [[ -f "$HOOK" ]]; then
|
||||
pass "commit-msg hook file created"
|
||||
else
|
||||
fail "commit-msg hook not created"
|
||||
fi
|
||||
if [[ -x "$HOOK" ]]; then
|
||||
pass "commit-msg hook is executable"
|
||||
else
|
||||
fail "commit-msg hook is not executable"
|
||||
fi
|
||||
if grep -q "# managed by setup-hooks.sh" "$HOOK"; then
|
||||
pass "commit-msg hook contains idempotency marker"
|
||||
else
|
||||
fail "commit-msg hook missing idempotency marker"
|
||||
fi
|
||||
|
||||
# --- 3. commit-msg hook validates conventional commits ---
|
||||
echo ""
|
||||
echo "--- commit-msg hook: valid message passes ---"
|
||||
REPO2="$(make_repo)"
|
||||
trap 'rm -rf "$REPO2"' EXIT
|
||||
bash "$SCRIPT" "$REPO2" > /dev/null 2>&1
|
||||
HOOK2="$REPO2/.git/hooks/commit-msg"
|
||||
TMPFILE="$(mktemp)"
|
||||
trap 'rm -f "$TMPFILE"' EXIT
|
||||
|
||||
for valid_msg in "feat: add validation" "fix(core): correct path resolution" "chore!: drop python dep" "docs: update readme" "refactor(hooks): extract marker logic"; do
|
||||
echo "$valid_msg" > "$TMPFILE"
|
||||
if bash "$HOOK2" "$TMPFILE" > /dev/null 2>&1; then
|
||||
pass "commit-msg hook accepts: $valid_msg"
|
||||
else
|
||||
fail "commit-msg hook wrongly rejected: $valid_msg"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- commit-msg hook: invalid message is rejected ---"
|
||||
for invalid_msg in "added some stuff" "WIP" "Fix the thing" "FEAT: bad case" "feat bad colon"; do
|
||||
echo "$invalid_msg" > "$TMPFILE"
|
||||
if bash "$HOOK2" "$TMPFILE" > /dev/null 2>&1; then
|
||||
fail "commit-msg hook wrongly accepted: $invalid_msg"
|
||||
else
|
||||
pass "commit-msg hook rejects: $invalid_msg"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 4. Appends pre-commit validation block ---
|
||||
echo ""
|
||||
echo "--- appends validation block to pre-commit hook ---"
|
||||
REPO3="$(make_repo)"
|
||||
trap 'rm -rf "$REPO3"' EXIT
|
||||
bash "$SCRIPT" "$REPO3" > /dev/null 2>&1
|
||||
PRE_COMMIT="$REPO3/.git/hooks/pre-commit"
|
||||
if [[ -f "$PRE_COMMIT" ]]; then
|
||||
pass "pre-commit hook created"
|
||||
else
|
||||
fail "pre-commit hook not created"
|
||||
fi
|
||||
if grep -q "shellcheck" "$PRE_COMMIT"; then
|
||||
pass "pre-commit hook contains shellcheck"
|
||||
else
|
||||
fail "pre-commit hook missing shellcheck"
|
||||
fi
|
||||
if grep -q "jq" "$PRE_COMMIT"; then
|
||||
pass "pre-commit hook contains jq"
|
||||
else
|
||||
fail "pre-commit hook missing jq"
|
||||
fi
|
||||
if grep -q "SKILL.md" "$PRE_COMMIT"; then
|
||||
pass "pre-commit hook contains SKILL.md frontmatter check"
|
||||
else
|
||||
fail "pre-commit hook missing SKILL.md frontmatter check"
|
||||
fi
|
||||
|
||||
# --- 5. Creates pre-push hook ---
|
||||
echo ""
|
||||
echo "--- creates pre-push hook ---"
|
||||
REPO4="$(make_repo)"
|
||||
trap 'rm -rf "$REPO4"' EXIT
|
||||
bash "$SCRIPT" "$REPO4" > /dev/null 2>&1
|
||||
PUSH_HOOK="$REPO4/.git/hooks/pre-push"
|
||||
if [[ -f "$PUSH_HOOK" ]]; then
|
||||
pass "pre-push hook created"
|
||||
else
|
||||
fail "pre-push hook not created"
|
||||
fi
|
||||
if [[ -x "$PUSH_HOOK" ]]; then
|
||||
pass "pre-push hook is executable"
|
||||
else
|
||||
fail "pre-push hook is not executable"
|
||||
fi
|
||||
if grep -q "check-manifests" "$PUSH_HOOK"; then
|
||||
pass "pre-push hook calls check-manifests.sh"
|
||||
else
|
||||
fail "pre-push hook missing check-manifests.sh call"
|
||||
fi
|
||||
if grep -q "run-tests.sh" "$PUSH_HOOK"; then
|
||||
pass "pre-push hook calls run-tests.sh"
|
||||
else
|
||||
fail "pre-push hook missing run-tests.sh call"
|
||||
fi
|
||||
|
||||
# --- 6. Idempotent: second run replaces each block exactly once ---
|
||||
echo ""
|
||||
echo "--- idempotent: second run does not duplicate blocks ---"
|
||||
REPO5="$(make_repo)"
|
||||
trap 'rm -rf "$REPO5"' EXIT
|
||||
bash "$SCRIPT" "$REPO5" > /dev/null 2>&1
|
||||
bash "$SCRIPT" "$REPO5" > /dev/null 2>&1
|
||||
bash "$SCRIPT" "$REPO5" > /dev/null 2>&1
|
||||
|
||||
for hook_file in "$REPO5/.git/hooks/commit-msg" "$REPO5/.git/hooks/pre-commit" "$REPO5/.git/hooks/pre-push"; do
|
||||
count=$(grep -c "# managed by setup-hooks.sh" "$hook_file" || true)
|
||||
hook_name="$(basename "$hook_file")"
|
||||
if [[ "$count" -eq 1 ]]; then
|
||||
pass "idempotent: $hook_name marker appears exactly once after 3 runs"
|
||||
else
|
||||
fail "idempotent: $hook_name marker appears $count times — block duplicated"
|
||||
fi
|
||||
if [[ -x "$hook_file" ]]; then
|
||||
pass "idempotent: $hook_name remains executable after 3 runs"
|
||||
else
|
||||
fail "idempotent: $hook_name lost executable bit after repeated runs"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 7. Setup installs tools; no "not installed" warnings when tools present ---
|
||||
echo ""
|
||||
echo "--- setup does not warn when tools are available ---"
|
||||
REPO6="$(make_repo)"
|
||||
trap 'rm -rf "$REPO6"' EXIT
|
||||
output6="$(bash "$SCRIPT" "$REPO6" 2>&1)"
|
||||
for tool in shellcheck jq yq; do
|
||||
if echo "$output6" | grep -qi "$tool not installed\|$tool.*not found"; then
|
||||
fail "setup warned about missing $tool — should install or already be present"
|
||||
else
|
||||
pass "setup emits no 'not installed' warning for: $tool (present or installed)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- pre-commit hook retains runtime fallback for missing tools ---"
|
||||
PRE_COMMIT6="$REPO6/.git/hooks/pre-commit"
|
||||
for tool in shellcheck jq yq; do
|
||||
if grep -q "Warning:.*$tool\|$tool.*not installed\|$tool.*skipped" "$PRE_COMMIT6"; then
|
||||
pass "pre-commit hook has runtime fallback for missing: $tool"
|
||||
else
|
||||
fail "pre-commit hook missing runtime fallback for: $tool"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- setup exits 0 when tools are present ---"
|
||||
REPO7="$(make_repo)"
|
||||
trap 'rm -rf "$REPO7"' EXIT
|
||||
if bash "$SCRIPT" "$REPO7" > /dev/null 2>&1; then
|
||||
pass "setup exits 0 when tools are present"
|
||||
else
|
||||
fail "setup exited non-zero unexpectedly"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
Reference in new issue
Block a user