From 51bc146d234b0a9df24941998e3d938309cc6446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Sun, 4 Oct 2026 20:19:34 -0500 Subject: [PATCH 1/4] fix: no escaped quotes in the hooks; release v1.0.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Directory's blocking "." stayed after v1.0.5, still named by triage-first.sh. Its reminder is the one line, unchanged since v1.0.1, with an escaped quote and a ">" before a period: \"Fast lane — …; model: .\" — a parser that doesn't take \" as a quote ends the string there and reads `opus>.` as a redirect to ".". The reminder now quotes the triage line with single quotes, same text. check-env-declared builds its patterns from quote variables (the 15 expand byte for byte as before), _lib.sh unquotes settings without escapes, and session-context's detached-HEAD line no longer ends ".". The static check refuses escaped quotes and ">." in the plugin's hooks; a negative control flags them on main. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 19 ++++++++++++ README.md | 2 +- evals/static/check-skills.sh | 2 ++ plugins/aplyca-adf/.claude-plugin/plugin.json | 2 +- plugins/aplyca-adf/README.md | 2 +- plugins/aplyca-adf/hooks/_lib.sh | 14 +++++---- .../aplyca-adf/hooks/check-env-declared.sh | 29 ++++++++++--------- plugins/aplyca-adf/hooks/session-context.sh | 2 +- plugins/aplyca-adf/hooks/triage-first.sh | 2 +- skeleton/.claude/hooks/_lib.sh | 14 +++++---- skeleton/.claude/hooks/check-env-declared.sh | 29 ++++++++++--------- skeleton/.claude/hooks/session-context.sh | 2 +- skeleton/.claude/hooks/triage-first.sh | 2 +- 13 files changed, 74 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c0b18..07284d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,25 @@ For each entry, **Upgrade impact** classifies the change against the [three-buck ## Unreleased +## v1.0.6 — 2026-10-04 — No escaped quotes in the hooks + +A patch release. The Directory's blocking `.` stayed after v1.0.5, still named by `triage-first.sh`. +Its reminder message is the one line, unchanged since v1.0.1, with both an escaped quote and a `>` +before a period: `\"Fast lane — …; model: .\"`. A parser that doesn't take `\"` as a +quote ends the string there and reads `opus>.` as a redirect to the folder `.`. + +#### Changed +- **`triage-first.sh`'s reminder** quotes the one-line triage with single quotes and ends it without + a period after `>`. The text is the same. +- **`check-env-declared.sh`** builds its search patterns from variables for the quote characters + instead of escaping them; the 15 patterns expand byte for byte as before. +- **`_lib.sh`'s `read_settings`** unquotes values without escaped quotes. +- **`session-context.sh`**'s detached-HEAD line no longer ends `.` +- **The static check** refuses escaped quotes and a `>` before a period in the plugin's hooks. + +#### Upgrade impact +- **Overwrite:** `_lib.sh`, `check-env-declared.sh`, `session-context.sh`, `triage-first.sh`. + ## v1.0.5 — 2026-10-04 — What the Directory's validator was pointing at A patch release. The validator lists the files it couldn't check in a fixed order — the hooks in the diff --git a/README.md b/README.md index e6deb49..da16c73 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Updates are deliberate: `/aplyca-adf:upgrade` moves a project from one release t pull request and keeps its customizations. Read the **Upgrade impact** of each release in [CHANGELOG.md](CHANGELOG.md) first. From v1.0.0, releases follow semantic versioning ([decision 0017](docs/decisions/0017-semantic-versioning.md)), so a major release asks something of -your team. The latest, **v1.0.5** (2026-10-04), is a patch for the Claude Directory. **v1.0.0** +your team. The latest, **v1.0.6** (2026-10-04), is a patch for the Claude Directory. **v1.0.0** (2026-10-02) renamed the plugin `aplyca-adf` and opens with the order to upgrade in. A baseline older than `7383422` takes that release's order first, and one older than `3eb7777` takes its three fixes before that — they affect every adopted repository. diff --git a/evals/static/check-skills.sh b/evals/static/check-skills.sh index 587cd88..3036e0f 100755 --- a/evals/static/check-skills.sh +++ b/evals/static/check-skills.sh @@ -781,6 +781,8 @@ rules = [ (r'\bcd\s+"\$\(', "changes into a computed directory"), (r'\b(jq|python3?)\b[^|]*\s"\$(?!1"|\{CLAUDE_PLUGIN_ROOT\})', "hands a program interpreter a computed path"), (r'\$\{TMPDIR:-/tmp\}', "builds a path from a defaulted variable"), + (r'\\["\x27]', "escapes a quote, which the parser of the validator misreads"), + (r'>\.', "has a greater-than sign before a period, read as a redirect to the folder"), ] for path in sorted(glob.glob(os.path.join(hooks_dir, "*.sh"))): for number, line in enumerate(open(path, encoding="utf-8"), 1): diff --git a/plugins/aplyca-adf/.claude-plugin/plugin.json b/plugins/aplyca-adf/.claude-plugin/plugin.json index 4447334..c2b8188 100644 --- a/plugins/aplyca-adf/.claude-plugin/plugin.json +++ b/plugins/aplyca-adf/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "aplyca-adf", "description": "The Agentic Development Framework for Claude Code. /adopt bootstraps a repository — skeleton, optional modules, guardrail hooks, verified facts — committed or packaged; /upgrade moves an adopted repository to a newer release; /cost-report shows what agent sessions cost, from local transcripts. In a packaged project it also carries the framework's skills, agents, workflows, and hooks, pinned to a release; in a committed project those step aside for the committed copies.", - "version": "1.0.5", + "version": "1.0.6", "author": { "name": "Aplyca", "email": "dev@aplyca.com" diff --git a/plugins/aplyca-adf/README.md b/plugins/aplyca-adf/README.md index bdacca3..bc8e7a3 100644 --- a/plugins/aplyca-adf/README.md +++ b/plugins/aplyca-adf/README.md @@ -141,7 +141,7 @@ the plugin by a relative path. A machine where nobody trusts the folder — CI { "extraKnownMarketplaces": { "aplyca": { - "source": { "source": "github", "repo": "aplyca/AgenticDevelopmentFramework", "ref": "v1.0.5" } + "source": { "source": "github", "repo": "aplyca/AgenticDevelopmentFramework", "ref": "v1.0.6" } } }, "enabledPlugins": { "aplyca-adf@aplyca": true } diff --git a/plugins/aplyca-adf/hooks/_lib.sh b/plugins/aplyca-adf/hooks/_lib.sh index 99c692f..bc6455c 100755 --- a/plugins/aplyca-adf/hooks/_lib.sh +++ b/plugins/aplyca-adf/hooks/_lib.sh @@ -32,7 +32,7 @@ in_words() { # read_settings … — set each listed KEY from its KEY=value line. The file is data and # never runs: double- or single-quoted or bare values, indentation and trailing comments. read_settings() { - local file="$1" line setting value + local file="$1" line setting value quote shift while IFS= read -r line || [ -n "$line" ]; do line="${line#"${line%%[![:space:]]*}"}" @@ -40,11 +40,13 @@ read_settings() { [[ $setting =~ ^[A-Z_][A-Z0-9_]*$ ]] || continue in_words "$setting" "$*" || continue value="${line#*=}" - case "$value" in - \"*) value="${value#\"}" && value="${value%%\"*}" ;; - \'*) value="${value#\'}" && value="${value%%\'*}" ;; - *) value="${value%%[[:space:]]*}" ;; - esac + quote="${value:0:1}" + if [ "$quote" = '"' ] || [ "$quote" = "'" ]; then + value="${value:1}" + value="${value%%"$quote"*}" + else + value="${value%%[[:space:]]*}" + fi printf -v "$setting" '%s' "$value" done < <(cat "$file") } diff --git a/plugins/aplyca-adf/hooks/check-env-declared.sh b/plugins/aplyca-adf/hooks/check-env-declared.sh index e046337..28631ef 100755 --- a/plugins/aplyca-adf/hooks/check-env-declared.sh +++ b/plugins/aplyca-adf/hooks/check-env-declared.sh @@ -35,23 +35,24 @@ if [ -z "$template" ]; then fi [ -n "$template" ] && [ -f "$root/$template" ] || exit 0 -name='([A-Z_][A-Z0-9_]*)' +name='([A-Z_][A-Z0-9_]*)' dq='"' dollar='$' +quote="['$dq]" patterns=( "process\\.env\\.$name" - "process\\.env\\[['\"]$name['\"]\\]" + "process\\.env\\[$quote$name$quote\\]" "import\\.meta\\.env\\.$name" - "os\\.environ\\[['\"]$name['\"]\\]" - "os\\.environ\\.get\\(['\"]$name['\"]" - "getenv\\(['\"]$name['\"]" - "ENV\\[['\"]$name['\"]\\]" - "ENV\\.fetch\\(['\"]$name['\"]" - "os\\.Getenv\\(\"$name\"" - "os\\.LookupEnv\\(\"$name\"" - "\\\$_ENV\\[['\"]$name['\"]\\]" - "env::var\\(\"$name\"" - "System\\.getenv\\(\"$name\"" - "Environment\\.GetEnvironmentVariable\\(\"$name\"" - "System\\.get_env\\(\"$name\"" + "os\\.environ\\[$quote$name$quote\\]" + "os\\.environ\\.get\\($quote$name$quote" + "getenv\\($quote$name$quote" + "ENV\\[$quote$name$quote\\]" + "ENV\\.fetch\\($quote$name$quote" + "os\\.Getenv\\($dq$name$dq" + "os\\.LookupEnv\\($dq$name$dq" + "\\${dollar}_ENV\\[$quote$name$quote\\]" + "env::var\\($dq$name$dq" + "System\\.getenv\\($dq$name$dq" + "Environment\\.GetEnvironmentVariable\\($dq$name$dq" + "System\\.get_env\\($dq$name$dq" ) found="" diff --git a/plugins/aplyca-adf/hooks/session-context.sh b/plugins/aplyca-adf/hooks/session-context.sh index 88b8b9e..96f1620 100755 --- a/plugins/aplyca-adf/hooks/session-context.sh +++ b/plugins/aplyca-adf/hooks/session-context.sh @@ -96,7 +96,7 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then if [[ $branch =~ $generated_name ]] || [ "${branch#*/}" = "$branch" ]; then generated=1; fi fi if [ "$branch" = "detached HEAD" ]; then - echo "- Detached HEAD: after triage, create the task's branch — git switch -c /." + echo "- Detached HEAD: after triage, create the task's branch with git switch -c /" elif [ -n "$generated" ]; then echo "- The branch name is generated ($branch): after triage, rename it — git branch -m / — so it joins its spec folder and /aplyca-adf:open-pr takes it." fi diff --git a/plugins/aplyca-adf/hooks/triage-first.sh b/plugins/aplyca-adf/hooks/triage-first.sh index 56e6605..3ec25e6 100755 --- a/plugins/aplyca-adf/hooks/triage-first.sh +++ b/plugins/aplyca-adf/hooks/triage-first.sh @@ -41,4 +41,4 @@ if [ -z "$(printf '%s' "$replies" | tr -d '[:space:]')" ]; then else seen="none of your replies in this session names a lane (fast, careful, or full), so the developer has seen no triage — what you decided while thinking isn't shown to anyone" fi -block "$seen. Before $change, write the triage as your next message. For a small change, one line: \"Fast lane — ; done when ; files: ; model: .\" Then run it again; this reminder shows once." +block "$seen. Before $change, write the triage as your next message. For a small change, one line: 'Fast lane — ; done when ; files: ; model: ' — then run it again; this reminder shows once." diff --git a/skeleton/.claude/hooks/_lib.sh b/skeleton/.claude/hooks/_lib.sh index 824296e..994ac35 100644 --- a/skeleton/.claude/hooks/_lib.sh +++ b/skeleton/.claude/hooks/_lib.sh @@ -32,7 +32,7 @@ in_words() { # read_settings … — set each listed KEY from its KEY=value line. The file is data and # never runs: double- or single-quoted or bare values, indentation and trailing comments. read_settings() { - local file="$1" line setting value + local file="$1" line setting value quote shift while IFS= read -r line || [ -n "$line" ]; do line="${line#"${line%%[![:space:]]*}"}" @@ -40,11 +40,13 @@ read_settings() { [[ $setting =~ ^[A-Z_][A-Z0-9_]*$ ]] || continue in_words "$setting" "$*" || continue value="${line#*=}" - case "$value" in - \"*) value="${value#\"}" && value="${value%%\"*}" ;; - \'*) value="${value#\'}" && value="${value%%\'*}" ;; - *) value="${value%%[[:space:]]*}" ;; - esac + quote="${value:0:1}" + if [ "$quote" = '"' ] || [ "$quote" = "'" ]; then + value="${value:1}" + value="${value%%"$quote"*}" + else + value="${value%%[[:space:]]*}" + fi printf -v "$setting" '%s' "$value" done < <(cat "$file") } diff --git a/skeleton/.claude/hooks/check-env-declared.sh b/skeleton/.claude/hooks/check-env-declared.sh index 1e02528..0c722ce 100755 --- a/skeleton/.claude/hooks/check-env-declared.sh +++ b/skeleton/.claude/hooks/check-env-declared.sh @@ -35,23 +35,24 @@ if [ -z "$template" ]; then fi [ -n "$template" ] && [ -f "$root/$template" ] || exit 0 -name='([A-Z_][A-Z0-9_]*)' +name='([A-Z_][A-Z0-9_]*)' dq='"' dollar='$' +quote="['$dq]" patterns=( "process\\.env\\.$name" - "process\\.env\\[['\"]$name['\"]\\]" + "process\\.env\\[$quote$name$quote\\]" "import\\.meta\\.env\\.$name" - "os\\.environ\\[['\"]$name['\"]\\]" - "os\\.environ\\.get\\(['\"]$name['\"]" - "getenv\\(['\"]$name['\"]" - "ENV\\[['\"]$name['\"]\\]" - "ENV\\.fetch\\(['\"]$name['\"]" - "os\\.Getenv\\(\"$name\"" - "os\\.LookupEnv\\(\"$name\"" - "\\\$_ENV\\[['\"]$name['\"]\\]" - "env::var\\(\"$name\"" - "System\\.getenv\\(\"$name\"" - "Environment\\.GetEnvironmentVariable\\(\"$name\"" - "System\\.get_env\\(\"$name\"" + "os\\.environ\\[$quote$name$quote\\]" + "os\\.environ\\.get\\($quote$name$quote" + "getenv\\($quote$name$quote" + "ENV\\[$quote$name$quote\\]" + "ENV\\.fetch\\($quote$name$quote" + "os\\.Getenv\\($dq$name$dq" + "os\\.LookupEnv\\($dq$name$dq" + "\\${dollar}_ENV\\[$quote$name$quote\\]" + "env::var\\($dq$name$dq" + "System\\.getenv\\($dq$name$dq" + "Environment\\.GetEnvironmentVariable\\($dq$name$dq" + "System\\.get_env\\($dq$name$dq" ) found="" diff --git a/skeleton/.claude/hooks/session-context.sh b/skeleton/.claude/hooks/session-context.sh index c167fc2..fab8535 100755 --- a/skeleton/.claude/hooks/session-context.sh +++ b/skeleton/.claude/hooks/session-context.sh @@ -96,7 +96,7 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then if [[ $branch =~ $generated_name ]] || [ "${branch#*/}" = "$branch" ]; then generated=1; fi fi if [ "$branch" = "detached HEAD" ]; then - echo "- Detached HEAD: after triage, create the task's branch — git switch -c /." + echo "- Detached HEAD: after triage, create the task's branch with git switch -c /" elif [ -n "$generated" ]; then echo "- The branch name is generated ($branch): after triage, rename it — git branch -m / — so it joins its spec folder and /open-pr takes it." fi diff --git a/skeleton/.claude/hooks/triage-first.sh b/skeleton/.claude/hooks/triage-first.sh index 8431716..b7052d5 100755 --- a/skeleton/.claude/hooks/triage-first.sh +++ b/skeleton/.claude/hooks/triage-first.sh @@ -41,4 +41,4 @@ if [ -z "$(printf '%s' "$replies" | tr -d '[:space:]')" ]; then else seen="none of your replies in this session names a lane (fast, careful, or full), so the developer has seen no triage — what you decided while thinking isn't shown to anyone" fi -block "$seen. Before $change, write the triage as your next message. For a small change, one line: \"Fast lane — ; done when ; files: ; model: .\" Then run it again; this reminder shows once." +block "$seen. Before $change, write the triage as your next message. For a small change, one line: 'Fast lane — ; done when ; files: ; model: ' — then run it again; this reminder shows once." From 83935397a469053a8b81dcb4eafedef3e781f018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Sun, 4 Oct 2026 20:24:20 -0500 Subject: [PATCH 2/4] fix: keep only the reminder fix; restore check-env-declared and _lib.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The branch scan showed the reminder fix worked — the blocking "." no longer follows triage-first.sh — but a new "." followed check-env-declared.sh and the _lib.sh it loads, both clean in v1.0.5 and rewritten in the previous commit without evidence. Both go back to v1.0.5 exactly; v1.0.6 is the two message lines alone. The static check drops the escaped-quote rule and keeps the ">." rule. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 ++--- evals/static/check-skills.sh | 1 - plugins/aplyca-adf/hooks/_lib.sh | 14 ++++----- .../aplyca-adf/hooks/check-env-declared.sh | 29 +++++++++---------- skeleton/.claude/hooks/_lib.sh | 14 ++++----- skeleton/.claude/hooks/check-env-declared.sh | 29 +++++++++---------- 6 files changed, 42 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07284d8..89d1894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,14 +21,11 @@ quote ends the string there and reads `opus>.` as a redirect to the folder `.`. #### Changed - **`triage-first.sh`'s reminder** quotes the one-line triage with single quotes and ends it without a period after `>`. The text is the same. -- **`check-env-declared.sh`** builds its search patterns from variables for the quote characters - instead of escaping them; the 15 patterns expand byte for byte as before. -- **`_lib.sh`'s `read_settings`** unquotes values without escaped quotes. - **`session-context.sh`**'s detached-HEAD line no longer ends `.` -- **The static check** refuses escaped quotes and a `>` before a period in the plugin's hooks. +- **The static check** refuses a `>` before a period in the plugin's hooks. #### Upgrade impact -- **Overwrite:** `_lib.sh`, `check-env-declared.sh`, `session-context.sh`, `triage-first.sh`. +- **Overwrite:** `session-context.sh`, `triage-first.sh`. ## v1.0.5 — 2026-10-04 — What the Directory's validator was pointing at diff --git a/evals/static/check-skills.sh b/evals/static/check-skills.sh index 3036e0f..1f1e69a 100755 --- a/evals/static/check-skills.sh +++ b/evals/static/check-skills.sh @@ -781,7 +781,6 @@ rules = [ (r'\bcd\s+"\$\(', "changes into a computed directory"), (r'\b(jq|python3?)\b[^|]*\s"\$(?!1"|\{CLAUDE_PLUGIN_ROOT\})', "hands a program interpreter a computed path"), (r'\$\{TMPDIR:-/tmp\}', "builds a path from a defaulted variable"), - (r'\\["\x27]', "escapes a quote, which the parser of the validator misreads"), (r'>\.', "has a greater-than sign before a period, read as a redirect to the folder"), ] for path in sorted(glob.glob(os.path.join(hooks_dir, "*.sh"))): diff --git a/plugins/aplyca-adf/hooks/_lib.sh b/plugins/aplyca-adf/hooks/_lib.sh index bc6455c..99c692f 100755 --- a/plugins/aplyca-adf/hooks/_lib.sh +++ b/plugins/aplyca-adf/hooks/_lib.sh @@ -32,7 +32,7 @@ in_words() { # read_settings … — set each listed KEY from its KEY=value line. The file is data and # never runs: double- or single-quoted or bare values, indentation and trailing comments. read_settings() { - local file="$1" line setting value quote + local file="$1" line setting value shift while IFS= read -r line || [ -n "$line" ]; do line="${line#"${line%%[![:space:]]*}"}" @@ -40,13 +40,11 @@ read_settings() { [[ $setting =~ ^[A-Z_][A-Z0-9_]*$ ]] || continue in_words "$setting" "$*" || continue value="${line#*=}" - quote="${value:0:1}" - if [ "$quote" = '"' ] || [ "$quote" = "'" ]; then - value="${value:1}" - value="${value%%"$quote"*}" - else - value="${value%%[[:space:]]*}" - fi + case "$value" in + \"*) value="${value#\"}" && value="${value%%\"*}" ;; + \'*) value="${value#\'}" && value="${value%%\'*}" ;; + *) value="${value%%[[:space:]]*}" ;; + esac printf -v "$setting" '%s' "$value" done < <(cat "$file") } diff --git a/plugins/aplyca-adf/hooks/check-env-declared.sh b/plugins/aplyca-adf/hooks/check-env-declared.sh index 28631ef..e046337 100755 --- a/plugins/aplyca-adf/hooks/check-env-declared.sh +++ b/plugins/aplyca-adf/hooks/check-env-declared.sh @@ -35,24 +35,23 @@ if [ -z "$template" ]; then fi [ -n "$template" ] && [ -f "$root/$template" ] || exit 0 -name='([A-Z_][A-Z0-9_]*)' dq='"' dollar='$' -quote="['$dq]" +name='([A-Z_][A-Z0-9_]*)' patterns=( "process\\.env\\.$name" - "process\\.env\\[$quote$name$quote\\]" + "process\\.env\\[['\"]$name['\"]\\]" "import\\.meta\\.env\\.$name" - "os\\.environ\\[$quote$name$quote\\]" - "os\\.environ\\.get\\($quote$name$quote" - "getenv\\($quote$name$quote" - "ENV\\[$quote$name$quote\\]" - "ENV\\.fetch\\($quote$name$quote" - "os\\.Getenv\\($dq$name$dq" - "os\\.LookupEnv\\($dq$name$dq" - "\\${dollar}_ENV\\[$quote$name$quote\\]" - "env::var\\($dq$name$dq" - "System\\.getenv\\($dq$name$dq" - "Environment\\.GetEnvironmentVariable\\($dq$name$dq" - "System\\.get_env\\($dq$name$dq" + "os\\.environ\\[['\"]$name['\"]\\]" + "os\\.environ\\.get\\(['\"]$name['\"]" + "getenv\\(['\"]$name['\"]" + "ENV\\[['\"]$name['\"]\\]" + "ENV\\.fetch\\(['\"]$name['\"]" + "os\\.Getenv\\(\"$name\"" + "os\\.LookupEnv\\(\"$name\"" + "\\\$_ENV\\[['\"]$name['\"]\\]" + "env::var\\(\"$name\"" + "System\\.getenv\\(\"$name\"" + "Environment\\.GetEnvironmentVariable\\(\"$name\"" + "System\\.get_env\\(\"$name\"" ) found="" diff --git a/skeleton/.claude/hooks/_lib.sh b/skeleton/.claude/hooks/_lib.sh index 994ac35..824296e 100644 --- a/skeleton/.claude/hooks/_lib.sh +++ b/skeleton/.claude/hooks/_lib.sh @@ -32,7 +32,7 @@ in_words() { # read_settings … — set each listed KEY from its KEY=value line. The file is data and # never runs: double- or single-quoted or bare values, indentation and trailing comments. read_settings() { - local file="$1" line setting value quote + local file="$1" line setting value shift while IFS= read -r line || [ -n "$line" ]; do line="${line#"${line%%[![:space:]]*}"}" @@ -40,13 +40,11 @@ read_settings() { [[ $setting =~ ^[A-Z_][A-Z0-9_]*$ ]] || continue in_words "$setting" "$*" || continue value="${line#*=}" - quote="${value:0:1}" - if [ "$quote" = '"' ] || [ "$quote" = "'" ]; then - value="${value:1}" - value="${value%%"$quote"*}" - else - value="${value%%[[:space:]]*}" - fi + case "$value" in + \"*) value="${value#\"}" && value="${value%%\"*}" ;; + \'*) value="${value#\'}" && value="${value%%\'*}" ;; + *) value="${value%%[[:space:]]*}" ;; + esac printf -v "$setting" '%s' "$value" done < <(cat "$file") } diff --git a/skeleton/.claude/hooks/check-env-declared.sh b/skeleton/.claude/hooks/check-env-declared.sh index 0c722ce..1e02528 100755 --- a/skeleton/.claude/hooks/check-env-declared.sh +++ b/skeleton/.claude/hooks/check-env-declared.sh @@ -35,24 +35,23 @@ if [ -z "$template" ]; then fi [ -n "$template" ] && [ -f "$root/$template" ] || exit 0 -name='([A-Z_][A-Z0-9_]*)' dq='"' dollar='$' -quote="['$dq]" +name='([A-Z_][A-Z0-9_]*)' patterns=( "process\\.env\\.$name" - "process\\.env\\[$quote$name$quote\\]" + "process\\.env\\[['\"]$name['\"]\\]" "import\\.meta\\.env\\.$name" - "os\\.environ\\[$quote$name$quote\\]" - "os\\.environ\\.get\\($quote$name$quote" - "getenv\\($quote$name$quote" - "ENV\\[$quote$name$quote\\]" - "ENV\\.fetch\\($quote$name$quote" - "os\\.Getenv\\($dq$name$dq" - "os\\.LookupEnv\\($dq$name$dq" - "\\${dollar}_ENV\\[$quote$name$quote\\]" - "env::var\\($dq$name$dq" - "System\\.getenv\\($dq$name$dq" - "Environment\\.GetEnvironmentVariable\\($dq$name$dq" - "System\\.get_env\\($dq$name$dq" + "os\\.environ\\[['\"]$name['\"]\\]" + "os\\.environ\\.get\\(['\"]$name['\"]" + "getenv\\(['\"]$name['\"]" + "ENV\\[['\"]$name['\"]\\]" + "ENV\\.fetch\\(['\"]$name['\"]" + "os\\.Getenv\\(\"$name\"" + "os\\.LookupEnv\\(\"$name\"" + "\\\$_ENV\\[['\"]$name['\"]\\]" + "env::var\\(\"$name\"" + "System\\.getenv\\(\"$name\"" + "Environment\\.GetEnvironmentVariable\\(\"$name\"" + "System\\.get_env\\(\"$name\"" ) found="" From 1e63367015d541ce61bfe468d1888db23f95506b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Sun, 4 Oct 2026 20:45:47 -0500 Subject: [PATCH 3/4] fix: no path outside the plugin in /adopt and /upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The branch scan dropped the "." from the list of files the hooks name, but the blocking finding still names ".". /aplyca-adf:adopt and /aplyca-adf:upgrade named ${CLAUDE_PLUGIN_ROOT}/../.. — from plugins/aplyca-adf, the repository root, outside the plugin's tree, which the validator reports as "." — to find a development checkout. The marketplace's installLocation already is that checkout when the marketplace was added from a local folder, so the path goes. The static check refuses any path that climbs out of the plugin. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 ++++++- evals/static/check-skills.sh | 3 ++- plugins/aplyca-adf/skills/adopt/SKILL.md | 12 ++++++------ plugins/aplyca-adf/skills/upgrade/SKILL.md | 10 +++++----- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d1894..b676c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,12 @@ quote ends the string there and reads `opus>.` as a redirect to the folder `.`. - **`triage-first.sh`'s reminder** quotes the one-line triage with single quotes and ends it without a period after `>`. The text is the same. - **`session-context.sh`**'s detached-HEAD line no longer ends `.` -- **The static check** refuses a `>` before a period in the plugin's hooks. +- **`/aplyca-adf:adopt` and `/aplyca-adf:upgrade`** no longer name `${CLAUDE_PLUGIN_ROOT}/../..`, a + path outside the plugin — from `plugins/aplyca-adf`, the repository root, which the validator reports + as `.`. A development install finds the framework through the marketplace's `installLocation`, + which is the local checkout when the marketplace was added from one. +- **The static check** refuses a `>` before a period in the plugin's hooks, and any path that climbs + out of the plugin. #### Upgrade impact - **Overwrite:** `session-context.sh`, `triage-first.sh`. diff --git a/evals/static/check-skills.sh b/evals/static/check-skills.sh index 1f1e69a..3070144 100755 --- a/evals/static/check-skills.sh +++ b/evals/static/check-skills.sh @@ -792,8 +792,9 @@ for path in sorted(glob.glob(os.path.join(hooks_dir, "*.sh"))): print(f"{os.path.basename(path)}:{number} {what}") PY ) + bad+=$(git -C "$REPO_ROOT" grep -n -F '${CLAUDE_PLUGIN_ROOT}/..' -- plugins/aplyca-adf | sed 's/$/ reaches outside the plugin/') if [ -z "$links" ] && [ -z "$bad" ]; then - pass "the Claude Directory's checks: no symlinks; the plugin's hooks name every file they load or run literally, with no inline programs" + pass "the Claude Directory's checks: no symlinks; the plugin's hooks name every file they load or run literally, with no inline programs; nothing reaches outside the plugin" else fail "the Claude Directory's checks: symlinks [$links] ${bad//$'\n'/; }" fi diff --git a/plugins/aplyca-adf/skills/adopt/SKILL.md b/plugins/aplyca-adf/skills/adopt/SKILL.md index e964c6d..ec5cdbc 100644 --- a/plugins/aplyca-adf/skills/adopt/SKILL.md +++ b/plugins/aplyca-adf/skills/adopt/SKILL.md @@ -29,12 +29,12 @@ read the source doc (locations in step 1). Resolve the framework root, in order: -1. `${CLAUDE_PLUGIN_ROOT}/../..` — only when the plugin runs from a checkout of the framework repo. -2. The marketplace checkout: the `installLocation` of the marketplace (usually `aplyca`) in - `claude plugin marketplace list --json` — by default `~/.claude/plugins/marketplaces/aplyca/`. - **The normal case on installed machines** — installed plugins run from a version cache, so - `${CLAUDE_PLUGIN_ROOT}` isn't inside the repo. Run `claude plugin marketplace update ` first. -3. Otherwise clone: `git clone --depth 1 https://github.com/aplyca/AgenticDevelopmentFramework`. +1. The marketplace checkout: the `installLocation` of the marketplace (usually `aplyca`) in + `claude plugin marketplace list --json` — by default `~/.claude/plugins/marketplaces/aplyca/`, or + the framework repository itself when the marketplace was added from a local checkout. Installed + plugins run from a version cache, never from the repository. Run + `claude plugin marketplace update ` first. +2. Otherwise clone: `git clone --depth 1 https://github.com/aplyca/AgenticDevelopmentFramework`. You need `/skeleton/`, `/modules/`, and `/docs/`. Record the source release, SHA, and date: `git -C describe --tags --abbrev=0 --match 'v*'` diff --git a/plugins/aplyca-adf/skills/upgrade/SKILL.md b/plugins/aplyca-adf/skills/upgrade/SKILL.md index e0c140b..06d84f1 100644 --- a/plugins/aplyca-adf/skills/upgrade/SKILL.md +++ b/plugins/aplyca-adf/skills/upgrade/SKILL.md @@ -43,11 +43,11 @@ agents) and confirm the inferred SHA with the user before proceeding. ## Step 2 — Locate the framework source and NEW_SHA -Same resolution order as `/adopt` Step 1: repo checkout via `${CLAUDE_PLUGIN_ROOT}/../..` -(development installs), else a **full** clone of `https://github.com/aplyca/AgenticDevelopmentFramework` -(not shallow — the diff needs history). The marketplace checkout (`installLocation` in -`claude plugin marketplace list --json`) sits at the release the project pins, so it can't show what's -newer. +A framework checkout with its full history: the marketplace's `installLocation` (in +`claude plugin marketplace list --json`) when the marketplace was added from a local checkout of the +framework repository, else a **full** clone of `https://github.com/aplyca/AgenticDevelopmentFramework` +(not shallow — the diff needs history). A marketplace added from GitHub sits at the release the +project pins, so it can't show what's newer. A project moves **from release to release** (decision 0017): NEW_SHA is the commit of the newest release tag (`git -C tag --list 'v*' --sort=-v:refname | head -1`), unless the From 0f3393169255d4137fb2864f2602205b7c651f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Sun, 4 Oct 2026 21:01:09 -0500 Subject: [PATCH 4/4] docs: v1.0.6's changelog says what the scans showed, not a diagnosis The branch scans showed the reminder fix and the out-of-plugin path each removed something the Directory's validator pointed at, but the blocking finding remains, its source unknown. The listing is paused. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b676c59..6d9cf43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,13 @@ For each entry, **Upgrade impact** classifies the change against the [three-buck ## Unreleased -## v1.0.6 — 2026-10-04 — No escaped quotes in the hooks +## v1.0.6 — 2026-10-04 — Closing the Claude Directory work, for now -A patch release. The Directory's blocking `.` stayed after v1.0.5, still named by `triage-first.sh`. -Its reminder message is the one line, unchanged since v1.0.1, with both an escaped quote and a `>` -before a period: `\"Fast lane — …; model: .\"`. A parser that doesn't take `\"` as a -quote ends the string there and reads `opus>.` as a redirect to the folder `.`. +A patch release, and the last of the Directory fixes for now. Its validator still reports one blocking +finding, `COMMAND_PATH_COMPUTED` at `.`, with no file or line. Scans of this branch showed the +reminder change below removed the `.` the validator listed after `triage-first.sh`; the other changes +showed no effect. The blocking finding's source, which v1.0.2 to v1.0.6 looked for, is still unknown. +The listing is paused. Installing from GitHub, per project, is unaffected. #### Changed - **`triage-first.sh`'s reminder** quotes the one-line triage with single quotes and ends it without