cc-dev-template 0.1.32 → 0.1.33

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.
Files changed (34) hide show
  1. package/bin/install.js +5 -9
  2. package/package.json +1 -1
  3. package/src/commands/done.md +52 -0
  4. package/src/commands/prime.md +40 -4
  5. package/src/skills/initialize-project/SKILL.md +88 -0
  6. package/src/agents/adr-agent.md +0 -167
  7. package/src/agents/claude-md-agent.md +0 -124
  8. package/src/agents/decomposition-agent.md +0 -170
  9. package/src/agents/execution-agent.md +0 -232
  10. package/src/agents/rca-agent.md +0 -192
  11. package/src/agents/tdd-agent.md +0 -205
  12. package/src/commands/create-agent-skill.md +0 -11
  13. package/src/commands/finalize.md +0 -48
  14. package/src/commands/heal-skill.md +0 -69
  15. package/src/hooks/orchestration-guidance.sh +0 -56
  16. package/src/hooks/orchestration-hook.json +0 -14
  17. package/src/scripts/adr-list.js +0 -298
  18. package/src/scripts/adr-tags.js +0 -242
  19. package/src/scripts/validate-yaml.js +0 -142
  20. package/src/scripts/yaml-validation-hook.json +0 -15
  21. package/src/skills/orchestration/SKILL.md +0 -161
  22. package/src/skills/orchestration/references/debugging/describe.md +0 -144
  23. package/src/skills/orchestration/references/debugging/fix.md +0 -117
  24. package/src/skills/orchestration/references/debugging/learn.md +0 -185
  25. package/src/skills/orchestration/references/debugging/rca.md +0 -92
  26. package/src/skills/orchestration/references/debugging/verify.md +0 -102
  27. package/src/skills/orchestration/references/execution/complete.md +0 -175
  28. package/src/skills/orchestration/references/execution/start.md +0 -77
  29. package/src/skills/orchestration/references/execution/tasks.md +0 -114
  30. package/src/skills/orchestration/references/planning/draft.md +0 -269
  31. package/src/skills/orchestration/references/planning/explore.md +0 -160
  32. package/src/skills/orchestration/references/planning/finalize.md +0 -184
  33. package/src/skills/orchestration/references/planning/start.md +0 -119
  34. package/src/skills/orchestration/scripts/plan-status.js +0 -355
