@fredericboyer/dev-team 0.1.2 → 0.3.0
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/README.md +128 -0
- package/bin/dev-team.js +1 -21
- package/dist/bin/dev-team.d.ts +1 -0
- package/dist/bin/dev-team.js +36 -0
- package/dist/bin/dev-team.js.map +1 -0
- package/dist/create-agent.d.ts +1 -0
- package/dist/create-agent.js +106 -0
- package/dist/create-agent.js.map +1 -0
- package/dist/files.d.ts +51 -0
- package/dist/files.js +155 -0
- package/dist/files.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +358 -0
- package/dist/init.js.map +1 -0
- package/dist/prompts.d.ts +25 -0
- package/dist/prompts.js +109 -0
- package/dist/prompts.js.map +1 -0
- package/dist/scan.d.ts +15 -0
- package/dist/scan.js +187 -0
- package/dist/scan.js.map +1 -0
- package/dist/update.d.ts +5 -0
- package/dist/update.js +229 -0
- package/dist/update.js.map +1 -0
- package/package.json +30 -9
- package/templates/CLAUDE.md +21 -0
- package/templates/agent-memory/dev-team-architect/MEMORY.md +12 -0
- package/templates/agent-memory/dev-team-docs/MEMORY.md +12 -0
- package/templates/agent-memory/dev-team-lead/MEMORY.md +12 -0
- package/templates/agent-memory/dev-team-release/MEMORY.md +12 -0
- package/templates/agents/dev-team-architect.md +62 -0
- package/templates/agents/dev-team-deming.md +2 -1
- package/templates/agents/dev-team-docs.md +63 -0
- package/templates/agents/dev-team-lead.md +95 -0
- package/templates/agents/dev-team-release.md +65 -0
- package/templates/hooks/dev-team-post-change-review.js +63 -10
- package/templates/hooks/dev-team-pre-commit-gate.js +43 -14
- package/templates/hooks/dev-team-safety-guard.js +21 -11
- package/templates/hooks/dev-team-task-loop.js +17 -9
- package/templates/hooks/dev-team-tdd-enforce.js +42 -23
- package/templates/hooks/dev-team-watch-list.js +84 -0
- package/templates/settings.json +4 -0
- package/templates/skills/dev-team-audit/SKILL.md +85 -0
- package/templates/skills/dev-team-review/SKILL.md +68 -0
- package/lib/files.js +0 -160
- package/lib/init.js +0 -206
- package/lib/prompts.js +0 -123
|
@@ -12,13 +12,21 @@
|
|
|
12
12
|
* Exit 2 = block, exit 0 = allow.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
"use strict";
|
|
16
16
|
|
|
17
|
-
const { execFileSync } = require(
|
|
18
|
-
const path = require(
|
|
17
|
+
const { execFileSync } = require("child_process");
|
|
18
|
+
const path = require("path");
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let input = {};
|
|
21
|
+
try {
|
|
22
|
+
input = JSON.parse(process.argv[2] || "{}");
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.warn(
|
|
25
|
+
`[dev-team tdd-enforce] Warning: Failed to parse hook input, allowing operation. ${err.message}`,
|
|
26
|
+
);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
const filePath = (input.tool_input && (input.tool_input.file_path || input.tool_input.path)) || "";
|
|
22
30
|
|
|
23
31
|
if (!filePath) {
|
|
24
32
|
process.exit(0);
|
|
@@ -28,20 +36,27 @@ const basename = path.basename(filePath);
|
|
|
28
36
|
const ext = path.extname(filePath);
|
|
29
37
|
|
|
30
38
|
// Skip non-code files
|
|
31
|
-
const SKIP_EXTENSIONS = [
|
|
39
|
+
const SKIP_EXTENSIONS = [
|
|
40
|
+
".md",
|
|
41
|
+
".json",
|
|
42
|
+
".yml",
|
|
43
|
+
".yaml",
|
|
44
|
+
".toml",
|
|
45
|
+
".txt",
|
|
46
|
+
".css",
|
|
47
|
+
".svg",
|
|
48
|
+
".png",
|
|
49
|
+
".jpg",
|
|
50
|
+
".gif",
|
|
51
|
+
".ico",
|
|
52
|
+
".lock",
|
|
53
|
+
];
|
|
32
54
|
if (SKIP_EXTENSIONS.includes(ext)) {
|
|
33
55
|
process.exit(0);
|
|
34
56
|
}
|
|
35
57
|
|
|
36
58
|
// Skip if the file IS a test file
|
|
37
|
-
const TEST_PATTERNS = [
|
|
38
|
-
/\.test\./,
|
|
39
|
-
/\.spec\./,
|
|
40
|
-
/_test\./,
|
|
41
|
-
/\/__tests__\//,
|
|
42
|
-
/\/test\//,
|
|
43
|
-
/\/tests\//,
|
|
44
|
-
];
|
|
59
|
+
const TEST_PATTERNS = [/\.test\./, /\.spec\./, /_test\./, /\/__tests__\//, /\/test\//, /\/tests\//];
|
|
45
60
|
|
|
46
61
|
if (TEST_PATTERNS.some((p) => p.test(filePath))) {
|
|
47
62
|
process.exit(0);
|
|
@@ -64,10 +79,10 @@ if (SKIP_PATTERNS.some((p) => p.test(filePath))) {
|
|
|
64
79
|
}
|
|
65
80
|
|
|
66
81
|
// Check if any test file has been modified in this session
|
|
67
|
-
let changedFiles =
|
|
82
|
+
let changedFiles = "";
|
|
68
83
|
try {
|
|
69
|
-
changedFiles = execFileSync(
|
|
70
|
-
encoding:
|
|
84
|
+
changedFiles = execFileSync("git", ["diff", "--name-only"], {
|
|
85
|
+
encoding: "utf-8",
|
|
71
86
|
timeout: 5000,
|
|
72
87
|
});
|
|
73
88
|
} catch {
|
|
@@ -76,7 +91,7 @@ try {
|
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
const hasTestChanges = changedFiles
|
|
79
|
-
.split(
|
|
94
|
+
.split("\n")
|
|
80
95
|
.filter(Boolean)
|
|
81
96
|
.some((f) => TEST_PATTERNS.some((p) => p.test(f)));
|
|
82
97
|
|
|
@@ -88,19 +103,23 @@ if (hasTestChanges) {
|
|
|
88
103
|
// No test changes — check if a corresponding test file already exists.
|
|
89
104
|
// This allows refactoring (modifying existing tested code) without
|
|
90
105
|
// requiring the test file to also be modified.
|
|
91
|
-
const fs = require(
|
|
106
|
+
const fs = require("fs");
|
|
92
107
|
const dir = path.dirname(filePath);
|
|
93
108
|
const name = path.basename(filePath, ext);
|
|
94
109
|
|
|
95
110
|
const CANDIDATE_PATTERNS = [
|
|
96
111
|
path.join(dir, `${name}.test${ext}`),
|
|
97
112
|
path.join(dir, `${name}.spec${ext}`),
|
|
98
|
-
path.join(dir,
|
|
99
|
-
path.join(dir,
|
|
113
|
+
path.join(dir, "__tests__", `${name}${ext}`),
|
|
114
|
+
path.join(dir, "__tests__", `${name}.test${ext}`),
|
|
100
115
|
];
|
|
101
116
|
|
|
102
117
|
const hasExistingTests = CANDIDATE_PATTERNS.some((candidate) => {
|
|
103
|
-
try {
|
|
118
|
+
try {
|
|
119
|
+
return fs.statSync(candidate).isFile();
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
104
123
|
});
|
|
105
124
|
|
|
106
125
|
if (hasExistingTests) {
|
|
@@ -110,6 +129,6 @@ if (hasExistingTests) {
|
|
|
110
129
|
|
|
111
130
|
// No test changes AND no existing test file — block
|
|
112
131
|
console.error(
|
|
113
|
-
`[dev-team tdd-enforce] TDD violation: "${basename}" modified but no corresponding test file exists. Write tests first
|
|
132
|
+
`[dev-team tdd-enforce] TDD violation: "${basename}" modified but no corresponding test file exists. Write tests first.`,
|
|
114
133
|
);
|
|
115
134
|
process.exit(2);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* dev-team-watch-list.js
|
|
5
|
+
* PostToolUse hook on Edit/Write.
|
|
6
|
+
*
|
|
7
|
+
* Reads configurable file-pattern-to-agent mappings from .claude/dev-team.json
|
|
8
|
+
* and outputs structured spawn recommendations when patterns match.
|
|
9
|
+
* Advisory only — always exits 0.
|
|
10
|
+
*
|
|
11
|
+
* Config format in dev-team.json:
|
|
12
|
+
* {
|
|
13
|
+
* "watchLists": [
|
|
14
|
+
* { "pattern": "src/db/", "agents": ["dev-team-codd"], "reason": "database code changed" },
|
|
15
|
+
* { "pattern": "\\.graphql$", "agents": ["dev-team-mori", "dev-team-voss"], "reason": "API schema changed" }
|
|
16
|
+
* ]
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
const fs = require("fs");
|
|
23
|
+
const path = require("path");
|
|
24
|
+
|
|
25
|
+
let input = {};
|
|
26
|
+
try {
|
|
27
|
+
input = JSON.parse(process.argv[2] || "{}");
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[dev-team watch-list] Warning: Failed to parse hook input, allowing operation. ${err.message}`,
|
|
31
|
+
);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const filePath = (input.tool_input && (input.tool_input.file_path || input.tool_input.path)) || "";
|
|
36
|
+
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Read watch list config
|
|
42
|
+
let watchLists = [];
|
|
43
|
+
try {
|
|
44
|
+
const prefsPath = path.join(process.cwd(), ".claude", "dev-team.json");
|
|
45
|
+
const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
|
|
46
|
+
watchLists = prefs.watchLists || [];
|
|
47
|
+
} catch {
|
|
48
|
+
// No config or invalid — skip silently
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (watchLists.length === 0) {
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const matches = [];
|
|
57
|
+
|
|
58
|
+
for (const entry of watchLists) {
|
|
59
|
+
if (!entry.pattern || !entry.agents) continue;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const regex = new RegExp(entry.pattern);
|
|
63
|
+
if (regex.test(filePath)) {
|
|
64
|
+
for (const agent of entry.agents) {
|
|
65
|
+
if (!matches.some((m) => m.agent === agent)) {
|
|
66
|
+
matches.push({
|
|
67
|
+
agent,
|
|
68
|
+
reason: entry.reason || `file matched pattern: ${entry.pattern}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Invalid regex — skip this entry
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (matches.length > 0) {
|
|
80
|
+
const agentList = matches.map((m) => `@${m.agent} (${m.reason})`).join(", ");
|
|
81
|
+
console.log(`[dev-team watch-list] Spawn recommended: ${agentList}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
process.exit(0);
|
package/templates/settings.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-team:audit
|
|
3
|
+
description: Full codebase audit combining security, quality, and tooling assessments. Use to run a comprehensive scan with Szabo (security), Knuth (quality), and Deming (tooling) in parallel. Can be scoped to specific directories or file patterns.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run a comprehensive audit of: $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
1. Determine the audit scope:
|
|
11
|
+
- If a directory or file pattern is given, scope the audit to those paths
|
|
12
|
+
- If no argument, audit the entire codebase
|
|
13
|
+
- Respect `.gitignore` — skip `node_modules/`, `dist/`, build artifacts
|
|
14
|
+
|
|
15
|
+
2. The audit always spawns three specialist agents:
|
|
16
|
+
- **@dev-team-szabo** — Security audit
|
|
17
|
+
- **@dev-team-knuth** — Quality and correctness audit
|
|
18
|
+
- **@dev-team-deming** — Tooling and automation audit
|
|
19
|
+
|
|
20
|
+
## Execution
|
|
21
|
+
|
|
22
|
+
1. Spawn all three agents as **parallel background subagents** using the Agent tool with `subagent_type: "general-purpose"`.
|
|
23
|
+
|
|
24
|
+
2. Each agent's prompt must include:
|
|
25
|
+
- The agent's full definition (read from `.claude/agents/<agent>.md`)
|
|
26
|
+
- The scope (directory/pattern or "full codebase")
|
|
27
|
+
- Instruction to produce classified findings: `[DEFECT]`, `[RISK]`, `[QUESTION]`, `[SUGGESTION]`
|
|
28
|
+
- Instruction to read the actual code and tests for full context
|
|
29
|
+
|
|
30
|
+
3. Agent-specific instructions:
|
|
31
|
+
|
|
32
|
+
**Szabo (Security)**:
|
|
33
|
+
- Map all trust boundaries and entry points
|
|
34
|
+
- Check for OWASP Top 10 vulnerabilities
|
|
35
|
+
- Audit auth/authz flows end-to-end
|
|
36
|
+
- Review secret management and dependency vulnerabilities
|
|
37
|
+
|
|
38
|
+
**Knuth (Quality)**:
|
|
39
|
+
- Identify untested code paths and coverage gaps
|
|
40
|
+
- Find boundary conditions without tests
|
|
41
|
+
- Check assertion quality in existing tests
|
|
42
|
+
- Map test-to-requirement traceability
|
|
43
|
+
|
|
44
|
+
**Deming (Tooling)**:
|
|
45
|
+
- Inventory current tooling (linters, formatters, SAST, CI)
|
|
46
|
+
- Identify missing automation opportunities
|
|
47
|
+
- Check for stale dependencies and known vulnerabilities
|
|
48
|
+
- Evaluate CI pipeline efficiency
|
|
49
|
+
|
|
50
|
+
4. Wait for all agents to complete.
|
|
51
|
+
|
|
52
|
+
## Report
|
|
53
|
+
|
|
54
|
+
Produce a consolidated audit report:
|
|
55
|
+
|
|
56
|
+
### Executive summary
|
|
57
|
+
|
|
58
|
+
One paragraph summarizing the overall health of the codebase across all three domains.
|
|
59
|
+
|
|
60
|
+
### Security findings (@dev-team-szabo)
|
|
61
|
+
|
|
62
|
+
List all findings, grouped by classification:
|
|
63
|
+
- `[DEFECT]` — must fix
|
|
64
|
+
- `[RISK]` — should address
|
|
65
|
+
- `[QUESTION]` / `[SUGGESTION]` — consider
|
|
66
|
+
|
|
67
|
+
### Quality findings (@dev-team-knuth)
|
|
68
|
+
|
|
69
|
+
Same grouping. Include specific files and line references.
|
|
70
|
+
|
|
71
|
+
### Tooling findings (@dev-team-deming)
|
|
72
|
+
|
|
73
|
+
Same grouping. Include actionable recommendations.
|
|
74
|
+
|
|
75
|
+
### Priority matrix
|
|
76
|
+
|
|
77
|
+
| Priority | Finding | Agent | Action |
|
|
78
|
+
|----------|---------|-------|--------|
|
|
79
|
+
| P0 (fix now) | `[DEFECT]` items | ... | ... |
|
|
80
|
+
| P1 (fix soon) | `[RISK]` items | ... | ... |
|
|
81
|
+
| P2 (improve) | `[SUGGESTION]` items | ... | ... |
|
|
82
|
+
|
|
83
|
+
### Recommended next steps
|
|
84
|
+
|
|
85
|
+
Numbered list of concrete actions, ordered by priority. Each action should reference the specific finding it addresses.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-team:review
|
|
3
|
+
description: Orchestrated multi-agent parallel review. Use to review a PR, branch, or set of changes with multiple specialist agents simultaneously. Spawns agents based on changed file patterns and produces a unified review summary.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run a multi-agent parallel review of: $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
1. Determine what to review:
|
|
11
|
+
- If a PR number or branch is given, use `git diff` to get the changed files
|
|
12
|
+
- If a directory or file pattern is given, review those files
|
|
13
|
+
- If no argument, review all uncommitted changes (`git diff HEAD`)
|
|
14
|
+
|
|
15
|
+
2. Categorize changed files by domain to determine which agents to spawn:
|
|
16
|
+
|
|
17
|
+
| File pattern | Agent | Reason |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `auth`, `login`, `password`, `token`, `session`, `crypto`, `secret`, `permission`, `oauth`, `jwt`, `cors`, `csrf` | @dev-team-szabo | Security surface |
|
|
20
|
+
| `/api/`, `/routes/`, `schema`, `.graphql`, `.proto`, `openapi` | @dev-team-mori | API/UI contract |
|
|
21
|
+
| `docker`, `.env`, `config`, `migration`, `database`, `.sql`, `deploy` | @dev-team-voss | Infrastructure |
|
|
22
|
+
| `.github/workflows`, `.claude/`, `tsconfig`, `eslint`, `prettier`, `package.json` | @dev-team-deming | Tooling |
|
|
23
|
+
| `readme`, `changelog`, `.md`, `/docs/` | @dev-team-docs | Documentation |
|
|
24
|
+
| `/adr/`, `architecture`, `/core/`, `/domain/` | @dev-team-architect | Architecture |
|
|
25
|
+
| `package.json`, `version`, `changelog`, release workflows | @dev-team-release | Release artifacts |
|
|
26
|
+
| Any `.js`, `.ts`, `.py`, `.go`, `.java`, `.rs` (non-test) | @dev-team-knuth | Quality/coverage |
|
|
27
|
+
|
|
28
|
+
3. Always include @dev-team-szabo and @dev-team-knuth — they review all code changes.
|
|
29
|
+
|
|
30
|
+
## Execution
|
|
31
|
+
|
|
32
|
+
1. Spawn each selected agent as a **parallel background subagent** using the Agent tool with `subagent_type: "general-purpose"`.
|
|
33
|
+
|
|
34
|
+
2. Each agent's prompt must include:
|
|
35
|
+
- The agent's full definition (read from `.claude/agents/<agent>.md`)
|
|
36
|
+
- The list of changed files relevant to their domain
|
|
37
|
+
- Instruction to produce classified findings: `[DEFECT]`, `[RISK]`, `[QUESTION]`, `[SUGGESTION]`
|
|
38
|
+
- Instruction to read the actual code — not just the diff — for full context
|
|
39
|
+
|
|
40
|
+
3. Wait for all agents to complete.
|
|
41
|
+
|
|
42
|
+
## Report
|
|
43
|
+
|
|
44
|
+
Produce a unified review summary:
|
|
45
|
+
|
|
46
|
+
### Blocking findings ([DEFECT])
|
|
47
|
+
|
|
48
|
+
List all `[DEFECT]` findings from all agents. These must be resolved before merge.
|
|
49
|
+
|
|
50
|
+
Format each as:
|
|
51
|
+
```
|
|
52
|
+
**[DEFECT]** @agent-name — file:line
|
|
53
|
+
Description of the defect and why it blocks.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Advisory findings
|
|
57
|
+
|
|
58
|
+
Group by severity:
|
|
59
|
+
- **[RISK]** — likely failure modes
|
|
60
|
+
- **[QUESTION]** — decisions needing justification
|
|
61
|
+
- **[SUGGESTION]** — specific improvements
|
|
62
|
+
|
|
63
|
+
### Verdict
|
|
64
|
+
|
|
65
|
+
- **Approve** — No `[DEFECT]` findings. Advisory items noted.
|
|
66
|
+
- **Request changes** — `[DEFECT]` findings must be resolved.
|
|
67
|
+
|
|
68
|
+
State the verdict clearly. List what must be fixed for approval if requesting changes.
|
package/lib/files.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Returns the absolute path to the templates/ directory within the package.
|
|
8
|
-
*/
|
|
9
|
-
function templateDir() {
|
|
10
|
-
return path.join(__dirname, '..', 'templates');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Copies a file from src to dest, creating parent directories as needed.
|
|
15
|
-
* Returns true if the file was written, false if skipped.
|
|
16
|
-
*/
|
|
17
|
-
function copyFile(src, dest) {
|
|
18
|
-
const dir = path.dirname(dest);
|
|
19
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
-
fs.copyFileSync(src, dest);
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Checks if a file exists.
|
|
26
|
-
*/
|
|
27
|
-
function fileExists(absPath) {
|
|
28
|
-
try {
|
|
29
|
-
return fs.statSync(absPath).isFile();
|
|
30
|
-
} catch {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if a directory exists.
|
|
37
|
-
*/
|
|
38
|
-
function dirExists(absPath) {
|
|
39
|
-
try {
|
|
40
|
-
return fs.statSync(absPath).isDirectory();
|
|
41
|
-
} catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Reads a file and returns its content, or null if it doesn't exist.
|
|
48
|
-
*/
|
|
49
|
-
function readFile(absPath) {
|
|
50
|
-
try {
|
|
51
|
-
return fs.readFileSync(absPath, 'utf-8');
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Writes content to a file, creating parent directories as needed.
|
|
59
|
-
*/
|
|
60
|
-
function writeFile(absPath, content) {
|
|
61
|
-
const dir = path.dirname(absPath);
|
|
62
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
63
|
-
fs.writeFileSync(absPath, content);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Deep merges hook configurations from source into target settings.
|
|
68
|
-
* Additive only — never removes existing hooks.
|
|
69
|
-
*/
|
|
70
|
-
function mergeSettings(existingPath, newFragment) {
|
|
71
|
-
let existing = {};
|
|
72
|
-
try {
|
|
73
|
-
existing = JSON.parse(fs.readFileSync(existingPath, 'utf-8'));
|
|
74
|
-
} catch {
|
|
75
|
-
// File doesn't exist or is invalid — start fresh
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!existing.hooks) {
|
|
79
|
-
existing.hooks = {};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (const [event, entries] of Object.entries(newFragment.hooks || {})) {
|
|
83
|
-
if (!existing.hooks[event]) {
|
|
84
|
-
existing.hooks[event] = entries;
|
|
85
|
-
} else {
|
|
86
|
-
// Add entries that don't already exist (by command string)
|
|
87
|
-
for (const newEntry of entries) {
|
|
88
|
-
const newCommands = (newEntry.hooks || []).map((h) => h.command);
|
|
89
|
-
const alreadyExists = existing.hooks[event].some((existingEntry) => {
|
|
90
|
-
const existingCommands = (existingEntry.hooks || []).map((h) => h.command);
|
|
91
|
-
return newCommands.every((cmd) => existingCommands.includes(cmd));
|
|
92
|
-
});
|
|
93
|
-
if (!alreadyExists) {
|
|
94
|
-
existing.hooks[event].push(newEntry);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fs.writeFileSync(existingPath, JSON.stringify(existing, null, 2) + '\n');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Appends content to a file with dev-team markers.
|
|
105
|
-
* If markers already exist, replaces content between them.
|
|
106
|
-
* If file doesn't exist, creates it with just the content.
|
|
107
|
-
*/
|
|
108
|
-
function mergeClaudeMd(filePath, newContent) {
|
|
109
|
-
const BEGIN_MARKER = '<!-- dev-team:begin -->';
|
|
110
|
-
const END_MARKER = '<!-- dev-team:end -->';
|
|
111
|
-
|
|
112
|
-
const existing = readFile(filePath);
|
|
113
|
-
|
|
114
|
-
if (!existing) {
|
|
115
|
-
// File doesn't exist — create with content
|
|
116
|
-
writeFile(filePath, newContent);
|
|
117
|
-
return 'created';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (existing.includes(BEGIN_MARKER)) {
|
|
121
|
-
// Markers exist — replace between them
|
|
122
|
-
const beforeMarker = existing.substring(0, existing.indexOf(BEGIN_MARKER));
|
|
123
|
-
const afterMarker = existing.substring(existing.indexOf(END_MARKER) + END_MARKER.length);
|
|
124
|
-
writeFile(filePath, beforeMarker + newContent + afterMarker);
|
|
125
|
-
return 'replaced';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// No markers — append
|
|
129
|
-
writeFile(filePath, existing.trimEnd() + '\n\n' + newContent + '\n');
|
|
130
|
-
return 'appended';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Lists all files in a directory recursively.
|
|
135
|
-
*/
|
|
136
|
-
function listFilesRecursive(dir) {
|
|
137
|
-
const results = [];
|
|
138
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
139
|
-
for (const entry of entries) {
|
|
140
|
-
const fullPath = path.join(dir, entry.name);
|
|
141
|
-
if (entry.isDirectory()) {
|
|
142
|
-
results.push(...listFilesRecursive(fullPath));
|
|
143
|
-
} else {
|
|
144
|
-
results.push(fullPath);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return results;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
module.exports = {
|
|
151
|
-
templateDir,
|
|
152
|
-
copyFile,
|
|
153
|
-
fileExists,
|
|
154
|
-
dirExists,
|
|
155
|
-
readFile,
|
|
156
|
-
writeFile,
|
|
157
|
-
mergeSettings,
|
|
158
|
-
mergeClaudeMd,
|
|
159
|
-
listFilesRecursive,
|
|
160
|
-
};
|