projecta-rrr 1.22.2 → 1.22.3
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/CHANGELOG.md +27 -0
- package/bin/install.js +41 -0
- package/hooks/tool-disallow.js +57 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to RRR will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.22.3] - 2026-04-19
|
|
8
|
+
|
|
9
|
+
**Patch: postinstall full provision + tool-disallow real-payload subagent detection + hook wiring.**
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **`bin/install.js` postinstall now full-provisions** (was `--hud-only`). Fresh `npm install -g projecta-rrr` lands 47 commands, 13 agents, 18 hooks, plus settings.json with v1.22 hooks pre-wired (default-safe). Previously users had to run `npx projecta-rrr install` separately to get commands/agents.
|
|
13
|
+
- **v1.22 hooks now auto-wired** into `~/.claude/settings.json` on install:
|
|
14
|
+
- `PreToolUse` matcher `Task|Agent` → `hooks/model-router.js`
|
|
15
|
+
- `PreToolUse` matcher `Read|Grep|Glob` → `hooks/tool-disallow.js`
|
|
16
|
+
- `PostToolUse` matcher `Edit|MultiEdit` → `hooks/edit-batching-nudge.js`
|
|
17
|
+
- Defaults seeded: `settings.rrr.model_router: "static"` (v1.21 behavior), `settings.rrr.tok_disallow: false` (opt-in).
|
|
18
|
+
- Idempotent — re-install does not duplicate entries.
|
|
19
|
+
- Previously hook files copied but not wired (only `runHostedInstallOrchestrator()` wired them, which only runs with `--enable-hosted`).
|
|
20
|
+
- **`hooks/tool-disallow.js` subagent detection** now uses real Claude Code payload schema. Previous heuristic checked `input.agent_name` which does NOT exist in PreToolUse payloads (UAT-verified schema: `{session_id, transcript_path, cwd, permission_mode, hook_event_name, tool_name, tool_input, tool_use_id}`). New mechanism: read tail of `transcript_path` and check `isSidechain: true` to detect subagent context. Fallback: legacy `agent_name` field for forward-compat. Fail-CLOSED if transcript unreadable (over-block better than under-block when goal is token discipline).
|
|
21
|
+
|
|
22
|
+
### Operator data populated this session
|
|
23
|
+
- **`installations` table**: 5 rows (was 0) — populated from existing `repos.installation_id` data.
|
|
24
|
+
- **`repos.github_repo_id` backfill**: 24 of 59 real repos populated via `gh api /repos/PA-Ai-Team/<slug>`. Remaining 35 are inaccessible to the user PAT (likely deleted, archived, or upstream forks like OpenHands/letta — webhook routing for them is unreachable anyway, so NULL is correct).
|
|
25
|
+
|
|
26
|
+
### Verified in UAT
|
|
27
|
+
- Fresh `HOME=tmp npm install -g projecta-rrr@1.22.3 --prefix=tmp` lands 47 commands + 13 agents + 18 hooks + 3 wired hook entries with safe defaults. Re-install is idempotent (no duplicate entries).
|
|
28
|
+
- `tool-disallow`: main-thread Read with synthetic transcript (`isSidechain:false`) → BLOCK exit 2; subagent Read with `isSidechain:true` → ALLOW exit 0.
|
|
29
|
+
- 239/239 tests pass; `prepublish:check` clean.
|
|
30
|
+
|
|
31
|
+
### Known followups
|
|
32
|
+
- 35 repos with NULL `github_repo_id` (private/deleted) — operator can run via App-installation token (broader scope) if needed; for now webhook safely skips them with `{"skipped":"unindexed-repo"}`.
|
|
33
|
+
|
|
7
34
|
## [1.22.2] - 2026-04-19
|
|
8
35
|
|
|
9
36
|
**Patch: dynamic routing now actually fires in Claude Code (Opus 4.7+).**
|
package/bin/install.js
CHANGED
|
@@ -1730,6 +1730,47 @@ function install(isGlobal) {
|
|
|
1730
1730
|
console.log(` ${green}✓${reset} Installed hooks`);
|
|
1731
1731
|
}
|
|
1732
1732
|
|
|
1733
|
+
// v1.22.3: wire Phase 83 (model-router), Phase 85 (tool-disallow + edit-batching-nudge)
|
|
1734
|
+
// PreToolUse/PostToolUse hooks. Idempotent — checks for existing entries before
|
|
1735
|
+
// adding. Defaults are SAFE (settings.rrr.tok_disallow=false; model_router=static).
|
|
1736
|
+
// Previously these were only wired when --enable-hosted ran the hosted orchestrator,
|
|
1737
|
+
// so users got the hooks on disk but no settings.json registration.
|
|
1738
|
+
if (bashStatus.available && fs.existsSync(path.join(claudeDir, 'hooks', 'model-router.js'))) {
|
|
1739
|
+
settings.hooks = settings.hooks || {};
|
|
1740
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
1741
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
1742
|
+
settings.rrr = settings.rrr || {};
|
|
1743
|
+
|
|
1744
|
+
const wireOnce = (chain, matcher, hookCmd, label) => {
|
|
1745
|
+
const exists = chain.some((entry) =>
|
|
1746
|
+
entry && Array.isArray(entry.hooks) && entry.hooks.some(
|
|
1747
|
+
(h) => h && typeof h.command === 'string' && h.command.includes(label)
|
|
1748
|
+
)
|
|
1749
|
+
);
|
|
1750
|
+
if (!exists) {
|
|
1751
|
+
chain.push({ matcher, hooks: [{ type: 'command', command: hookCmd }] });
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
return false;
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
let wiredCount = 0;
|
|
1758
|
+
if (wireOnce(settings.hooks.PreToolUse, 'Task|Agent', '$HOME/.claude/hooks/model-router.js', 'model-router.js')) wiredCount++;
|
|
1759
|
+
if (fs.existsSync(path.join(claudeDir, 'hooks', 'tool-disallow.js'))) {
|
|
1760
|
+
if (wireOnce(settings.hooks.PreToolUse, 'Read|Grep|Glob', 'node $HOME/.claude/hooks/tool-disallow.js', 'tool-disallow.js')) wiredCount++;
|
|
1761
|
+
}
|
|
1762
|
+
if (fs.existsSync(path.join(claudeDir, 'hooks', 'edit-batching-nudge.js'))) {
|
|
1763
|
+
if (wireOnce(settings.hooks.PostToolUse, 'Edit|MultiEdit', 'node $HOME/.claude/hooks/edit-batching-nudge.js', 'edit-batching-nudge.js')) wiredCount++;
|
|
1764
|
+
}
|
|
1765
|
+
if (settings.rrr.model_router === undefined) settings.rrr.model_router = 'static';
|
|
1766
|
+
if (settings.rrr.tok_disallow === undefined) settings.rrr.tok_disallow = false;
|
|
1767
|
+
if (wiredCount > 0) {
|
|
1768
|
+
console.log(` ${green}✓${reset} Wired ${wiredCount} v1.22 hook(s) (settings.rrr.model_router=static, tok_disallow=false — opt-in)`);
|
|
1769
|
+
} else {
|
|
1770
|
+
console.log(` ${dim}✓${reset} v1.22 hooks already wired`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1733
1774
|
const statuslineCommand = isGlobal
|
|
1734
1775
|
? '$HOME/.claude/hooks/statusline.sh'
|
|
1735
1776
|
: `${localDirName}/hooks/statusline.sh`;
|
package/hooks/tool-disallow.js
CHANGED
|
@@ -18,9 +18,16 @@
|
|
|
18
18
|
* and scripts/test-install-smoke.js).
|
|
19
19
|
* - env RRR_TOK_DISALLOW=off → kill-switch override.
|
|
20
20
|
*
|
|
21
|
-
* Subagent exemption:
|
|
22
|
-
* - PreToolUse
|
|
23
|
-
*
|
|
21
|
+
* Subagent exemption (v1.22.3 fix — UAT-verified payload schema):
|
|
22
|
+
* - Real Claude Code PreToolUse payload does NOT include `agent_name`.
|
|
23
|
+
* Schema is: {session_id, transcript_path, cwd, permission_mode,
|
|
24
|
+
* hook_event_name, tool_name, tool_input, tool_use_id}
|
|
25
|
+
* - Subagent context is detected by reading the LAST line of
|
|
26
|
+
* `transcript_path` and checking `isSidechain: true`. Sidechain entries
|
|
27
|
+
* are subagent calls; main thread is `isSidechain: false`.
|
|
28
|
+
* - Fallback: if transcript can't be read or parsed, treat as main thread
|
|
29
|
+
* (fail-CLOSED for the BLOCK case — better to over-block than under-block
|
|
30
|
+
* when the goal is token discipline).
|
|
24
31
|
*
|
|
25
32
|
* Settings location (read-only): ~/.claude/settings.json (then project
|
|
26
33
|
* .claude/settings.json — project wins). Never modified here; wiring is
|
|
@@ -66,6 +73,43 @@ function isAgentExempt(agentName, exemptList) {
|
|
|
66
73
|
return true;
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Detect subagent context by reading the tail of the Claude Code transcript
|
|
78
|
+
* and checking for `isSidechain: true` on recent entries.
|
|
79
|
+
*
|
|
80
|
+
* Real Claude Code PreToolUse payload includes `transcript_path` but NOT
|
|
81
|
+
* `agent_name`. The transcript JSONL has one entry per turn with isSidechain
|
|
82
|
+
* marking subagent context (UAT-verified in v1.22.3).
|
|
83
|
+
*
|
|
84
|
+
* Returns: 'subagent' | 'main' | 'unknown'
|
|
85
|
+
*/
|
|
86
|
+
function detectThreadFromTranscript(transcriptPath) {
|
|
87
|
+
if (!transcriptPath || typeof transcriptPath !== 'string') return 'unknown';
|
|
88
|
+
try {
|
|
89
|
+
if (!fs.existsSync(transcriptPath)) return 'unknown';
|
|
90
|
+
// Read last ~64KB only (transcripts can be large; we only need recent context).
|
|
91
|
+
const stat = fs.statSync(transcriptPath);
|
|
92
|
+
const fd = fs.openSync(transcriptPath, 'r');
|
|
93
|
+
const tailSize = Math.min(stat.size, 65536);
|
|
94
|
+
const buf = Buffer.alloc(tailSize);
|
|
95
|
+
fs.readSync(fd, buf, 0, tailSize, Math.max(0, stat.size - tailSize));
|
|
96
|
+
fs.closeSync(fd);
|
|
97
|
+
const lines = buf.toString('utf8').split('\n').filter(Boolean);
|
|
98
|
+
// Walk backward — the most recent non-tool-result entry is the current turn.
|
|
99
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
100
|
+
try {
|
|
101
|
+
const e = JSON.parse(lines[i]);
|
|
102
|
+
if (typeof e.isSidechain === 'boolean') {
|
|
103
|
+
return e.isSidechain ? 'subagent' : 'main';
|
|
104
|
+
}
|
|
105
|
+
} catch { /* skip malformed line */ }
|
|
106
|
+
}
|
|
107
|
+
return 'unknown';
|
|
108
|
+
} catch {
|
|
109
|
+
return 'unknown';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
69
113
|
function decideMode(settings) {
|
|
70
114
|
// Env kill-switch wins.
|
|
71
115
|
if ((process.env.RRR_TOK_DISALLOW || '').toLowerCase() === 'off') return 'off';
|
|
@@ -98,12 +142,18 @@ function main() {
|
|
|
98
142
|
// a misconfigured settings.json never breaks the installer.
|
|
99
143
|
if (process.env.RRR_INTERNAL_TOOL === '1') { process.exit(0); }
|
|
100
144
|
|
|
101
|
-
// Subagent exemption —
|
|
102
|
-
|
|
145
|
+
// Subagent exemption — v1.22.3: detect via transcript_path + isSidechain.
|
|
146
|
+
// Real Claude Code payload schema does NOT include input.agent_name.
|
|
147
|
+
// Fallback to legacy input.agent_name field for forward-compat with future
|
|
148
|
+
// Claude Code versions that may add it.
|
|
149
|
+
const legacyAgentName = input.agent_name || (input.session && input.session.agent_name) || '';
|
|
150
|
+
const thread = legacyAgentName ? 'subagent' : detectThreadFromTranscript(input.transcript_path);
|
|
103
151
|
const settings = loadSettings();
|
|
104
152
|
const exemptList = settings.tok_disallow_exempt;
|
|
105
153
|
|
|
106
|
-
|
|
154
|
+
// Subagent (sidechain) → always exempt. Unknown → treat as main (fail-CLOSED).
|
|
155
|
+
if (thread === 'subagent') { process.exit(0); }
|
|
156
|
+
if (legacyAgentName && isAgentExempt(legacyAgentName, exemptList)) { process.exit(0); }
|
|
107
157
|
|
|
108
158
|
// Explicit per-name allow (rare: when main-thread invocation of a
|
|
109
159
|
// specific named context should still be permitted).
|
|
@@ -127,5 +177,6 @@ module.exports = {
|
|
|
127
177
|
BLOCKED_TOOLS,
|
|
128
178
|
decideMode,
|
|
129
179
|
isAgentExempt,
|
|
180
|
+
detectThreadFromTranscript,
|
|
130
181
|
buildMessage,
|
|
131
182
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projecta-rrr",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.3",
|
|
4
4
|
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
|
|
5
5
|
"bin": {
|
|
6
6
|
"projecta-rrr": "bin/install.js",
|
|
7
7
|
"projecta-rrr-hosted-setup": "bin/hosted-setup.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"postinstall": "node bin/install.js --
|
|
10
|
+
"postinstall": "node bin/install.js --global --yes --skip-optimization && node scripts/register-mcp.js",
|
|
11
11
|
"watch": "node watcher/index.js",
|
|
12
12
|
"watch:verbose": "node watcher/index.js --verbose",
|
|
13
13
|
"watch:claude-code": "node watcher/watchers/claude-code.js --verbose",
|