@givolt/i3-composer-agent 0.0.0-stage → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +22 -0
- package/.claude/commands/build.md +44 -0
- package/.claude/commands/code-simplify.md +22 -0
- package/.claude/commands/constraints.md +32 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/review.md +16 -0
- package/.claude/commands/ship.md +72 -0
- package/.claude/commands/spec.md +17 -0
- package/.claude/commands/test.md +19 -0
- package/.claude/commands/webperf.md +32 -0
- package/.claude-plugin/marketplace.json +26 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.codex-plugin/plugin.json +26 -0
- package/.gemini/commands/build.toml +43 -0
- package/.gemini/commands/code-simplify.toml +22 -0
- package/.gemini/commands/constraints.toml +32 -0
- package/.gemini/commands/planning.toml +18 -0
- package/.gemini/commands/review.toml +16 -0
- package/.gemini/commands/ship.toml +72 -0
- package/.gemini/commands/spec.toml +17 -0
- package/.gemini/commands/test.toml +19 -0
- package/.gemini/commands/webperf.toml +32 -0
- package/LICENSE +21 -0
- package/NOTICE.md +9 -0
- package/README.md +177 -2
- package/agents/code-reviewer.md +102 -0
- package/agents/security-auditor.md +112 -0
- package/agents/test-engineer.md +95 -0
- package/agents/web-performance-auditor.md +184 -0
- package/bin/i3-composer-agent.cjs +119 -0
- package/commands/build.toml +43 -0
- package/commands/code-simplify.toml +22 -0
- package/commands/constraints.toml +32 -0
- package/commands/planning.toml +18 -0
- package/commands/review.toml +16 -0
- package/commands/ship.toml +72 -0
- package/commands/spec.toml +17 -0
- package/commands/test.toml +19 -0
- package/commands/webperf.toml +32 -0
- package/docs/adoption-guide.md +129 -0
- package/docs/advanced-per-agent-configuration.md +70 -0
- package/docs/agents.md +123 -0
- package/docs/antigravity-setup.md +128 -0
- package/docs/codebuddy-setup.md +41 -0
- package/docs/codex-setup.md +34 -0
- package/docs/commandcode-setup.md +69 -0
- package/docs/comparison.md +131 -0
- package/docs/copilot-cli-setup.md +64 -0
- package/docs/copilot-setup.md +162 -0
- package/docs/cursor-setup.md +225 -0
- package/docs/developer-onboarding.md +116 -0
- package/docs/gemini-cli-setup.md +133 -0
- package/docs/getting-started.md +208 -0
- package/docs/opencode-setup.md +259 -0
- package/docs/other-hosts.md +7 -0
- package/docs/skill-anatomy.md +203 -0
- package/docs/windsurf-setup.md +48 -0
- package/evals/README.md +101 -0
- package/evals/cases/api-and-interface-design.json +45 -0
- package/evals/cases/browser-testing-with-devtools.json +44 -0
- package/evals/cases/ci-cd-and-automation.json +45 -0
- package/evals/cases/code-review-and-quality.json +49 -0
- package/evals/cases/code-simplification.json +45 -0
- package/evals/cases/constraint-driven-development.json +79 -0
- package/evals/cases/context-engineering.json +44 -0
- package/evals/cases/debugging-and-error-recovery.json +58 -0
- package/evals/cases/deprecation-and-migration.json +52 -0
- package/evals/cases/documentation-and-adrs.json +44 -0
- package/evals/cases/doubt-driven-development.json +43 -0
- package/evals/cases/frontend-ui-engineering.json +52 -0
- package/evals/cases/git-workflow-and-versioning.json +47 -0
- package/evals/cases/idea-refine.json +43 -0
- package/evals/cases/incremental-implementation.json +57 -0
- package/evals/cases/interview-me.json +42 -0
- package/evals/cases/observability-and-instrumentation.json +44 -0
- package/evals/cases/performance-optimization.json +53 -0
- package/evals/cases/planning-and-task-breakdown.json +45 -0
- package/evals/cases/security-and-hardening.json +53 -0
- package/evals/cases/shipping-and-launch.json +56 -0
- package/evals/cases/source-driven-development.json +44 -0
- package/evals/cases/spec-driven-development.json +72 -0
- package/evals/cases/test-driven-development.json +73 -0
- package/evals/cases/using-agent-skills.json +44 -0
- package/evals/fixtures/api-and-interface-design/service-brief.md +19 -0
- package/evals/fixtures/browser-testing-with-devtools/README.md +5 -0
- package/evals/fixtures/browser-testing-with-devtools/index.html +24 -0
- package/evals/fixtures/browser-testing-with-devtools/server.js +15 -0
- package/evals/fixtures/ci-cd-and-automation/package.json +8 -0
- package/evals/fixtures/ci-cd-and-automation/src/slug.js +3 -0
- package/evals/fixtures/ci-cd-and-automation/test/slug.test.js +9 -0
- package/evals/fixtures/code-review-and-quality/user-search.diff +16 -0
- package/evals/fixtures/code-simplification/config-parser.js +46 -0
- package/evals/fixtures/code-simplification/config-parser.test.js +15 -0
- package/evals/fixtures/context-engineering/context-audit.md +15 -0
- package/evals/fixtures/debugging-and-error-recovery/pagination.js +8 -0
- package/evals/fixtures/debugging-and-error-recovery/pagination.test.js +9 -0
- package/evals/fixtures/debugging-and-error-recovery/time-pressure.md +6 -0
- package/evals/fixtures/deprecation-and-migration/api-inventory.md +9 -0
- package/evals/fixtures/documentation-and-adrs/decision-context.md +16 -0
- package/evals/fixtures/doubt-driven-development/migration-plan.md +19 -0
- package/evals/fixtures/frontend-ui-engineering/Button.tsx +7 -0
- package/evals/fixtures/frontend-ui-engineering/design-system.md +11 -0
- package/evals/fixtures/git-workflow-and-versioning/.eval/working-tree.patch +21 -0
- package/evals/fixtures/git-workflow-and-versioning/app.js +7 -0
- package/evals/fixtures/git-workflow-and-versioning/app.test.js +9 -0
- package/evals/fixtures/incremental-implementation/reports.js +7 -0
- package/evals/fixtures/incremental-implementation/reports.test.js +12 -0
- package/evals/fixtures/incremental-implementation/tasks/plan.md +8 -0
- package/evals/fixtures/incremental-implementation-pressure/draft-export.js +17 -0
- package/evals/fixtures/incremental-implementation-pressure/scenario.md +9 -0
- package/evals/fixtures/observability-and-instrumentation/operations.md +11 -0
- package/evals/fixtures/observability-and-instrumentation/payment-retry.js +14 -0
- package/evals/fixtures/performance-optimization/benchmark.js +15 -0
- package/evals/fixtures/performance-optimization/products.js +14 -0
- package/evals/fixtures/planning-and-task-breakdown/notifications-spec.md +18 -0
- package/evals/fixtures/security-and-hardening/webhook.js +11 -0
- package/evals/fixtures/security-and-hardening/webhook.test.js +13 -0
- package/evals/fixtures/shipping-and-launch/authority-pressure.md +6 -0
- package/evals/fixtures/shipping-and-launch/launch-status.md +11 -0
- package/evals/fixtures/source-driven-development/framework-task.md +10 -0
- package/evals/fixtures/spec-driven-development/billing-brief.md +16 -0
- package/evals/fixtures/spec-driven-development-decomposition/portal-brief.md +22 -0
- package/evals/fixtures/test-driven-development/BUG.md +10 -0
- package/evals/fixtures/test-driven-development/README.md +28 -0
- package/evals/fixtures/test-driven-development/package.json +9 -0
- package/evals/fixtures/test-driven-development/src/split.js +8 -0
- package/evals/fixtures/test-driven-development/test/split.test.js +13 -0
- package/evals/fixtures/test-driven-development-ecosystem/README.md +9 -0
- package/evals/fixtures/test-driven-development-ecosystem/ledger.py +15 -0
- package/evals/fixtures/test-driven-development-ecosystem/test_ledger.py +19 -0
- package/evals/fixtures/using-agent-skills/incident.md +6 -0
- package/evals/plugin/code-review-fires/graders/off-by-one.md +7 -0
- package/evals/plugin/code-review-fires/graders/severity-labels.md +4 -0
- package/evals/plugin/code-review-fires/graders/skill-fired.md +5 -0
- package/evals/plugin/code-review-fires/prompt.md +31 -0
- package/evals/plugin/code-review-stays-quiet/graders/not-fired.md +8 -0
- package/evals/plugin/code-review-stays-quiet/graders/tdd-fired.md +6 -0
- package/evals/plugin/code-review-stays-quiet/prompt.md +8 -0
- package/evals/plugin/code-review-stays-quiet-on-commit-message/graders/not-fired.md +8 -0
- package/evals/plugin/code-review-stays-quiet-on-commit-message/prompt.md +25 -0
- package/evals/skill-impact.md +6 -0
- package/hooks/SDD-CACHE.md +167 -0
- package/hooks/SIMPLIFY-IGNORE.md +91 -0
- package/hooks/sdd-cache-post.sh +135 -0
- package/hooks/sdd-cache-pre.sh +106 -0
- package/hooks/sdd-cache-test.sh +246 -0
- package/hooks/session-start-test.sh +47 -0
- package/hooks/session-start.sh +33 -0
- package/hooks/simplify-ignore-test.sh +476 -0
- package/hooks/simplify-ignore.sh +352 -0
- package/package.json +31 -4
- package/plugin.json +5 -0
- package/rebrand/MAINTENANCE.md +51 -0
- package/rebrand/apply.cjs +121 -0
- package/rebrand/brand.json +9 -0
- package/rebrand/templates/README.md +178 -0
- package/rebrand/templates/codebuddy-setup.md +41 -0
- package/rebrand/templates/codex-setup.md +34 -0
- package/references/accessibility-checklist.md +160 -0
- package/references/definition-of-done.md +67 -0
- package/references/observability-checklist.md +92 -0
- package/references/orchestration-patterns.md +370 -0
- package/references/performance-checklist.md +236 -0
- package/references/security-checklist.md +248 -0
- package/references/testing-patterns.md +235 -0
- package/scripts/floor-guard-reference-test.js +291 -0
- package/scripts/lib/skill-lint-test.js +650 -0
- package/scripts/lib/skill-lint.js +584 -0
- package/scripts/run-evals-test.js +623 -0
- package/scripts/run-evals.js +638 -0
- package/scripts/validate-artifact-paths-test.js +179 -0
- package/scripts/validate-artifact-paths.js +128 -0
- package/scripts/validate-brand.cjs +36 -0
- package/scripts/validate-commands-test.js +216 -0
- package/scripts/validate-commands.js +222 -0
- package/scripts/validate-reference-links-test.js +495 -0
- package/scripts/validate-reference-links.js +187 -0
- package/scripts/validate-skills.js +69 -0
- package/scripts/validate-versions-test.js +32 -0
- package/scripts/validate-versions.js +35 -0
- package/skills/api-and-interface-design/SKILL.md +367 -0
- package/skills/browser-testing-with-devtools/SKILL.md +317 -0
- package/skills/ci-cd-and-automation/SKILL.md +390 -0
- package/skills/code-review-and-quality/SKILL.md +398 -0
- package/skills/code-simplification/SKILL.md +331 -0
- package/skills/constraint-driven-development/SKILL.md +311 -0
- package/skills/constraint-driven-development/references/floor-guard.md +182 -0
- package/skills/context-engineering/SKILL.md +353 -0
- package/skills/debugging-and-error-recovery/SKILL.md +300 -0
- package/skills/deprecation-and-migration/SKILL.md +247 -0
- package/skills/documentation-and-adrs/SKILL.md +288 -0
- package/skills/doubt-driven-development/SKILL.md +243 -0
- package/skills/frontend-ui-engineering/SKILL.md +340 -0
- package/skills/git-workflow-and-versioning/SKILL.md +355 -0
- package/skills/idea-refine/SKILL.md +178 -0
- package/skills/idea-refine/examples.md +238 -0
- package/skills/idea-refine/frameworks.md +99 -0
- package/skills/idea-refine/refinement-criteria.md +113 -0
- package/skills/idea-refine/scripts/idea-refine.sh +15 -0
- package/skills/incremental-implementation/SKILL.md +249 -0
- package/skills/interview-me/SKILL.md +235 -0
- package/skills/observability-and-instrumentation/SKILL.md +238 -0
- package/skills/performance-optimization/SKILL.md +267 -0
- package/skills/performance-optimization/references/optimization-patterns.md +250 -0
- package/skills/planning-and-task-breakdown/SKILL.md +257 -0
- package/skills/security-and-hardening/SKILL.md +216 -0
- package/skills/security-and-hardening/references/hardening-patterns.md +324 -0
- package/skills/shipping-and-launch/SKILL.md +330 -0
- package/skills/source-driven-development/SKILL.md +216 -0
- package/skills/spec-driven-development/SKILL.md +259 -0
- package/skills/test-driven-development/SKILL.md +398 -0
- package/skills/using-agent-skills/SKILL.md +192 -0
- package/tests/bundle.test.cjs +79 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Simple in-memory ledger utilities."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def apply_entries(balance, entries):
|
|
5
|
+
"""Apply entries to a starting balance and return the result.
|
|
6
|
+
|
|
7
|
+
Entries are (kind, amount) tuples. Only "credit" entries are
|
|
8
|
+
supported; anything else raises ValueError.
|
|
9
|
+
"""
|
|
10
|
+
for kind, amount in entries:
|
|
11
|
+
if kind == "credit":
|
|
12
|
+
balance += amount
|
|
13
|
+
else:
|
|
14
|
+
raise ValueError(f"unsupported entry kind: {kind}")
|
|
15
|
+
return balance
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from ledger import apply_entries
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApplyEntriesTest(unittest.TestCase):
|
|
7
|
+
def test_applies_a_single_credit(self):
|
|
8
|
+
self.assertEqual(apply_entries(100, [("credit", 25)]), 125)
|
|
9
|
+
|
|
10
|
+
def test_applies_multiple_credits_in_order(self):
|
|
11
|
+
self.assertEqual(apply_entries(0, [("credit", 10), ("credit", 5)]), 15)
|
|
12
|
+
|
|
13
|
+
def test_rejects_unknown_entry_kinds(self):
|
|
14
|
+
with self.assertRaises(ValueError):
|
|
15
|
+
apply_entries(0, [("transfer", 10)])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
unittest.main()
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Login regression report
|
|
2
|
+
|
|
3
|
+
The login page began returning HTTP 500 after yesterday's deployment. The
|
|
4
|
+
request reaches the authentication callback, then fails before a session cookie
|
|
5
|
+
is written. There is no confirmed root cause yet. The user asked for help
|
|
6
|
+
getting login working again, not for a new authentication design.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
PASS if the review points out that `rows.slice(start, start + size - 1)` returns one row fewer than `size` on every page (the end index of slice is exclusive) and treats it as something that must be fixed before merge, for example by labelling it Critical or leaving it unprefixed as a required change.
|
|
6
|
+
|
|
7
|
+
FAIL if the review does not mention that pages come back one row short, or files that finding as a Nit, Optional, Consider, or FYI.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: A review request that names the axes, with a small diff inline. The skill should fire (through the review command or directly) and the reply should carry its severity labels.
|
|
3
|
+
expected_outcome: A structured review with a verdict, findings labelled with the skill's severity taxonomy, and the off-by-one in pageOrders treated as a required change.
|
|
4
|
+
max_turns: 12
|
|
5
|
+
allowed_tools: [Read, Glob, Grep, Skill]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
I want a code review of this diff across correctness, readability, security and performance before merging. It adds paging to the orders endpoint.
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
diff --git a/src/orders.js b/src/orders.js
|
|
12
|
+
--- a/src/orders.js
|
|
13
|
+
+++ b/src/orders.js
|
|
14
|
+
@@ -12,3 +12,15 @@ function listOrders(db) {
|
|
15
|
+
return db.query('SELECT * FROM orders ORDER BY created_at DESC');
|
|
16
|
+
}
|
|
17
|
+
-module.exports = { listOrders };
|
|
18
|
+
+function pageOrders(db, page, size) {
|
|
19
|
+
+ const rows = db.query('SELECT * FROM orders ORDER BY created_at DESC');
|
|
20
|
+
+ const start = (page - 1) * size;
|
|
21
|
+
+ return rows.slice(start, start + size - 1);
|
|
22
|
+
+}
|
|
23
|
+
+
|
|
24
|
+
+app.get('/orders', (req, res) => {
|
|
25
|
+
+ const page = Number(req.query.page) || 1;
|
|
26
|
+
+ const size = Number(req.query.size) || 20;
|
|
27
|
+
+ res.json(pageOrders(db, page, size));
|
|
28
|
+
+});
|
|
29
|
+
+
|
|
30
|
+
+module.exports = { listOrders, pageOrders };
|
|
31
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: A test-first request that belongs to test-driven-development. The review skill must not fire.
|
|
3
|
+
expected_outcome: The reply proposes a failing test first; code-review-and-quality is never invoked.
|
|
4
|
+
max_turns: 12
|
|
5
|
+
allowed_tools: [Read, Glob, Grep, Skill]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Write a failing test for this bug before we fix it: splitCents(100, 3) returns [33, 33, 33] and a cent goes missing.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: A pasted diff with a request that belongs to git-workflow-and-versioning. Guards the "even when the diff is pasted inline" clause of the review skill's description, since a pasted diff alone must not make the review skill fire.
|
|
3
|
+
expected_outcome: A commit message; code-review-and-quality is never invoked.
|
|
4
|
+
max_turns: 12
|
|
5
|
+
allowed_tools: [Read, Glob, Grep, Skill]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Write me a commit message for this diff.
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
diff --git a/src/orders.js b/src/orders.js
|
|
12
|
+
--- a/src/orders.js
|
|
13
|
+
+++ b/src/orders.js
|
|
14
|
+
@@ -12,3 +12,9 @@ function listOrders(db) {
|
|
15
|
+
return db.query('SELECT * FROM orders ORDER BY created_at DESC');
|
|
16
|
+
}
|
|
17
|
+
-module.exports = { listOrders };
|
|
18
|
+
+function pageOrders(db, page, size) {
|
|
19
|
+
+ const rows = db.query('SELECT * FROM orders ORDER BY created_at DESC');
|
|
20
|
+
+ const start = (page - 1) * size;
|
|
21
|
+
+ return rows.slice(start, start + size);
|
|
22
|
+
+}
|
|
23
|
+
+
|
|
24
|
+
+module.exports = { listOrders, pageOrders };
|
|
25
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Rejected Skill Changes
|
|
2
|
+
|
|
3
|
+
This append-only ledger records skill and description changes rejected on eval evidence so contributors can review prior attempts before proposing overlapping work. Add one concise row per rejected proposal; do not record accepted changes here.
|
|
4
|
+
|
|
5
|
+
| Date | Affected skill | Attempted change | Rank-1 score (before → after) | Rejected PR and outcome |
|
|
6
|
+
|---|---|---|---:|---|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# sdd-cache hook
|
|
2
|
+
|
|
3
|
+
Cross-session citation cache for [`source-driven-development`](../skills/source-driven-development/SKILL.md). Skips redundant `WebFetch` calls without weakening the skill's "verify against current docs" guarantee.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
`source-driven-development` fetches official docs for every framework-specific decision. Working on the same project across sessions means fetching the same pages over and over. Caching the content as local memory would contradict the skill — docs change, and a stale cache hides that.
|
|
8
|
+
|
|
9
|
+
This hook caches fetched content on disk, but **revalidates with the origin server on every reuse** via HTTP `If-None-Match` / `If-Modified-Since`. Content is only served from cache when the server responds `304 Not Modified`, which is a fresh verification — not a memory read.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. Add hooks to `.claude/settings.json` (or `.claude/settings.local.json` for personal use):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"hooks": {
|
|
18
|
+
"PreToolUse": [
|
|
19
|
+
{
|
|
20
|
+
"matcher": "WebFetch",
|
|
21
|
+
"hooks": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "bash \"${CLAUDE_PROJECT_DIR}/hooks/sdd-cache-pre.sh\"",
|
|
25
|
+
"timeout": 10
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PostToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "WebFetch",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "bash \"${CLAUDE_PROJECT_DIR}/hooks/sdd-cache-post.sh\"",
|
|
37
|
+
"async": true,
|
|
38
|
+
"timeout": 10
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`${CLAUDE_PROJECT_DIR}` resolves to the directory you launched Claude Code from. The snippet above works when the hooks live inside the same project. If you installed `i3-composer-agent` elsewhere (e.g. as a shared plugin under `~/agent-skills`), replace `${CLAUDE_PROJECT_DIR}/hooks/...` with the absolute path to each script.
|
|
48
|
+
|
|
49
|
+
2. Make sure `.claude/sdd-cache/` is in your `.gitignore` (already included in this repo).
|
|
50
|
+
|
|
51
|
+
3. Use `/source-driven-development` (or the skill) as usual. No changes to the skill or the agent's workflow — the cache is transparent.
|
|
52
|
+
|
|
53
|
+
## Mental model
|
|
54
|
+
|
|
55
|
+
HTTP resource cache keyed by URL. Freshness is delegated to the origin via `ETag` / `Last-Modified`; no TTL, no prompt in the key.
|
|
56
|
+
|
|
57
|
+
The stored body is not raw HTML — `WebFetch` post-processes each response through a model using the caller's prompt, so what we cache is one agent's reading of the page. The key stays URL-only so reads reuse across sessions; the original prompt is kept as metadata and surfaced in the hit message so the next agent can tell whether the earlier reading fits.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
One cache entry per URL, stored as JSON in `.claude/sdd-cache/<sha>.json`:
|
|
62
|
+
|
|
63
|
+
| Event | Action |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `PreToolUse WebFetch` | If an entry exists, sends a `HEAD` request with `If-None-Match` / `If-Modified-Since`. On `304`, blocks the fetch and returns the cached content to the agent via stderr, with the original prompt surfaced as metadata. Otherwise allows the fetch. |
|
|
66
|
+
| `PostToolUse WebFetch` | Captures the response, issues a `HEAD` request to record the current `ETag` / `Last-Modified`, and stores `{url, prompt, etag, last_modified, content, fetched_at}`. |
|
|
67
|
+
|
|
68
|
+
**Freshness rules:**
|
|
69
|
+
|
|
70
|
+
- Entry is served only if the origin confirms `304 Not Modified`.
|
|
71
|
+
- Entries without an `ETag` or `Last-Modified` header are never cached — without a validator, the hook cannot verify freshness later, and caching would mean trusting memory.
|
|
72
|
+
- Cache key is `sha256(url)`. The same URL asked with a different prompt hits the same entry; the cached body reflects the prompt used on the first fetch, and that prompt is shown alongside the hit so the agent can decide whether to re-use or re-fetch manually.
|
|
73
|
+
|
|
74
|
+
**What the agent sees:**
|
|
75
|
+
|
|
76
|
+
- Cache hit: `WebFetch` is blocked via exit code 2. Claude Code delivers the hook's stderr payload back to the agent as a tool error — this is the intended signal for a cache hit, not a failure. The payload is prefixed with `[sdd-cache] Cache hit for <url>` and wraps the cached body between `----- BEGIN CACHED CONTENT -----` / `----- END CACHED CONTENT -----` markers so the agent can use it as if `WebFetch` had just returned it.
|
|
77
|
+
- Cache miss or stale: `WebFetch` runs normally; the result is stored for next time.
|
|
78
|
+
|
|
79
|
+
The skill itself is unchanged. It continues to follow `DETECT → FETCH → IMPLEMENT → CITE`. The hook only changes what happens under the hood when `FETCH` runs.
|
|
80
|
+
|
|
81
|
+
## Local testing
|
|
82
|
+
|
|
83
|
+
### 1. Smoke test the scripts directly
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Simulate a PostToolUse payload: cache a page
|
|
87
|
+
echo '{
|
|
88
|
+
"tool_input": {
|
|
89
|
+
"url": "https://react.dev/reference/react/useActionState",
|
|
90
|
+
"prompt": "extract the signature"
|
|
91
|
+
},
|
|
92
|
+
"tool_response": "useActionState(action, initialState) returns [state, formAction, isPending]"
|
|
93
|
+
}' | bash hooks/sdd-cache-post.sh
|
|
94
|
+
|
|
95
|
+
# Inspect the stored entry
|
|
96
|
+
ls .claude/sdd-cache/
|
|
97
|
+
cat .claude/sdd-cache/*.json | jq .
|
|
98
|
+
|
|
99
|
+
# Simulate the next PreToolUse on the same URL + prompt
|
|
100
|
+
echo '{
|
|
101
|
+
"tool_input": {
|
|
102
|
+
"url": "https://react.dev/reference/react/useActionState",
|
|
103
|
+
"prompt": "extract the signature"
|
|
104
|
+
}
|
|
105
|
+
}' | bash hooks/sdd-cache-pre.sh
|
|
106
|
+
echo "exit=$?"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Expected:
|
|
110
|
+
|
|
111
|
+
- First command creates one file under `.claude/sdd-cache/` (only if the server returned an `ETag` or `Last-Modified`).
|
|
112
|
+
- Second command exits `2` with the cached content on stderr when the origin replies `304`, or exits `0` silently otherwise.
|
|
113
|
+
|
|
114
|
+
### 2. End-to-end in a real session
|
|
115
|
+
|
|
116
|
+
1. Register the hooks in `.claude/settings.local.json` as shown above.
|
|
117
|
+
2. Start a Claude Code session in this repo.
|
|
118
|
+
3. Ask the agent to fetch a documentation page (e.g. "fetch `https://react.dev/reference/react/useActionState` and summarize").
|
|
119
|
+
4. Verify a file appears under `.claude/sdd-cache/`.
|
|
120
|
+
5. Ask the agent to fetch the same page with the same prompt again.
|
|
121
|
+
6. Verify the second `WebFetch` is blocked and the cached content is returned (visible in the session transcript as a tool error with `[sdd-cache]` prefix).
|
|
122
|
+
|
|
123
|
+
### 3. Freshness verification
|
|
124
|
+
|
|
125
|
+
To confirm the cache invalidates when docs change, force an `ETag` mismatch. Pick one specific entry — `*.json` is unsafe once the cache holds more than one file:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Pick the entry you want to corrupt (swap in the actual filename)
|
|
129
|
+
ENTRY=.claude/sdd-cache/e49c9f378670cfbb1d7d871b6dee16d9.json
|
|
130
|
+
|
|
131
|
+
# Patch its ETag to something the origin will not recognize
|
|
132
|
+
jq '.etag = "W/\"stale-etag-forced\""' "$ENTRY" > "$ENTRY.tmp" && mv "$ENTRY.tmp" "$ENTRY"
|
|
133
|
+
|
|
134
|
+
# Next PreToolUse should miss (server returns 200, not 304)
|
|
135
|
+
echo '{"tool_input":{"url":"...", "prompt":"..."}}' | bash hooks/sdd-cache-pre.sh
|
|
136
|
+
echo "exit=$?" # expect 0 (fetch allowed through)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 4. Debugging
|
|
140
|
+
|
|
141
|
+
Both hooks write timestamped events to `.claude/sdd-cache/.debug.log` when debug mode is on. Enable it with either:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Option A: env var (per-session)
|
|
145
|
+
SDD_CACHE_DEBUG=1 claude
|
|
146
|
+
|
|
147
|
+
# Option B: sentinel file (persistent)
|
|
148
|
+
mkdir -p .claude/sdd-cache && touch .claude/sdd-cache/.debug
|
|
149
|
+
# …disable with: rm .claude/sdd-cache/.debug
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The log captures URL, detected `tool_response` shape, HEAD status, and why each invocation hit or missed. Useful when a cache miss looks unexpected (typically: the origin stopped emitting validators).
|
|
153
|
+
|
|
154
|
+
## Known limitations
|
|
155
|
+
|
|
156
|
+
- **Body is prompt-shaped.** A hit returns the earlier agent's reading of the page, with the original prompt surfaced so the current agent can decide whether it applies. If it doesn't, delete the file under `.claude/sdd-cache/` to force a re-fetch.
|
|
157
|
+
- **Every cache write costs an extra HEAD.** Claude Code doesn't expose the response headers that `WebFetch` already received, so the post hook re-queries the origin to capture `ETag` / `Last-Modified`. One extra roundtrip per miss — the price of keeping this a pure hook with no core changes.
|
|
158
|
+
- **Servers without `ETag` or `Last-Modified` are never cached.** Most official doc sites (react.dev, docs.djangoproject.com, developer.mozilla.org) emit validators. Sites that don't are always re-fetched.
|
|
159
|
+
- **A misbehaving server can serve a wrong `304`.** That's a server bug to diagnose, not a cache invariant to defend against; we don't paper over it with a TTL. Delete the entry if you spot a stale one.
|
|
160
|
+
- **Cache is local and per-project.** There is no team-wide shared cache. Adding one would require a signed-content-addressable storage layer, which is out of scope.
|
|
161
|
+
|
|
162
|
+
## Requirements
|
|
163
|
+
|
|
164
|
+
- `jq`
|
|
165
|
+
- `curl`
|
|
166
|
+
- `shasum` or `sha256sum` (auto-detected)
|
|
167
|
+
- Bash 3.2+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# simplify-ignore hook
|
|
2
|
+
|
|
3
|
+
Block-level protection for `/code-simplify`. Mark code that should never be simplified — the model won't see it.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Annotate blocks you want to protect:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
/* simplify-ignore-start: perf-critical */
|
|
11
|
+
// manually unrolled XOR — 3x faster than a loop
|
|
12
|
+
result[0] = buf[0] ^ key[0];
|
|
13
|
+
result[1] = buf[1] ^ key[1];
|
|
14
|
+
result[2] = buf[2] ^ key[2];
|
|
15
|
+
result[3] = buf[3] ^ key[3];
|
|
16
|
+
/* simplify-ignore-end */
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Add hooks to `.claude/settings.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"hooks": {
|
|
24
|
+
"PreToolUse": [
|
|
25
|
+
{
|
|
26
|
+
"matcher": "Read",
|
|
27
|
+
"hooks": [{ "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}/hooks/simplify-ignore.sh\"" }]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PostToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "Edit|Write",
|
|
33
|
+
"hooks": [{ "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}/hooks/simplify-ignore.sh\"" }]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"Stop": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [{ "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}/hooks/simplify-ignore.sh\"" }]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. Run `/code-simplify` — protected blocks become `/* BLOCK_de115a1d: perf-critical */` placeholders. The model reasons about surrounding code without seeing the protected implementation.
|
|
46
|
+
|
|
47
|
+
> **Note:** The hook stores temporary backups in `.claude/.simplify-ignore-cache/`. Make sure this path is in your `.gitignore`.
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
One script, three hook events:
|
|
52
|
+
|
|
53
|
+
| Event | Action |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `PreToolUse Read` | Backs up file, replaces blocks with `BLOCK_<hash>` placeholders in-place |
|
|
56
|
+
| `PostToolUse Edit\|Write` | Expands placeholders back to real code, saves model's changes, re-filters |
|
|
57
|
+
| `Stop` | Expands the placeholders left in each file on disk, so changes made outside `Edit`/`Write` survive; falls back to the backup only if no placeholder is left |
|
|
58
|
+
|
|
59
|
+
Each block is content-hashed (8 hex chars via `shasum`/`sha1sum`) so the round-trip is unambiguous even if the model duplicates or reorders placeholders. Cache is project-scoped to prevent cross-session interference.
|
|
60
|
+
|
|
61
|
+
## Annotation syntax
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
/* simplify-ignore-start */ // basic — hides the block
|
|
65
|
+
/* simplify-ignore-start: reason */ // with reason — appears in placeholder
|
|
66
|
+
/* simplify-ignore-end */
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Any comment style works (`//`, `/*`, `#`, `<!--`). Multiple blocks per file and single-line blocks supported. Placeholders preserve the original comment syntax (e.g. `# BLOCK_xxx` for Python, `<!-- BLOCK_xxx -->` for HTML).
|
|
70
|
+
|
|
71
|
+
## Crash recovery
|
|
72
|
+
|
|
73
|
+
If Claude Code crashes without triggering the Stop hook, files on disk may still have `BLOCK_<hash>` placeholders. To restore manually:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
echo '{}' | bash hooks/simplify-ignore.sh
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Backups are stored in `.claude/.simplify-ignore-cache/` within your project directory.
|
|
80
|
+
|
|
81
|
+
## Known limitations
|
|
82
|
+
|
|
83
|
+
- **Single-line blocks hide the entire line.** If `simplify-ignore-start` and `simplify-ignore-end` appear on the same line as other code, the whole line is hidden from the model, not just the annotated portion. Use dedicated lines for annotations.
|
|
84
|
+
- **Comment suffix detection covers `*/` and `-->` only.** Template engines with non-standard comment closers (ERB `%>`, Blade `--}}`) may produce unbalanced placeholders. Use `#` or `//` style comments instead.
|
|
85
|
+
- **Fallback expansion is progressive, not exact.** If the model alters a placeholder's formatting (e.g. changes the reason text), the hook tries progressively simpler matches: full placeholder → prefix+hash+suffix → hash-only. The hash-only fallback may leave cosmetic debris (e.g. stray `:` or reason text). A warning is printed to stderr when this happens.
|
|
86
|
+
- **A wholesale rewrite falls back to the backup.** If something replaces a filtered file's entire contents and removes every `BLOCK_<hash>` placeholder, the hook can no longer tell where the protected block belongs. At `Stop` it restores the backup and prints a warning; the rewrite is not lost but is kept in the cache as `.claude/.simplify-ignore-cache/<id>.recovered`, whose path the warning prints, and you merge it back by hand. Changes that leave the placeholders in place — a `Bash` edit, a formatter, an external editor — are kept in the file itself.
|
|
87
|
+
- **File renaming leaves placeholders.** If the model renames or moves a file via a shell command, the new file will retain `BLOCK_<hash>` placeholders. The original code is saved as `<old-filename>.recovered` when the session stops. You must manually restore the recovered code into the new file.
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- `jq`, `shasum` or `sha1sum` (auto-detected), Bash 3.2+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# sdd-cache-post.sh — PostToolUse hook for WebFetch.
|
|
3
|
+
#
|
|
4
|
+
# After WebFetch, stores the response body in .claude/sdd-cache/<sha>.json
|
|
5
|
+
# with the current ETag / Last-Modified captured via a HEAD request so the
|
|
6
|
+
# pre hook can revalidate on the next fetch.
|
|
7
|
+
#
|
|
8
|
+
# Keyed by URL. The caller's prompt is stored as metadata (not part of the
|
|
9
|
+
# key) so a future cache hit can show what question produced the cached
|
|
10
|
+
# reading. Entries without ETag or Last-Modified are not cached.
|
|
11
|
+
#
|
|
12
|
+
# Dependencies: jq, curl, shasum (or sha256sum).
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
17
|
+
command -v curl >/dev/null 2>&1 || exit 0
|
|
18
|
+
command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || exit 0
|
|
19
|
+
|
|
20
|
+
if [ -t 0 ]; then INPUT="{}"; else INPUT=$(cat); fi
|
|
21
|
+
|
|
22
|
+
# Debug logging: active when SDD_CACHE_DEBUG=1 is set, or when a sentinel
|
|
23
|
+
# file exists at .claude/sdd-cache/.debug. Toggle with `touch` / `rm`.
|
|
24
|
+
dbg() {
|
|
25
|
+
local dir="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
|
|
26
|
+
[ "${SDD_CACHE_DEBUG:-0}" = "1" ] || [ -f "$dir/.debug" ] || return 0
|
|
27
|
+
mkdir -p "$dir"
|
|
28
|
+
printf '%s [post] %s\n' "$(date -u +%FT%TZ)" "$*" >> "$dir/.debug.log"
|
|
29
|
+
}
|
|
30
|
+
dbg "fired, input=$(printf '%s' "$INPUT" | head -c 400)"
|
|
31
|
+
|
|
32
|
+
URL=$(printf '%s' "$INPUT" | jq -r '.tool_input.url // empty' 2>/dev/null || true)
|
|
33
|
+
PROMPT=$(printf '%s' "$INPUT" | jq -r '.tool_input.prompt // empty' 2>/dev/null || true)
|
|
34
|
+
if [ -z "$URL" ]; then dbg "no url in tool_input, exit"; exit 0; fi
|
|
35
|
+
dbg "url=$URL prompt=$(printf '%s' "$PROMPT" | head -c 80)"
|
|
36
|
+
|
|
37
|
+
# WebFetch tool_response shape (Claude Code as of 2026-04): an object with
|
|
38
|
+
# keys bytes, code, codeText, durationMs, result, url — content lives at
|
|
39
|
+
# .result. The other keys (.output / .text / .content / .body) are kept as
|
|
40
|
+
# defensive fallbacks in case the shape changes; jq returns empty if none
|
|
41
|
+
# match. The string branch handles older/custom integrations.
|
|
42
|
+
TOOL_RESPONSE_TYPE=$(printf '%s' "$INPUT" | jq -r '.tool_response | type' 2>/dev/null || echo "unknown")
|
|
43
|
+
dbg "tool_response type=$TOOL_RESPONSE_TYPE keys=$(printf '%s' "$INPUT" | jq -r 'try (.tool_response | keys | join(",")) catch "n/a"' 2>/dev/null)"
|
|
44
|
+
|
|
45
|
+
CONTENT=$(printf '%s' "$INPUT" | jq -r '
|
|
46
|
+
if (.tool_response | type) == "object" then
|
|
47
|
+
(.tool_response.result
|
|
48
|
+
// .tool_response.output
|
|
49
|
+
// .tool_response.text
|
|
50
|
+
// .tool_response.content
|
|
51
|
+
// .tool_response.body
|
|
52
|
+
// empty)
|
|
53
|
+
elif (.tool_response | type) == "string" then
|
|
54
|
+
.tool_response
|
|
55
|
+
else
|
|
56
|
+
empty
|
|
57
|
+
end
|
|
58
|
+
' 2>/dev/null || true)
|
|
59
|
+
|
|
60
|
+
if [ -z "$CONTENT" ]; then
|
|
61
|
+
dbg "could not extract content from tool_response, exit (shape unknown)"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
dbg "extracted content bytes=${#CONTENT}"
|
|
65
|
+
|
|
66
|
+
# Must match the pre hook: sha256(URL), first 32 hex chars.
|
|
67
|
+
hash_key() {
|
|
68
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
69
|
+
printf '%s' "$1" | shasum -a 256 | cut -c1-32
|
|
70
|
+
else
|
|
71
|
+
printf '%s' "$1" | sha256sum | cut -c1-32
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
CACHE_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
|
|
76
|
+
mkdir -p "$CACHE_DIR"
|
|
77
|
+
CACHE_FILE="$CACHE_DIR/$(hash_key "$URL").json"
|
|
78
|
+
|
|
79
|
+
# Capture validators from the origin. Follow redirects so they match the
|
|
80
|
+
# URL the agent actually talked to. Strip CR so awk's paragraph mode
|
|
81
|
+
# recognises blank separators between response blocks on a redirect chain.
|
|
82
|
+
HEAD_OUT=$(curl -sI -L --max-time 5 "$URL" 2>/dev/null | tr -d '\r' || true)
|
|
83
|
+
|
|
84
|
+
# Take only the final response's headers (last paragraph) to avoid picking
|
|
85
|
+
# up validators from intermediate 301/302 hops.
|
|
86
|
+
FINAL_HEADERS=$(printf '%s' "$HEAD_OUT" | awk '
|
|
87
|
+
BEGIN { RS = ""; last = "" }
|
|
88
|
+
{ last = $0 }
|
|
89
|
+
END { print last }
|
|
90
|
+
')
|
|
91
|
+
|
|
92
|
+
extract_header() {
|
|
93
|
+
local name="$1"
|
|
94
|
+
printf '%s' "$FINAL_HEADERS" | awk -v h="$name" '
|
|
95
|
+
BEGIN { FS = ":" }
|
|
96
|
+
tolower($1) == tolower(h) {
|
|
97
|
+
sub(/^[^:]*:[ \t]*/, "")
|
|
98
|
+
sub(/[ \t]+$/, "")
|
|
99
|
+
print
|
|
100
|
+
exit
|
|
101
|
+
}
|
|
102
|
+
'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ETAG=$(extract_header "ETag")
|
|
106
|
+
LAST_MOD=$(extract_header "Last-Modified")
|
|
107
|
+
dbg "HEAD etag=$ETAG last_modified=$LAST_MOD"
|
|
108
|
+
|
|
109
|
+
if [ -z "$ETAG" ] && [ -z "$LAST_MOD" ]; then
|
|
110
|
+
dbg "no validator from origin, removing any stale entry and exit"
|
|
111
|
+
rm -f "$CACHE_FILE"
|
|
112
|
+
exit 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
NOW=$(date +%s)
|
|
116
|
+
|
|
117
|
+
TMP="${CACHE_FILE}.$$.tmp"
|
|
118
|
+
if jq -n \
|
|
119
|
+
--arg url "$URL" \
|
|
120
|
+
--arg prompt "$PROMPT" \
|
|
121
|
+
--arg etag "$ETAG" \
|
|
122
|
+
--arg last_modified "$LAST_MOD" \
|
|
123
|
+
--arg content "$CONTENT" \
|
|
124
|
+
--argjson fetched_at "$NOW" \
|
|
125
|
+
'{url: $url, prompt: $prompt, etag: $etag, last_modified: $last_modified, content: $content, fetched_at: $fetched_at}' \
|
|
126
|
+
> "$TMP"
|
|
127
|
+
then
|
|
128
|
+
mv "$TMP" "$CACHE_FILE"
|
|
129
|
+
dbg "wrote cache file $CACHE_FILE"
|
|
130
|
+
else
|
|
131
|
+
rm -f "$TMP"
|
|
132
|
+
dbg "jq failed, temp cleaned"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
exit 0
|