@agentic15.com/agentic15-claude-zen 3.0.4 → 3.2.1
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 +15 -0
- package/README.md +14 -2
- package/bin/agentic15.js +7 -0
- package/package.json +1 -1
- package/src/cli/UpgradeCommand.js +189 -0
- package/templates/.claude/hooks/require-active-task.js +89 -0
- package/templates/.claude/settings.json +31 -26
- package/templates/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
Copyright 2024-2025 agentic15.com
|
|
9
9
|
|
|
10
|
+
## [3.1.0] - 2025-12-26
|
|
11
|
+
|
|
12
|
+
### Summary
|
|
13
|
+
Minor release adding upgrade command for existing projects.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **`npx agentic15 upgrade`** - New command to upgrade framework files in existing projects
|
|
17
|
+
- Updates .claude/hooks/, settings.json, schemas, and templates
|
|
18
|
+
- Preserves user code (Agent/), plans (.claude/plans/), and local settings
|
|
19
|
+
- Creates automatic backup (.claude.backup/) before upgrading
|
|
20
|
+
- Shows version information and detailed upgrade report
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Updated command count in README from 5 to 6 commands
|
|
24
|
+
|
|
10
25
|
## [3.0.4] - 2025-12-25
|
|
11
26
|
|
|
12
27
|
### Summary
|
package/README.md
CHANGED
|
@@ -18,16 +18,27 @@ npx @agentic15.com/agentic15-claude-zen my-project
|
|
|
18
18
|
npx "@agentic15.com/agentic15-claude-zen" my-project
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**Step 2:
|
|
21
|
+
**Step 2: Navigate Into Project**
|
|
22
22
|
```bash
|
|
23
23
|
cd my-project
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Step 3: Launch Claude Code from Inside Project Directory**
|
|
27
|
+
|
|
28
|
+
Start Claude Code CLI from inside the `my-project` directory. Claude Code MUST be running from inside your project directory to access the framework files.
|
|
29
|
+
|
|
30
|
+
**Step 4: Use Framework Commands**
|
|
31
|
+
```bash
|
|
24
32
|
npx agentic15 auth # One-time GitHub setup
|
|
25
33
|
npx agentic15 plan # Enter interactive mode
|
|
26
34
|
# Type/paste your requirements, press Ctrl+D when done
|
|
27
35
|
npx agentic15 task next # Start first task
|
|
36
|
+
# Make your code changes with Claude...
|
|
28
37
|
npx agentic15 commit # Test, commit, push, PR
|
|
29
38
|
```
|
|
30
39
|
|
|
40
|
+
> **IMPORTANT**: Always launch Claude Code from inside your project directory, not from the parent directory. The framework relies on `.claude/` configuration files that must be accessible from the working directory.
|
|
41
|
+
>
|
|
31
42
|
> **Note**: Project creation uses the full package name `@agentic15.com/agentic15-claude-zen`.
|
|
32
43
|
> Once inside your project, use the short command `agentic15` for all workflows.
|
|
33
44
|
>
|
|
@@ -41,11 +52,12 @@ npx agentic15 commit # Test, commit, push, PR
|
|
|
41
52
|
|
|
42
53
|
## What It Does
|
|
43
54
|
|
|
44
|
-
**
|
|
55
|
+
**6 Commands:**
|
|
45
56
|
- `npx agentic15 plan` - Generate and lock plans
|
|
46
57
|
- `npx agentic15 task next` - Start next task
|
|
47
58
|
- `npx agentic15 commit` - Test + commit + push + PR
|
|
48
59
|
- `npx agentic15 status` - Check progress
|
|
60
|
+
- `npx agentic15 upgrade` - Upgrade framework files
|
|
49
61
|
- `npm test` - Run tests
|
|
50
62
|
|
|
51
63
|
**Automates:**
|
package/bin/agentic15.js
CHANGED
|
@@ -6,6 +6,7 @@ import { TaskCommand } from '../src/cli/TaskCommand.js';
|
|
|
6
6
|
import { CommitCommand } from '../src/cli/CommitCommand.js';
|
|
7
7
|
import { StatusCommand } from '../src/cli/StatusCommand.js';
|
|
8
8
|
import { PlanCommand } from '../src/cli/PlanCommand.js';
|
|
9
|
+
import { UpgradeCommand } from '../src/cli/UpgradeCommand.js';
|
|
9
10
|
|
|
10
11
|
const program = new Command();
|
|
11
12
|
|
|
@@ -47,4 +48,10 @@ program
|
|
|
47
48
|
.argument('[description]', 'Project description (required for first run)')
|
|
48
49
|
.action((description) => PlanCommand.handle(description));
|
|
49
50
|
|
|
51
|
+
// Upgrade framework
|
|
52
|
+
program
|
|
53
|
+
.command('upgrade')
|
|
54
|
+
.description('Upgrade framework files to latest version')
|
|
55
|
+
.action(() => UpgradeCommand.execute());
|
|
56
|
+
|
|
50
57
|
program.parse();
|
package/package.json
CHANGED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024-2025 agentic15.com
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, readFileSync, writeFileSync, unlinkSync, rmdirSync } from 'fs';
|
|
18
|
+
import { join, dirname } from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* UpgradeCommand - Updates framework files in existing projects
|
|
26
|
+
*
|
|
27
|
+
* Upgrades .claude/ framework files while preserving:
|
|
28
|
+
* - User code (Agent/src, Agent/tests)
|
|
29
|
+
* - Active plans (.claude/plans, .claude/ACTIVE-PLAN)
|
|
30
|
+
* - Local settings (.claude/settings.local.json)
|
|
31
|
+
*/
|
|
32
|
+
export class UpgradeCommand {
|
|
33
|
+
static execute() {
|
|
34
|
+
console.log('\n' + '═'.repeat(70));
|
|
35
|
+
console.log('🔄 Agentic15 Claude Zen - Framework Upgrade');
|
|
36
|
+
console.log('═'.repeat(70) + '\n');
|
|
37
|
+
|
|
38
|
+
// Verify we're in a project directory
|
|
39
|
+
if (!existsSync('.claude')) {
|
|
40
|
+
console.log('❌ Not in an Agentic15 project directory');
|
|
41
|
+
console.log(' Run this command from your project root (where .claude/ exists)\n');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get current and package versions
|
|
46
|
+
const currentVersion = this.getCurrentVersion();
|
|
47
|
+
const packageVersion = this.getPackageVersion();
|
|
48
|
+
|
|
49
|
+
console.log(`📦 Current framework version: ${currentVersion || 'unknown'}`);
|
|
50
|
+
console.log(`📦 Package version: ${packageVersion}\n`);
|
|
51
|
+
|
|
52
|
+
// Backup current .claude directory
|
|
53
|
+
console.log('📂 Creating backup...');
|
|
54
|
+
this.createBackup();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Update framework files
|
|
58
|
+
console.log('🔄 Updating framework files...\n');
|
|
59
|
+
this.updateFrameworkFiles();
|
|
60
|
+
|
|
61
|
+
// Update version file
|
|
62
|
+
this.updateVersionFile(packageVersion);
|
|
63
|
+
|
|
64
|
+
console.log('\n✅ Upgrade completed successfully!\n');
|
|
65
|
+
console.log('📋 What was updated:');
|
|
66
|
+
console.log(' - .claude/hooks/ (git hooks)');
|
|
67
|
+
console.log(' - .claude/settings.json (framework settings)');
|
|
68
|
+
console.log(' - .claude/PLAN-SCHEMA.json (plan structure)');
|
|
69
|
+
console.log(' - .claude/PROJECT-PLAN-TEMPLATE.json (plan template)');
|
|
70
|
+
console.log(' - .claude/POST-INSTALL.md (documentation)\n');
|
|
71
|
+
console.log('📋 What was preserved:');
|
|
72
|
+
console.log(' - .claude/plans/ (your project plans)');
|
|
73
|
+
console.log(' - .claude/ACTIVE-PLAN (current plan)');
|
|
74
|
+
console.log(' - .claude/settings.local.json (local settings)');
|
|
75
|
+
console.log(' - Agent/ (your source code)');
|
|
76
|
+
console.log(' - test-site/ (your test site)\n');
|
|
77
|
+
console.log('💾 Backup location: .claude.backup/\n');
|
|
78
|
+
console.log('═'.repeat(70) + '\n');
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log('\n❌ Upgrade failed:', error.message);
|
|
81
|
+
console.log(' Your original files are backed up in .claude.backup/');
|
|
82
|
+
console.log(' To restore: rm -rf .claude && mv .claude.backup .claude\n');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static getCurrentVersion() {
|
|
88
|
+
const versionFile = join('.claude', '.framework-version');
|
|
89
|
+
if (existsSync(versionFile)) {
|
|
90
|
+
return readFileSync(versionFile, 'utf-8').trim();
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static getPackageVersion() {
|
|
96
|
+
const packageJsonPath = join(__dirname, '..', '..', 'package.json');
|
|
97
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
98
|
+
return packageJson.version;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static createBackup() {
|
|
102
|
+
const backupDir = '.claude.backup';
|
|
103
|
+
|
|
104
|
+
// Remove old backup if exists
|
|
105
|
+
if (existsSync(backupDir)) {
|
|
106
|
+
this.removeDirectory(backupDir);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Copy entire .claude directory
|
|
110
|
+
this.copyDirectory('.claude', backupDir);
|
|
111
|
+
console.log(' ✓ Backup created at .claude.backup/\n');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static updateFrameworkFiles() {
|
|
115
|
+
const templatesDir = join(__dirname, '..', '..', 'templates', '.claude');
|
|
116
|
+
|
|
117
|
+
// Files to update (framework files only)
|
|
118
|
+
const filesToUpdate = [
|
|
119
|
+
'hooks/complete-task.js',
|
|
120
|
+
'hooks/enforce-plan-template.js',
|
|
121
|
+
'hooks/post-merge.js',
|
|
122
|
+
'hooks/session-start-context.js',
|
|
123
|
+
'hooks/start-task.js',
|
|
124
|
+
'hooks/validate-git-workflow.js',
|
|
125
|
+
'PLAN-SCHEMA.json',
|
|
126
|
+
'PROJECT-PLAN-TEMPLATE.json',
|
|
127
|
+
'POST-INSTALL.md',
|
|
128
|
+
'settings.json'
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
// Ensure hooks directory exists
|
|
132
|
+
const hooksDir = join('.claude', 'hooks');
|
|
133
|
+
if (!existsSync(hooksDir)) {
|
|
134
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy framework files
|
|
138
|
+
filesToUpdate.forEach(file => {
|
|
139
|
+
const sourcePath = join(templatesDir, file);
|
|
140
|
+
const destPath = join('.claude', file);
|
|
141
|
+
|
|
142
|
+
if (existsSync(sourcePath)) {
|
|
143
|
+
copyFileSync(sourcePath, destPath);
|
|
144
|
+
console.log(` ✓ Updated ${file}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static updateVersionFile(version) {
|
|
150
|
+
const versionFile = join('.claude', '.framework-version');
|
|
151
|
+
writeFileSync(versionFile, version);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static copyDirectory(src, dest) {
|
|
155
|
+
if (!existsSync(dest)) {
|
|
156
|
+
mkdirSync(dest, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
160
|
+
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
const srcPath = join(src, entry.name);
|
|
163
|
+
const destPath = join(dest, entry.name);
|
|
164
|
+
|
|
165
|
+
if (entry.isDirectory()) {
|
|
166
|
+
this.copyDirectory(srcPath, destPath);
|
|
167
|
+
} else {
|
|
168
|
+
copyFileSync(srcPath, destPath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static removeDirectory(dir) {
|
|
174
|
+
if (existsSync(dir)) {
|
|
175
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
const fullPath = join(dir, entry.name);
|
|
179
|
+
if (entry.isDirectory()) {
|
|
180
|
+
this.removeDirectory(fullPath);
|
|
181
|
+
} else {
|
|
182
|
+
unlinkSync(fullPath);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
rmdirSync(dir);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Require Active Task Hook
|
|
5
|
+
*
|
|
6
|
+
* BLOCKS all Edit/Write operations when no active task exists
|
|
7
|
+
*
|
|
8
|
+
* This is the enforcement mechanism that prevents workflow violations
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Read tool use from stdin
|
|
15
|
+
let input = '';
|
|
16
|
+
process.stdin.on('data', chunk => {
|
|
17
|
+
input += chunk;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
try {
|
|
22
|
+
const toolUse = JSON.parse(input);
|
|
23
|
+
|
|
24
|
+
// Only check Edit and Write operations
|
|
25
|
+
if (toolUse.name !== 'Edit' && toolUse.name !== 'Write') {
|
|
26
|
+
// Allow all other tools
|
|
27
|
+
process.exit(0);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check if active task exists
|
|
32
|
+
const activePlanFile = '.claude/ACTIVE-PLAN';
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(activePlanFile)) {
|
|
35
|
+
console.error('\n' + '═'.repeat(70));
|
|
36
|
+
console.error('❌ BLOCKED: No active plan exists');
|
|
37
|
+
console.error('═'.repeat(70));
|
|
38
|
+
console.error('\nYou MUST have an active plan before making code changes.');
|
|
39
|
+
console.error('\nTo create a plan:');
|
|
40
|
+
console.error(' npx agentic15 plan "Your project requirements"');
|
|
41
|
+
console.error('\n' + '═'.repeat(70) + '\n');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const activePlan = fs.readFileSync(activePlanFile, 'utf8').trim();
|
|
47
|
+
const planDir = path.join('.claude/plans', activePlan);
|
|
48
|
+
const trackerPath = path.join(planDir, 'TASK-TRACKER.json');
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(trackerPath)) {
|
|
51
|
+
console.error('\n' + '═'.repeat(70));
|
|
52
|
+
console.error('❌ BLOCKED: Task tracker not found');
|
|
53
|
+
console.error('═'.repeat(70));
|
|
54
|
+
console.error('\nPlan exists but task tracker is missing.');
|
|
55
|
+
console.error('This indicates a corrupted plan state.');
|
|
56
|
+
console.error('\n' + '═'.repeat(70) + '\n');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tracker = JSON.parse(fs.readFileSync(trackerPath, 'utf8'));
|
|
62
|
+
|
|
63
|
+
if (!tracker.activeTask) {
|
|
64
|
+
console.error('\n' + '═'.repeat(70));
|
|
65
|
+
console.error('❌ BLOCKED: No active task');
|
|
66
|
+
console.error('═'.repeat(70));
|
|
67
|
+
console.error('\nYou MUST have an active task before making code changes.');
|
|
68
|
+
console.error('\nTo start a task:');
|
|
69
|
+
console.error(' npx agentic15 task next');
|
|
70
|
+
console.error('\nThis will:');
|
|
71
|
+
console.error(' 1. Show you the next pending task');
|
|
72
|
+
console.error(' 2. Create a feature branch');
|
|
73
|
+
console.error(' 3. Create a GitHub issue');
|
|
74
|
+
console.error(' 4. Mark the task as active');
|
|
75
|
+
console.error('\nThen you can make your changes.');
|
|
76
|
+
console.error('\n' + '═'.repeat(70) + '\n');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Active task exists - allow the operation
|
|
82
|
+
process.exit(0);
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// If we can't parse or check, fail safe and allow
|
|
86
|
+
// (don't want to block legitimate work due to hook errors)
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
@@ -4,15 +4,11 @@
|
|
|
4
4
|
"Read(**)",
|
|
5
5
|
"Glob",
|
|
6
6
|
"Grep",
|
|
7
|
-
"Bash(npm run
|
|
8
|
-
"Bash(git status
|
|
9
|
-
"Bash(git log
|
|
10
|
-
"Bash(git diff
|
|
11
|
-
"Bash(git branch
|
|
12
|
-
"Bash(git checkout main)",
|
|
13
|
-
"Bash(git add *)",
|
|
14
|
-
"Bash(git commit*)",
|
|
15
|
-
"Bash(git push*)",
|
|
7
|
+
"Bash(npm run:*)",
|
|
8
|
+
"Bash(git status:*)",
|
|
9
|
+
"Bash(git log:*)",
|
|
10
|
+
"Bash(git diff:*)",
|
|
11
|
+
"Bash(git branch:*)",
|
|
16
12
|
"Edit(./Agent/**)",
|
|
17
13
|
"Write(./Agent/**)",
|
|
18
14
|
"Edit(./scripts/**)",
|
|
@@ -42,29 +38,29 @@
|
|
|
42
38
|
"Edit(./.claude/TASK-TRACKER.json)",
|
|
43
39
|
"Read(./.claude/POST-INSTALL.md)",
|
|
44
40
|
"Read(./docs/**)",
|
|
45
|
-
"Bash(agentic15
|
|
46
|
-
"Bash(git
|
|
47
|
-
"Bash(gh
|
|
41
|
+
"Bash(agentic15:*)",
|
|
42
|
+
"Bash(git:*)",
|
|
43
|
+
"Bash(gh:*)",
|
|
48
44
|
"Bash(npm run task:*)",
|
|
49
45
|
"Bash(npm run plan:*)",
|
|
50
|
-
"Bash(curl
|
|
51
|
-
"Bash(wget
|
|
52
|
-
"Bash(rm -rf
|
|
53
|
-
"Bash(sudo
|
|
54
|
-
"Bash(npm install
|
|
55
|
-
"Bash(npm publish
|
|
56
|
-
"Bash(git push --force
|
|
57
|
-
"Bash(git merge
|
|
58
|
-
"Bash(psql
|
|
59
|
-
"Bash(mysql
|
|
60
|
-
"Bash(sqlite3
|
|
61
|
-
"Bash(mongosh
|
|
62
|
-
"Bash(redis-cli
|
|
46
|
+
"Bash(curl:*)",
|
|
47
|
+
"Bash(wget:*)",
|
|
48
|
+
"Bash(rm -rf:*)",
|
|
49
|
+
"Bash(sudo:*)",
|
|
50
|
+
"Bash(npm install:*)",
|
|
51
|
+
"Bash(npm publish:*)",
|
|
52
|
+
"Bash(git push --force:*)",
|
|
53
|
+
"Bash(git merge:*)",
|
|
54
|
+
"Bash(psql:*)",
|
|
55
|
+
"Bash(mysql:*)",
|
|
56
|
+
"Bash(sqlite3:*)",
|
|
57
|
+
"Bash(mongosh:*)",
|
|
58
|
+
"Bash(redis-cli:*)",
|
|
63
59
|
"Bash(node ./scripts/**)",
|
|
64
60
|
"Bash(node ./Agent/db/**)",
|
|
65
61
|
"Bash(bash ./scripts/**)",
|
|
66
62
|
"Bash(sh ./scripts/**)",
|
|
67
|
-
"Bash(git checkout -b
|
|
63
|
+
"Bash(git checkout -b:*)",
|
|
68
64
|
"WebFetch",
|
|
69
65
|
"WebSearch"
|
|
70
66
|
]
|
|
@@ -164,6 +160,15 @@
|
|
|
164
160
|
}
|
|
165
161
|
],
|
|
166
162
|
"PreToolUse": [
|
|
163
|
+
{
|
|
164
|
+
"matcher": "Edit|Write",
|
|
165
|
+
"hooks": [
|
|
166
|
+
{
|
|
167
|
+
"type": "command",
|
|
168
|
+
"command": "node .claude/hooks/require-active-task.js"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
},
|
|
167
172
|
{
|
|
168
173
|
"matcher": "*",
|
|
169
174
|
"hooks": [
|