@@ -1,298 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * adr-list.js - List ADRs with optional filtering by tags and status
5
- *
6
- * This CLI script scans all YAML ADR files in the project's .claude/adrs/
7
- * and returns their metadata, with optional filtering capabilities. Designed
8
- * to support progressive ADR filtering (per ADR-007) by providing descriptions
9
- * without full content.
10
- *
11
- * Usage:
12
- * node ~/.claude/scripts/adr-list.js # All ADRs
13
- * node ~/.claude/scripts/adr-list.js --tags=architecture # Filter by single tag
14
- * node ~/.claude/scripts/adr-list.js --tags=agents,planning # Filter by multiple tags (OR logic)
15
- * node ~/.claude/scripts/adr-list.js --status=Accepted # Filter by status
16
- * node ~/.claude/scripts/adr-list.js --tags=adrs --status=Accepted # Combine filters
17
- * node ~/.claude/scripts/adr-list.js --include-parent # Include parent repo ADRs (in submodule)
18
- *
19
- * Flags:
20
- * --tags=tag1,tag2 Filter ADRs that have ANY of the specified tags (OR logic)
21
- * --status=Status Filter ADRs that match the exact status string
22
- * --include-parent When in a git submodule, also scan parent repo's .claude/adrs/
23
- *
24
- * Output:
25
- * JSON array to stdout: [{ id, title, description, tags, status, date, source, sourcePath }]
26
- *
27
- * Example output:
28
- * [
29
- * {
30
- * "id": "ADR-001",
31
- * "title": "Use YAML ADRs for Architectural Decisions",
32
- * "description": "Establishes YAML-formatted ADRs as the primary mechanism...",
33
- * "tags": ["architecture", "adrs", "format"],
34
- * "status": "Accepted",
35
- * "date": "2025-11-22",
36
- * "source": "local",
37
- * "sourcePath": "/path/to/project"
38
- * }
39
- * ]
40
- *
41
- * Note: This script supplements the adr-agent (per ADR-009), providing
42
- * quick CLI access to ADR listings without invoking the full agent.
43
- */
44
-
45
- const fs = require('fs');
46
- const path = require('path');
47
- const yaml = require('js-yaml');
48
- const { execSync } = require('child_process');
49
-
50
- // ADRs are in the project's .claude/adrs/ directory (relative to CWD)
51
- const ADRS_DIR = path.join(process.cwd(), '.claude', 'adrs');
52
-
53
- /**
54
- * Parse command line arguments for --tags, --status, and --include-parent flags
55
- * @returns {{ tags: string[] | null, status: string | null, includeParent: boolean }}
56
- */
57
- function parseArgs() {
58
- const args = process.argv.slice(2);
59
- let tags = null;
60
- let status = null;
61
- let includeParent = false;
62
-
63
- for (const arg of args) {
64
- // Handle --tags=value or --tags value
65
- if (arg.startsWith('--tags=')) {
66
- const value = arg.slice('--tags='.length);
67
- tags = value.split(',').map(t => t.trim()).filter(t => t.length > 0);
68
- }
69
- // Handle --status=value or --status value
70
- else if (arg.startsWith('--status=')) {
71
- status = arg.slice('--status='.length).trim();
72
- }
73
- // Handle --include-parent flag
74
- else if (arg === '--include-parent') {
75
- includeParent = true;
76
- }
77
- }
78
-
79
- return { tags, status, includeParent };
80
- }
81
-
82
- /**
83
- * Detect submodule context using git commands
84
- * @returns {{ isInSubmodule: boolean, parentRepoPath: string | null, currentSubmodulePath: string | null }}
85
- */
86
- function detectSubmoduleContext() {
87
- const result = {
88
- isInSubmodule: false,
89
- parentRepoPath: null,
90
- currentSubmodulePath: null
91
- };
92
-
93
- try {
94
- // Check if we're in a git repo at all
95
- execSync('git rev-parse --git-dir', { stdio: 'pipe' });
96
-
97
- // Check if we're in a submodule by looking for a superproject
98
- const superprojectPath = execSync('git rev-parse --show-superproject-working-tree', {
99
- encoding: 'utf8',
100
- stdio: ['pipe', 'pipe', 'pipe']
101
- }).trim();
102
-
103
- if (superprojectPath) {
104
- result.isInSubmodule = true;
105
- result.parentRepoPath = superprojectPath;
106
-
107
- // Get the current submodule path relative to parent
108
- // This is the path from the parent repo root to the submodule
109
- const currentPath = process.cwd();
110
- result.currentSubmodulePath = path.relative(superprojectPath, currentPath);
111
- }
112
- } catch (error) {
113
- // Not in a git repo or git not available - silently ignore
114
- }
115
-
116
- return result;
117
- }
118
-
119
- /**
120
- * Read and parse a YAML ADR file, extracting metadata fields
121
- * @param {string} filePath - Absolute path to the YAML file
122
- * @param {string} source - "local" or "inherited"
123
- * @param {string} sourcePath - Path to the repo containing this ADR
124
- * @returns {{ id: string, title: string, description: string, tags: string[], status: string, date: string, scope: string[] | null, source: string, sourcePath: string } | null}
125
- */
126
- function parseAdrFile(filePath, source, sourcePath) {
127
- try {
128
- const content = fs.readFileSync(filePath, 'utf8');
129
- const data = yaml.load(content);
130
-
131
- // Extract fields, handling missing optional fields gracefully
132
- const id = data?.id || path.basename(filePath, '.yaml');
133
- const title = data?.title || '';
134
- // Clean up description - remove trailing newlines from YAML multi-line strings
135
- const description = (data?.description || '').trim();
136
- const tags = Array.isArray(data?.tags) ? data.tags : [];
137
- const status = data?.status || '';
138
- const date = data?.date || '';
139
- // Scope is optional - null means global (applies everywhere)
140
- const scope = Array.isArray(data?.scope) ? data.scope : null;
141
-
142
- return { id, title, description, tags, status, date, scope, source, sourcePath };
143
- } catch (error) {
144
- // Log to stderr so it doesn't pollute JSON output
145
- console.error(`Warning: Failed to parse ${filePath}: ${error.message}`);
146
- return null;
147
- }
148
- }
149
-
150
- /**
151
- * Check if an ADR matches the given tag filter (OR logic)
152
- * @param {string[]} adrTags - Tags from the ADR
153
- * @param {string[]} filterTags - Tags to filter by
154
- * @returns {boolean} - True if ADR has ANY of the filter tags
155
- */
156
- function matchesTags(adrTags, filterTags) {
157
- if (!filterTags || filterTags.length === 0) {
158
- return true; // No filter = match all
159
- }
160
- // OR logic: return true if ADR has ANY of the filter tags
161
- return filterTags.some(filterTag => adrTags.includes(filterTag));
162
- }
163
-
164
- /**
165
- * Check if an ADR matches the given status filter (exact match)
166
- * @param {string} adrStatus - Status from the ADR
167
- * @param {string | null} filterStatus - Status to filter by
168
- * @returns {boolean} - True if status matches exactly
169
- */
170
- function matchesStatus(adrStatus, filterStatus) {
171
- if (!filterStatus) {
172
- return true; // No filter = match all
173
- }
174
- return adrStatus === filterStatus;
175
- }
176
-
177
- /**
178
- * Check if an inherited ADR's scope applies to the current submodule
179
- * @param {string[] | null} scope - ADR's scope field (null = global, array = specific submodules)
180
- * @param {string | null} currentSubmodulePath - Current submodule path relative to parent
181
- * @returns {boolean} - True if ADR applies to current context
182
- */
183
- function matchesScope(scope, currentSubmodulePath) {
184
- // null scope = global, applies everywhere
185
- if (scope === null) {
186
- return true;
187
- }
188
-
189
- // If we don't know the current submodule path, be conservative and include it
190
- if (!currentSubmodulePath) {
191
- return true;
192
- }
193
-
194
- // Check if current submodule path matches any scope entry
195
- return scope.some(scopePath => {
196
- // Exact match or prefix match (for nested submodules)
197
- return currentSubmodulePath === scopePath ||
198
- currentSubmodulePath.startsWith(scopePath + '/');
199
- });
200
- }
201
-
202
- /**
203
- * Collect ADRs from a specific directory
204
- * @param {string} adrsDir - Directory to scan for ADRs
205
- * @param {string} source - "local" or "inherited"
206
- * @param {string} sourcePath - Path to the repo containing this ADR
207
- * @param {{ tags: string[] | null, status: string | null }} filters
208
- * @param {string | null} currentSubmodulePath - For inherited ADRs, used for scope filtering
209
- * @returns {Array<object>}
210
- */
211
- function collectAdrsFromDir(adrsDir, source, sourcePath, filters, currentSubmodulePath) {
212
- const adrs = [];
213
-
214
- // Check if ADRs directory exists
215
- if (!fs.existsSync(adrsDir)) {
216
- return adrs;
217
- }
218
-
219
- // Read all .yaml files in the ADRs directory
220
- const files = fs.readdirSync(adrsDir)
221
- .filter(file => file.endsWith('.yaml'))
222
- .sort() // Sort alphabetically for consistent ordering
223
- .map(file => path.join(adrsDir, file));
224
-
225
- // Process each ADR file
226
- for (const filePath of files) {
227
- const adr = parseAdrFile(filePath, source, sourcePath);
228
- if (!adr) continue;
229
-
230
- // Apply tag and status filters
231
- if (!matchesTags(adr.tags, filters.tags)) continue;
232
- if (!matchesStatus(adr.status, filters.status)) continue;
233
-
234
- // For inherited ADRs, apply scope filtering
235
- if (source === 'inherited' && !matchesScope(adr.scope, currentSubmodulePath)) {
236
- continue;
237
- }
238
-
239
- adrs.push(adr);
240
- }
241
-
242
- return adrs;
243
- }
244
-
245
- /**
246
- * Scan ADRs directory and collect all ADR metadata with optional filtering
247
- * @param {{ tags: string[] | null, status: string | null, includeParent: boolean }} options
248
- * @returns {Array<{ id: string, title: string, description: string, tags: string[], status: string, date: string, source: string, sourcePath: string }>}
249
- */
250
- function collectAdrs(options) {
251
- const { tags, status, includeParent } = options;
252
- const filters = { tags, status };
253
- let allAdrs = [];
254
-
255
- // Always collect local ADRs
256
- const localAdrs = collectAdrsFromDir(ADRS_DIR, 'local', process.cwd(), filters, null);
257
- allAdrs = allAdrs.concat(localAdrs);
258
-
259
- // If --include-parent flag is set and we're in a submodule, also collect parent ADRs
260
- if (includeParent) {
261
- const submoduleContext = detectSubmoduleContext();
262
-
263
- if (submoduleContext.isInSubmodule && submoduleContext.parentRepoPath) {
264
- const parentAdrsDir = path.join(submoduleContext.parentRepoPath, '.claude', 'adrs');
265
- const parentAdrs = collectAdrsFromDir(
266
- parentAdrsDir,
267
- 'inherited',
268
- submoduleContext.parentRepoPath,
269
- filters,
270
- submoduleContext.currentSubmodulePath
271
- );
272
- allAdrs = allAdrs.concat(parentAdrs);
273
- }
274
- }
275
-
276
- // For backward compatibility, remove scope field from output (internal use only)
277
- // and only include source/sourcePath when --include-parent is used
278
- return allAdrs.map(adr => {
279
- const { scope, ...rest } = adr;
280
- if (!includeParent) {
281
- // For backward compatibility, don't include source fields when not using --include-parent
282
- const { source, sourcePath, ...backwardCompatible } = rest;
283
- return backwardCompatible;
284
- }
285
- return rest;
286
- });
287
- }
288
-
289
- // Main execution
290
- function main() {
291
- const options = parseArgs();
292
- const adrs = collectAdrs(options);
293
-
294
- // Output JSON to stdout
295
- console.log(JSON.stringify(adrs, null, 2));
296
- }
297
-
298
- main();
@@ -1,242 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * adr-tags.js - List all distinct tags across all ADRs with usage counts
5
- *
6
- * This CLI script scans all YAML ADR files in the project's .claude/adrs/
7
- * and extracts tag information, producing a sorted summary of tag usage.
8
- *
9
- * Usage:
10
- * node ~/.claude/scripts/adr-tags.js # Tags from local ADRs only
11
- * node ~/.claude/scripts/adr-tags.js --include-parent # Include parent repo ADRs (in submodule)
12
- *
13
- * Flags:
14
- * --include-parent When in a git submodule, also include tags from parent repo's ADRs
15
- *
16
- * Output:
17
- * JSON array to stdout: [{ tag, count, adrs }] sorted by count descending
18
- *
19
- * Example output:
20
- * [
21
- * { "tag": "architecture", "count": 8, "adrs": ["ADR-001", "ADR-003"] },
22
- * { "tag": "agents", "count": 4, "adrs": ["ADR-003", "ADR-004"] }
23
- * ]
24
- *
25
- * Note: This script supplements the adr-agent (per ADR-009), providing
26
- * quick CLI access to tag information without invoking the full agent.
27
- */
28
-
29
- const fs = require('fs');
30
- const path = require('path');
31
- const yaml = require('js-yaml');
32
- const { execSync } = require('child_process');
33
-
34
- // ADRs are in the project's .claude/adrs/ directory (relative to CWD)
35
- const ADRS_DIR = path.join(process.cwd(), '.claude', 'adrs');
36
-
37
- /**
38
- * Parse command line arguments for --include-parent flag
39
- * @returns {{ includeParent: boolean }}
40
- */
41
- function parseArgs() {
42
- const args = process.argv.slice(2);
43
- let includeParent = false;
44
-
45
- for (const arg of args) {
46
- if (arg === '--include-parent') {
47
- includeParent = true;
48
- }
49
- }
50
-
51
- return { includeParent };
52
- }
53
-
54
- /**
55
- * Detect submodule context using git commands
56
- * @returns {{ isInSubmodule: boolean, parentRepoPath: string | null, currentSubmodulePath: string | null }}
57
- */
58
- function detectSubmoduleContext() {
59
- const result = {
60
- isInSubmodule: false,
61
- parentRepoPath: null,
62
- currentSubmodulePath: null
63
- };
64
-
65
- try {
66
- // Check if we're in a git repo at all
67
- execSync('git rev-parse --git-dir', { stdio: 'pipe' });
68
-
69
- // Check if we're in a submodule by looking for a superproject
70
- const superprojectPath = execSync('git rev-parse --show-superproject-working-tree', {
71
- encoding: 'utf8',
72
- stdio: ['pipe', 'pipe', 'pipe']
73
- }).trim();
74
-
75
- if (superprojectPath) {
76
- result.isInSubmodule = true;
77
- result.parentRepoPath = superprojectPath;
78
-
79
- // Get the current submodule path relative to parent
80
- const currentPath = process.cwd();
81
- result.currentSubmodulePath = path.relative(superprojectPath, currentPath);
82
- }
83
- } catch (error) {
84
- // Not in a git repo or git not available - silently ignore
85
- }
86
-
87
- return result;
88
- }
89
-
90
- /**
91
- * Check if an inherited ADR's scope applies to the current submodule
92
- * @param {string[] | null} scope - ADR's scope field (null = global, array = specific submodules)
93
- * @param {string | null} currentSubmodulePath - Current submodule path relative to parent
94
- * @returns {boolean} - True if ADR applies to current context
95
- */
96
- function matchesScope(scope, currentSubmodulePath) {
97
- // null scope = global, applies everywhere
98
- if (scope === null) {
99
- return true;
100
- }
101
-
102
- // If we don't know the current submodule path, be conservative and include it
103
- if (!currentSubmodulePath) {
104
- return true;
105
- }
106
-
107
- // Check if current submodule path matches any scope entry
108
- return scope.some(scopePath => {
109
- // Exact match or prefix match (for nested submodules)
110
- return currentSubmodulePath === scopePath ||
111
- currentSubmodulePath.startsWith(scopePath + '/');
112
- });
113
- }
114
-
115
- /**
116
- * Read and parse a YAML ADR file, extracting id, tags, and scope
117
- * @param {string} filePath - Absolute path to the YAML file
118
- * @returns {{ id: string, tags: string[], scope: string[] | null } | null} - Parsed data or null on error
119
- */
120
- function parseAdrFile(filePath) {
121
- try {
122
- const content = fs.readFileSync(filePath, 'utf8');
123
- const data = yaml.load(content);
124
-
125
- // Extract id, tags, and scope, handling missing fields gracefully
126
- const id = data?.id || path.basename(filePath, '.yaml');
127
- const tags = Array.isArray(data?.tags) ? data.tags : [];
128
- const scope = Array.isArray(data?.scope) ? data.scope : null;
129
-
130
- return { id, tags, scope };
131
- } catch (error) {
132
- // Log to stderr so it doesn't pollute JSON output
133
- console.error(`Warning: Failed to parse ${filePath}: ${error.message}`);
134
- return null;
135
- }
136
- }
137
-
138
- /**
139
- * Collect tags from a specific ADRs directory
140
- * @param {string} adrsDir - Directory to scan for ADRs
141
- * @param {Map<string, string[]>} tagMap - Map to add tags to
142
- * @param {boolean} isInherited - Whether these are inherited ADRs
143
- * @param {string | null} currentSubmodulePath - For scope filtering on inherited ADRs
144
- */
145
- function collectTagsFromDir(adrsDir, tagMap, isInherited, currentSubmodulePath) {
146
- // Check if ADRs directory exists
147
- if (!fs.existsSync(adrsDir)) {
148
- return;
149
- }
150
-
151
- // Read all .yaml files in the ADRs directory
152
- const files = fs.readdirSync(adrsDir)
153
- .filter(file => file.endsWith('.yaml'))
154
- .map(file => path.join(adrsDir, file));
155
-
156
- // Process each ADR file
157
- for (const filePath of files) {
158
- const adr = parseAdrFile(filePath);
159
- if (!adr) continue;
160
-
161
- // For inherited ADRs, apply scope filtering
162
- if (isInherited && !matchesScope(adr.scope, currentSubmodulePath)) {
163
- continue;
164
- }
165
-
166
- // Add this ADR's id to each of its tags
167
- for (const tag of adr.tags) {
168
- if (!tagMap.has(tag)) {
169
- tagMap.set(tag, []);
170
- }
171
- // Avoid duplicates (in case same ADR id exists in both local and parent)
172
- const adrList = tagMap.get(tag);
173
- if (!adrList.includes(adr.id)) {
174
- adrList.push(adr.id);
175
- }
176
- }
177
- }
178
- }
179
-
180
- /**
181
- * Scan ADRs directories and collect all tag information
182
- * @param {{ includeParent: boolean }} options
183
- * @returns {Map<string, string[]>} - Map of tag -> array of ADR ids
184
- */
185
- function collectTags(options) {
186
- const tagMap = new Map();
187
- const { includeParent } = options;
188
-
189
- // Collect tags from local ADRs
190
- collectTagsFromDir(ADRS_DIR, tagMap, false, null);
191
-
192
- // If --include-parent flag is set and we're in a submodule, also collect parent tags
193
- if (includeParent) {
194
- const submoduleContext = detectSubmoduleContext();
195
-
196
- if (submoduleContext.isInSubmodule && submoduleContext.parentRepoPath) {
197
- const parentAdrsDir = path.join(submoduleContext.parentRepoPath, '.claude', 'adrs');
198
- collectTagsFromDir(parentAdrsDir, tagMap, true, submoduleContext.currentSubmodulePath);
199
- }
200
- }
201
-
202
- return tagMap;
203
- }
204
-
205
- /**
206
- * Format tag data as sorted output array
207
- * @param {Map<string, string[]>} tagMap - Map of tag -> array of ADR ids
208
- * @returns {Array<{ tag: string, count: number, adrs: string[] }>}
209
- */
210
- function formatOutput(tagMap) {
211
- const result = [];
212
-
213
- for (const [tag, adrs] of tagMap.entries()) {
214
- result.push({
215
- tag,
216
- count: adrs.length,
217
- adrs: adrs.sort() // Sort ADR ids alphabetically within each tag
218
- });
219
- }
220
-
221
- // Sort by count descending, then alphabetically by tag name for ties
222
- result.sort((a, b) => {
223
- if (b.count !== a.count) {
224
- return b.count - a.count;
225
- }
226
- return a.tag.localeCompare(b.tag);
227
- });
228
-
229
- return result;
230
- }
231
-
232
- // Main execution
233
- function main() {
234
- const options = parseArgs();
235
- const tagMap = collectTags(options);
236
- const output = formatOutput(tagMap);
237
-
238
- // Output JSON to stdout
239
- console.log(JSON.stringify(output, null, 2));
240
- }
241
-
242
- main();
@@ -1,142 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * validate-yaml.js - Validate YAML syntax in files
5
- *
6
- * This script validates YAML files after they are created or modified through Claude Code's
7
- * Edit or Write tools. It's invoked by the PostToolUse hook system with JSON input via stdin
8
- * containing the file path that was just modified.
9
- *
10
- * Usage:
11
- * echo '{"tool_input":{"file_path":"/path/to/file.yaml"}}' | node validate-yaml.js
12
- *
13
- * Input format (JSON via stdin):
14
- * {
15
- * "tool_input": {
16
- * "file_path": "/absolute/path/to/file.yaml"
17
- * }
18
- * }
19
- *
20
- * Exit codes:
21
- * 0 - File is valid YAML or not a YAML file (silent success)
22
- * 2 - File is invalid YAML (parsing error written to stderr)
23
- *
24
- * Behavior:
25
- * - Non-YAML files (.yaml and .yml extensions) pass through silently with exit 0
26
- * - Valid YAML files produce no output and exit 0
27
- * - Invalid YAML files produce parsing error on stderr and exit 2
28
- *
29
- * Dependencies:
30
- * - js-yaml (for YAML parsing)
31
- * - Node.js built-ins (fs, path)
32
- */
33
-
34
- const fs = require('fs');
35
- const path = require('path');
36
- const yaml = require('js-yaml');
37
-
38
- /**
39
- * Read JSON from stdin and extract file_path
40
- * @returns {Promise<string | null>} - The file path from tool_input, or null if invalid
41
- */
42
- async function readStdin() {
43
- return new Promise((resolve, reject) => {
44
- let data = '';
45
-
46
- process.stdin.setEncoding('utf8');
47
- process.stdin.on('data', chunk => {
48
- data += chunk;
49
- });
50
-
51
- process.stdin.on('end', () => {
52
- try {
53
- const parsed = JSON.parse(data);
54
- const filePath = parsed?.tool_input?.file_path;
55
- resolve(filePath || null);
56
- } catch (error) {
57
- // If we can't parse the input, still exit cleanly - don't interrupt the user's work
58
- resolve(null);
59
- }
60
- });
61
-
62
- process.stdin.on('error', () => {
63
- // If there's an issue reading stdin, exit cleanly
64
- resolve(null);
65
- });
66
- });
67
- }
68
-
69
- /**
70
- * Check if a file has a YAML extension
71
- * @param {string} filePath - The file path to check
72
- * @returns {boolean} - True if the file ends with .yaml or .yml
73
- */
74
- function isYamlFile(filePath) {
75
- return filePath.endsWith('.yaml') || filePath.endsWith('.yml');
76
- }
77
-
78
- /**
79
- * Check if a file is in a Helm charts directory (uses Go templating, not valid YAML)
80
- * @param {string} filePath - The file path to check
81
- * @returns {boolean} - True if the file is in a charts directory
82
- */
83
- function isHelmChartFile(filePath) {
84
- return filePath.includes('/charts/');
85
- }
86
-
87
- /**
88
- * Validate YAML syntax by attempting to parse the file
89
- * @param {string} filePath - The absolute path to the YAML file
90
- * @returns {{ valid: boolean, error?: string }} - Validation result with optional error message
91
- */
92
- function validateYaml(filePath) {
93
- try {
94
- const content = fs.readFileSync(filePath, 'utf8');
95
- yaml.load(content);
96
- return { valid: true };
97
- } catch (error) {
98
- return {
99
- valid: false,
100
- error: error.message
101
- };
102
- }
103
- }
104
-
105
- /**
106
- * Main execution - validate YAML file from tool input
107
- */
108
- async function main() {
109
- const filePath = await readStdin();
110
-
111
- // If we couldn't extract a file path, exit silently
112
- if (!filePath) {
113
- process.exit(0);
114
- }
115
-
116
- // If it's not a YAML file, pass through silently
117
- if (!isYamlFile(filePath)) {
118
- process.exit(0);
119
- }
120
-
121
- // If it's a Helm chart file, skip validation (uses Go templating syntax)
122
- if (isHelmChartFile(filePath)) {
123
- process.exit(0);
124
- }
125
-
126
- // Validate the YAML file
127
- const result = validateYaml(filePath);
128
-
129
- if (result.valid) {
130
- // Valid YAML - silent success
131
- process.exit(0);
132
- } else {
133
- // Invalid YAML - report error to stderr
134
- console.error(`YAML validation error in ${filePath}:\n${result.error}`);
135
- process.exit(2);
136
- }
137
- }
138
-
139
- main().catch(() => {
140
- // If anything goes wrong, exit silently to avoid disrupting the user's workflow
141
- process.exit(0);
142
- });
@@ -1,15 +0,0 @@
1
- {
2
- "hooks": {
3
- "PostToolUse": [
4
- {
5
- "matcher": "Edit|Write",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "node ~/.claude/scripts/validate-yaml.js"
10
- }
11
- ]
12
- }
13
- ]
14
- }
15
- }