@electriccitizen/bolt 0.1.0 → 0.2.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 +93 -14
- package/dist/ai/agent.d.ts +66 -0
- package/dist/ai/agent.js +232 -0
- package/dist/ai/agent.js.map +1 -0
- package/dist/ai/knowledge/composer.md +90 -0
- package/dist/ai/knowledge/config-safety.md +46 -0
- package/dist/ai/knowledge/ddev-operations.md +41 -0
- package/dist/ai/knowledge/drupal-internals.md +52 -0
- package/dist/ai/knowledge/drupal-updates.md +90 -0
- package/dist/ai/knowledge/knowledge/composer.md +90 -0
- package/dist/ai/knowledge/knowledge/config-safety.md +46 -0
- package/dist/ai/knowledge/knowledge/ddev-operations.md +41 -0
- package/dist/ai/knowledge/knowledge/drupal-debugging.md +89 -0
- package/dist/ai/knowledge/knowledge/drupal-internals.md +52 -0
- package/dist/ai/knowledge/knowledge/drupal-updates.md +90 -0
- package/dist/ai/prompts/analyze-ticket.d.ts +30 -0
- package/dist/ai/prompts/analyze-ticket.js +116 -0
- package/dist/ai/prompts/analyze-ticket.js.map +1 -0
- package/dist/ai/prompts/fix-ticket.d.ts +27 -0
- package/dist/ai/prompts/fix-ticket.js +129 -0
- package/dist/ai/prompts/fix-ticket.js.map +1 -0
- package/dist/ai/prompts/pr-description.d.ts +19 -0
- package/dist/ai/prompts/pr-description.js +56 -0
- package/dist/ai/prompts/pr-description.js.map +1 -0
- package/dist/ai/prompts/update-package.d.ts +25 -0
- package/dist/ai/prompts/update-package.js +87 -0
- package/dist/ai/prompts/update-package.js.map +1 -0
- package/dist/ai/prompts/update-plan.d.ts +20 -0
- package/dist/ai/prompts/update-plan.js +66 -0
- package/dist/ai/prompts/update-plan.js.map +1 -0
- package/dist/ai/schemas/analysis-result.d.ts +44 -0
- package/dist/ai/schemas/analysis-result.js +101 -0
- package/dist/ai/schemas/analysis-result.js.map +1 -0
- package/dist/ai/schemas/fix-result.d.ts +34 -0
- package/dist/ai/schemas/fix-result.js +55 -0
- package/dist/ai/schemas/fix-result.js.map +1 -0
- package/dist/ai/schemas/pr-body.d.ts +12 -0
- package/dist/ai/schemas/pr-body.js +18 -0
- package/dist/ai/schemas/pr-body.js.map +1 -0
- package/dist/ai/schemas/update-plan.d.ts +20 -0
- package/dist/ai/schemas/update-plan.js +33 -0
- package/dist/ai/schemas/update-plan.js.map +1 -0
- package/dist/ai/schemas/update-result.d.ts +22 -0
- package/dist/ai/schemas/update-result.js +30 -0
- package/dist/ai/schemas/update-result.js.map +1 -0
- package/dist/cli.js +63 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +25 -0
- package/dist/commands/analyze.js +377 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/doctor.js +61 -13
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/fix.d.ts +35 -0
- package/dist/commands/fix.js +480 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/init.d.ts +3 -2
- package/dist/commands/init.js +117 -160
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.js +121 -3
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/refresh.js +10 -57
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +463 -64
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -1
- package/dist/runner.js +12 -0
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +63 -0
- package/dist/safety.js +192 -0
- package/dist/safety.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -3
- package/modules/bolt_inspect/bolt_inspect.info.yml +0 -6
- package/modules/bolt_inspect/bolt_inspect.services.yml +0 -22
- package/modules/bolt_inspect/composer.json +0 -16
- package/modules/bolt_inspect/drush.services.yml +0 -10
- package/modules/bolt_inspect/src/Drush/Commands/BoltInspectCommands.php +0 -203
- package/modules/bolt_inspect/src/Service/ContentGenerator.php +0 -586
- package/modules/bolt_inspect/src/Service/SiteProfiler.php +0 -362
- package/modules/bolt_inspect/src/Service/TestEntityTracker.php +0 -98
package/README.md
CHANGED
|
@@ -157,16 +157,19 @@ bolt update [options]
|
|
|
157
157
|
--dry-run Show what would be updated without doing it
|
|
158
158
|
--skip-refresh Skip the pre-update refresh step
|
|
159
159
|
--skip-test Skip the baseline test
|
|
160
|
+
--no-ai Disable AI reasoning (use scripted fallback)
|
|
160
161
|
--yes Skip confirmation prompts
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
What it does:
|
|
164
|
-
1. Pre-flight: refreshes local state, runs baseline test, gets outdated packages
|
|
165
|
-
2. Per-module loop: creates branch, updates via Composer, runs `drush updb`, runs full test suite
|
|
165
|
+
1. Pre-flight: refreshes local state, runs baseline test, captures VR baselines, gets outdated packages
|
|
166
|
+
2. Per-module loop: creates branch, updates via Composer, runs `drush updb`, runs full test suite + VR comparison
|
|
166
167
|
3. If tests pass: exports config, commits on update branch
|
|
167
168
|
4. If tests fail: rolls back changes, deletes branch, continues to next module
|
|
168
169
|
5. Produces summary report with per-module results
|
|
169
170
|
|
|
171
|
+
**Visual regression:** Before any updates begin, bolt captures screenshots of all representative pages. After each module update, it compares against those baselines. Module updates should produce zero visual changes — any difference triggers a test failure and rollback. VR runs automatically during updates even if skipped in `.bolt.yml`.
|
|
172
|
+
|
|
170
173
|
Each update gets its own branch (`update/<package>-<version>`) off the base branch for clean per-module PRs.
|
|
171
174
|
|
|
172
175
|
**Exit codes:** 0 = at least one update succeeded, 1 = all failed, 2 = error.
|
|
@@ -191,13 +194,17 @@ bolt pr [options]
|
|
|
191
194
|
--reviewers <list> Comma-separated GitHub usernames
|
|
192
195
|
--draft Create as draft PR
|
|
193
196
|
--all Create PRs for all update/* branches
|
|
197
|
+
--no-ai Disable AI-generated PR descriptions
|
|
198
|
+
--yes Skip confirmation prompts
|
|
194
199
|
```
|
|
195
200
|
|
|
196
201
|
What it does:
|
|
197
|
-
1.
|
|
198
|
-
2.
|
|
199
|
-
3.
|
|
200
|
-
4.
|
|
202
|
+
1. Warns if uncommitted changes exist (PRs only include committed state)
|
|
203
|
+
2. Finds update branches (current branch or `--all` for all `update/*` branches)
|
|
204
|
+
3. Confirms before pushing (lists branches that will be pushed)
|
|
205
|
+
4. Parses commit messages for update details (package, versions, test results)
|
|
206
|
+
5. Pushes branch to remote
|
|
207
|
+
6. Creates PR with structured body via `gh pr create`
|
|
201
208
|
5. Checks for existing PRs to avoid duplicates
|
|
202
209
|
|
|
203
210
|
**Examples:**
|
|
@@ -210,6 +217,76 @@ bolt update --all # creates multiple update branches
|
|
|
210
217
|
bolt pr --all --draft # creates draft PRs for all of them
|
|
211
218
|
```
|
|
212
219
|
|
|
220
|
+
### `bolt analyze`
|
|
221
|
+
|
|
222
|
+
AI-powered ticket analysis — produces an investigation plan from a Jira ticket or description.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
bolt analyze [options]
|
|
226
|
+
--ticket <key> Jira ticket key (e.g. PROJ-123) — pulls via Jira MCP
|
|
227
|
+
--description <text> Bug description (manual, no Jira needed)
|
|
228
|
+
--url <url> Site URL (auto-detected from DDEV if omitted)
|
|
229
|
+
--output <format> text | json | markdown (default: text)
|
|
230
|
+
--report <path> Write analysis to file
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Requires AI** — there is no `--no-ai` fallback for this command, since analysis IS the AI.
|
|
234
|
+
|
|
235
|
+
Input methods:
|
|
236
|
+
- `--ticket PROJ-123` — pulls from Jira via Atlassian MCP (configured per user)
|
|
237
|
+
- `--description "..."` — manual text input
|
|
238
|
+
- `cat ticket.txt | bolt analyze` — stdin
|
|
239
|
+
|
|
240
|
+
Output includes: problem summary, likely root causes (ranked), investigation steps with specific commands, risk assessment, suggested bolt tests to run after fix, and a confidence assessment for whether the issue can be fixed autonomously.
|
|
241
|
+
|
|
242
|
+
**Examples:**
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
bolt analyze --ticket EC-42 # Jira integration
|
|
246
|
+
bolt analyze --description "Media upload broken" # manual input
|
|
247
|
+
bolt analyze --ticket EC-42 --output markdown --report analysis.md
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `bolt fix`
|
|
251
|
+
|
|
252
|
+
Autonomous ticket resolution — analyze, fix, test, and create a PR.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
bolt fix [options]
|
|
256
|
+
--ticket <key> Jira ticket key (runs analysis internally)
|
|
257
|
+
--description <text> Bug description (manual, no Jira needed)
|
|
258
|
+
--analysis <path> Path to saved bolt analyze report (JSON)
|
|
259
|
+
--context <text> Additional context (answers to questions from analyze)
|
|
260
|
+
--interactive Launch interactive Claude session (converse instead of autonomous)
|
|
261
|
+
--dry-run Show planned changes without modifying files
|
|
262
|
+
--skip-test Skip post-fix test run
|
|
263
|
+
--skip-pr Skip PR creation
|
|
264
|
+
--branch <name> Branch name (default: fix/<ticket-key>)
|
|
265
|
+
--yes Skip confirmation prompts
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Requires AI** — no `--no-ai` fallback.
|
|
269
|
+
|
|
270
|
+
Two modes:
|
|
271
|
+
- **Autonomous** (default): Claude runs unattended with full tool access (ddev, drush, file editing). The confidence model from `bolt analyze` gates entry — if it says "needs info" or "needs human", bolt exits with instructions.
|
|
272
|
+
- **Interactive** (`--interactive`): Launches a live Claude session with the ticket analysis, site profile, and Drupal knowledge pre-loaded. You converse with Claude to guide the fix.
|
|
273
|
+
|
|
274
|
+
**Examples:**
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# Full autonomous flow
|
|
278
|
+
bolt fix --ticket EC-42 # analyze → fix → test → PR
|
|
279
|
+
|
|
280
|
+
# Provide answers to questions from analyze
|
|
281
|
+
bolt fix --ticket EC-42 --context "Chrome only, content_editor role"
|
|
282
|
+
|
|
283
|
+
# Interactive — converse with Claude
|
|
284
|
+
bolt fix --ticket EC-42 --interactive
|
|
285
|
+
|
|
286
|
+
# Dry run — see what would change
|
|
287
|
+
bolt fix --description "footer logo broken" --dry-run
|
|
288
|
+
```
|
|
289
|
+
|
|
213
290
|
### `bolt init`
|
|
214
291
|
|
|
215
292
|
Connect bolt to a client site. Run from the site's project root.
|
|
@@ -218,16 +295,18 @@ Connect bolt to a client site. Run from the site's project root.
|
|
|
218
295
|
bolt init [options]
|
|
219
296
|
--skip-composer Skip Composer setup
|
|
220
297
|
--skip-module Skip module enable
|
|
221
|
-
--upgrade Update
|
|
298
|
+
--upgrade Update bolt-inspect module via Composer
|
|
299
|
+
--yes Skip confirmation prompts
|
|
222
300
|
```
|
|
223
301
|
|
|
224
302
|
What it does:
|
|
225
|
-
1.
|
|
226
|
-
2.
|
|
227
|
-
3.
|
|
228
|
-
4.
|
|
229
|
-
5.
|
|
230
|
-
|
|
303
|
+
1. Prompts if on `main`/`master` branch (bolt changes should go on a dedicated branch)
|
|
304
|
+
2. Installs `electriccitizen/bolt-inspect` via Composer (from Packagist)
|
|
305
|
+
3. Enables `bolt_inspect` Drupal module
|
|
306
|
+
4. Creates `.bolt.yml` config template
|
|
307
|
+
5. Adds bolt artifacts to `.gitignore`
|
|
308
|
+
|
|
309
|
+
Other developers on the same project just run `composer install` — the module is in `composer.json` like any other dependency. No additional setup needed beyond installing the bolt CLI globally.
|
|
231
310
|
|
|
232
311
|
Idempotent — safe to re-run.
|
|
233
312
|
|
|
@@ -346,7 +425,7 @@ bolt init --upgrade
|
|
|
346
425
|
bolt doctor # verify versions match
|
|
347
426
|
```
|
|
348
427
|
|
|
349
|
-
`bolt init --upgrade`
|
|
428
|
+
`bolt init --upgrade` runs `composer update electriccitizen/bolt-inspect` and clears caches — no config changes, no re-init.
|
|
350
429
|
|
|
351
430
|
## Roadmap
|
|
352
431
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI agent infrastructure — spawns Claude Code as a subprocess
|
|
3
|
+
* for reasoning tasks (conflict resolution, error diagnosis, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Pattern: deterministic TypeScript gathers context → Claude reasons → structured JSON back.
|
|
6
|
+
*/
|
|
7
|
+
export interface AgentTask {
|
|
8
|
+
/** Human-readable goal for the agent. */
|
|
9
|
+
goal: string;
|
|
10
|
+
/** Structured context data passed to the agent. */
|
|
11
|
+
context: Record<string, unknown>;
|
|
12
|
+
/** Knowledge domains to include in the prompt. */
|
|
13
|
+
knowledge: KnowledgeDomain[];
|
|
14
|
+
/** JSON schema description for expected output format. */
|
|
15
|
+
outputFormat: string;
|
|
16
|
+
/** Maximum turns the agent can take (default: 25). */
|
|
17
|
+
maxTurns?: number;
|
|
18
|
+
/** Timeout in milliseconds (default: 300_000 = 5 min). */
|
|
19
|
+
timeout?: number;
|
|
20
|
+
/** Allowed tools for the agent (default: Bash only). */
|
|
21
|
+
allowedTools?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface AgentResult {
|
|
24
|
+
/** Whether the agent completed successfully. */
|
|
25
|
+
success: boolean;
|
|
26
|
+
/** Parsed output matching the requested format. */
|
|
27
|
+
output: Record<string, unknown>;
|
|
28
|
+
/** Raw text response from Claude. */
|
|
29
|
+
rawResponse: string;
|
|
30
|
+
/** Number of turns the agent used. */
|
|
31
|
+
numTurns: number;
|
|
32
|
+
/** Duration in milliseconds. */
|
|
33
|
+
duration: number;
|
|
34
|
+
/** Cost in USD (if reported). */
|
|
35
|
+
costUsd?: number;
|
|
36
|
+
}
|
|
37
|
+
export type KnowledgeDomain = 'composer' | 'drupal-updates' | 'config-safety' | 'ddev-operations' | 'drupal-internals' | 'drupal-debugging';
|
|
38
|
+
/**
|
|
39
|
+
* Check if Claude Code CLI is installed and accessible.
|
|
40
|
+
* Result is cached for the process lifetime.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isClaudeAvailable(): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Get Claude Code version string, or null if not available.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getClaudeVersion(): Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Load a knowledge file from src/ai/knowledge/.
|
|
49
|
+
* Files are read at runtime from the dist directory.
|
|
50
|
+
*/
|
|
51
|
+
export declare function loadKnowledge(domain: KnowledgeDomain): string;
|
|
52
|
+
/**
|
|
53
|
+
* Build a complete prompt from an AgentTask.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildPrompt(task: AgentTask): string;
|
|
56
|
+
/**
|
|
57
|
+
* Parse a JSON code block from Claude's response text.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseJsonFromResponse(text: string): Record<string, unknown> | null;
|
|
60
|
+
/**
|
|
61
|
+
* Run an agent task via Claude Code subprocess.
|
|
62
|
+
*
|
|
63
|
+
* Spawns `claude -p <prompt> --output-format json` and parses
|
|
64
|
+
* the structured result.
|
|
65
|
+
*/
|
|
66
|
+
export declare function runAgent(task: AgentTask): Promise<AgentResult>;
|
package/dist/ai/agent.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI agent infrastructure — spawns Claude Code as a subprocess
|
|
3
|
+
* for reasoning tasks (conflict resolution, error diagnosis, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Pattern: deterministic TypeScript gathers context → Claude reasons → structured JSON back.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile, spawn } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { resolve, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
// --- Claude Availability ---
|
|
16
|
+
let claudeAvailable = null;
|
|
17
|
+
/**
|
|
18
|
+
* Check if Claude Code CLI is installed and accessible.
|
|
19
|
+
* Result is cached for the process lifetime.
|
|
20
|
+
*/
|
|
21
|
+
export async function isClaudeAvailable() {
|
|
22
|
+
if (claudeAvailable !== null)
|
|
23
|
+
return claudeAvailable;
|
|
24
|
+
try {
|
|
25
|
+
const { stdout } = await execFileAsync('claude', ['--version'], {
|
|
26
|
+
timeout: 5_000,
|
|
27
|
+
});
|
|
28
|
+
claudeAvailable = stdout.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
claudeAvailable = false;
|
|
32
|
+
}
|
|
33
|
+
return claudeAvailable;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get Claude Code version string, or null if not available.
|
|
37
|
+
*/
|
|
38
|
+
export async function getClaudeVersion() {
|
|
39
|
+
try {
|
|
40
|
+
const { stdout } = await execFileAsync('claude', ['--version'], {
|
|
41
|
+
timeout: 5_000,
|
|
42
|
+
});
|
|
43
|
+
return stdout.trim() || null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// --- Knowledge Loading ---
|
|
50
|
+
const knowledgeCache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Load a knowledge file from src/ai/knowledge/.
|
|
53
|
+
* Files are read at runtime from the dist directory.
|
|
54
|
+
*/
|
|
55
|
+
export function loadKnowledge(domain) {
|
|
56
|
+
if (knowledgeCache.has(domain)) {
|
|
57
|
+
return knowledgeCache.get(domain);
|
|
58
|
+
}
|
|
59
|
+
const knowledgePath = resolve(__dirname, 'knowledge', `${domain}.md`);
|
|
60
|
+
try {
|
|
61
|
+
const content = readFileSync(knowledgePath, 'utf-8');
|
|
62
|
+
knowledgeCache.set(domain, content);
|
|
63
|
+
return content;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Knowledge file not found — return empty (non-fatal).
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// --- Prompt Building ---
|
|
71
|
+
/**
|
|
72
|
+
* Build a complete prompt from an AgentTask.
|
|
73
|
+
*/
|
|
74
|
+
export function buildPrompt(task) {
|
|
75
|
+
const sections = [];
|
|
76
|
+
// Role and goal.
|
|
77
|
+
sections.push(`# Task\n\n${task.goal}`);
|
|
78
|
+
// Context data.
|
|
79
|
+
if (Object.keys(task.context).length > 0) {
|
|
80
|
+
sections.push(`# Context\n\n\`\`\`json\n${JSON.stringify(task.context, null, 2)}\n\`\`\``);
|
|
81
|
+
}
|
|
82
|
+
// Knowledge sections.
|
|
83
|
+
for (const domain of task.knowledge) {
|
|
84
|
+
const content = loadKnowledge(domain);
|
|
85
|
+
if (content) {
|
|
86
|
+
sections.push(content);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Output format instructions.
|
|
90
|
+
sections.push(`# Output Format\n\n` +
|
|
91
|
+
`When you have completed the task, output your result as a single JSON code block.\n` +
|
|
92
|
+
`The JSON must match this format:\n\n` +
|
|
93
|
+
`\`\`\`\n${task.outputFormat}\n\`\`\`\n\n` +
|
|
94
|
+
`IMPORTANT: Your final message must contain exactly one JSON code block with the result. ` +
|
|
95
|
+
`Do not include any other text after the JSON block.`);
|
|
96
|
+
return sections.join('\n\n---\n\n');
|
|
97
|
+
}
|
|
98
|
+
// --- Agent Execution ---
|
|
99
|
+
/**
|
|
100
|
+
* Parse a JSON code block from Claude's response text.
|
|
101
|
+
*/
|
|
102
|
+
export function parseJsonFromResponse(text) {
|
|
103
|
+
// Find all code blocks and try the last one first (most likely the final result).
|
|
104
|
+
const allBlocks = [...text.matchAll(/```(?:json)?\s*\n([\s\S]*?)\n```/g)];
|
|
105
|
+
if (allBlocks.length > 0) {
|
|
106
|
+
// Try last block first (agent's final answer).
|
|
107
|
+
for (let i = allBlocks.length - 1; i >= 0; i--) {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(allBlocks[i][1]);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Try the next block.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Try parsing the entire response as JSON (unlikely but possible).
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(text);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run an agent task via Claude Code subprocess.
|
|
126
|
+
*
|
|
127
|
+
* Spawns `claude -p <prompt> --output-format json` and parses
|
|
128
|
+
* the structured result.
|
|
129
|
+
*/
|
|
130
|
+
export async function runAgent(task) {
|
|
131
|
+
const startTime = Date.now();
|
|
132
|
+
const prompt = buildPrompt(task);
|
|
133
|
+
const timeout = task.timeout ?? 300_000;
|
|
134
|
+
const maxTurns = task.maxTurns ?? 25;
|
|
135
|
+
// Build claude CLI args — use --print with stdin to avoid arg length limits.
|
|
136
|
+
const args = [
|
|
137
|
+
'--print',
|
|
138
|
+
'--output-format', 'json',
|
|
139
|
+
'--max-turns', String(maxTurns),
|
|
140
|
+
];
|
|
141
|
+
// Restrict tools if specified.
|
|
142
|
+
if (task.allowedTools && task.allowedTools.length > 0) {
|
|
143
|
+
args.push('--allowedTools', task.allowedTools.join(','));
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const stdout = await new Promise((resolvePromise, reject) => {
|
|
147
|
+
const child = spawn('claude', args, {
|
|
148
|
+
env: {
|
|
149
|
+
...process.env,
|
|
150
|
+
CI: '1',
|
|
151
|
+
},
|
|
152
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
153
|
+
});
|
|
154
|
+
const chunks = [];
|
|
155
|
+
let stderrOutput = '';
|
|
156
|
+
child.stdout.on('data', (chunk) => chunks.push(chunk));
|
|
157
|
+
child.stderr.on('data', (chunk) => { stderrOutput += chunk.toString(); });
|
|
158
|
+
child.on('close', (code) => {
|
|
159
|
+
const output = Buffer.concat(chunks).toString('utf-8');
|
|
160
|
+
if (code === 0) {
|
|
161
|
+
resolvePromise(output);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
reject(new Error(`claude exited with code ${code}: ${stderrOutput || output}`));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
child.on('error', (err) => reject(err));
|
|
168
|
+
// Write prompt to stdin and close.
|
|
169
|
+
child.stdin.write(prompt);
|
|
170
|
+
child.stdin.end();
|
|
171
|
+
// Timeout handling.
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
child.kill('SIGTERM');
|
|
174
|
+
reject(new Error(`Agent timed out after ${timeout}ms`));
|
|
175
|
+
}, timeout);
|
|
176
|
+
child.on('close', () => clearTimeout(timer));
|
|
177
|
+
});
|
|
178
|
+
const duration = Date.now() - startTime;
|
|
179
|
+
// Parse Claude Code's JSON output format.
|
|
180
|
+
// { type: "result", result: "...", num_turns: N, cost_usd: N, ... }
|
|
181
|
+
let claudeOutput;
|
|
182
|
+
try {
|
|
183
|
+
claudeOutput = JSON.parse(stdout);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// If the output isn't valid JSON, treat the raw text as the response.
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
output: {},
|
|
190
|
+
rawResponse: stdout,
|
|
191
|
+
numTurns: 0,
|
|
192
|
+
duration,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const resultText = String(claudeOutput.result ?? '');
|
|
196
|
+
const numTurns = claudeOutput.num_turns ?? 0;
|
|
197
|
+
const costUsd = claudeOutput.cost_usd ?? undefined;
|
|
198
|
+
const isError = claudeOutput.is_error ?? false;
|
|
199
|
+
if (isError) {
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
output: { error: resultText },
|
|
203
|
+
rawResponse: resultText,
|
|
204
|
+
numTurns,
|
|
205
|
+
duration,
|
|
206
|
+
costUsd,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Parse the structured JSON from Claude's response text.
|
|
210
|
+
const parsed = parseJsonFromResponse(resultText);
|
|
211
|
+
return {
|
|
212
|
+
success: parsed !== null,
|
|
213
|
+
output: parsed ?? { rawText: resultText },
|
|
214
|
+
rawResponse: resultText,
|
|
215
|
+
numTurns,
|
|
216
|
+
duration,
|
|
217
|
+
costUsd,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
const duration = Date.now() - startTime;
|
|
222
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
output: { error: message },
|
|
226
|
+
rawResponse: message,
|
|
227
|
+
numTurns: 0,
|
|
228
|
+
duration,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/ai/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Composer Conflict Resolution
|
|
2
|
+
|
|
3
|
+
You are an expert at resolving Composer dependency conflicts for Drupal projects.
|
|
4
|
+
|
|
5
|
+
## Diagnostic Workflow
|
|
6
|
+
|
|
7
|
+
When a `composer update` fails, follow this sequence:
|
|
8
|
+
|
|
9
|
+
1. **Identify the blocker:** `composer why-not vendor/package <target-version>`
|
|
10
|
+
- Shows the dependency chain preventing the upgrade
|
|
11
|
+
- Read the output carefully — it tells you exactly which package constrains the version
|
|
12
|
+
|
|
13
|
+
2. **Trace reverse dependencies:** `composer why vendor/package`
|
|
14
|
+
- Shows which installed packages depend on the blocked package
|
|
15
|
+
- Helps identify if updating a parent would unlock the target
|
|
16
|
+
|
|
17
|
+
3. **Check available versions:** `composer show --all vendor/package`
|
|
18
|
+
- Lists all available versions
|
|
19
|
+
- Helps find the highest compatible version if the target isn't achievable
|
|
20
|
+
|
|
21
|
+
4. **Apply the right update strategy** (see below)
|
|
22
|
+
|
|
23
|
+
5. **Harden constraints after success:** `composer bump` (applications only, not libraries)
|
|
24
|
+
- Raises lower bounds to currently installed versions
|
|
25
|
+
- Prevents accidental downgrades
|
|
26
|
+
|
|
27
|
+
## Update Strategies (in order of preference)
|
|
28
|
+
|
|
29
|
+
### Strategy 1: Standard update with transitive deps
|
|
30
|
+
```bash
|
|
31
|
+
composer update vendor/package --with-dependencies
|
|
32
|
+
```
|
|
33
|
+
Tries first because it has the smallest blast radius.
|
|
34
|
+
|
|
35
|
+
### Strategy 2: Update with all dependencies
|
|
36
|
+
```bash
|
|
37
|
+
composer update vendor/package --with-all-dependencies
|
|
38
|
+
```
|
|
39
|
+
Broader resolution — allows all transitive deps to change.
|
|
40
|
+
|
|
41
|
+
### Strategy 3: Require with caret constraint
|
|
42
|
+
```bash
|
|
43
|
+
composer require vendor/package:^X.Y --update-with-all-dependencies
|
|
44
|
+
```
|
|
45
|
+
Rewrites the constraint in composer.json. Use when the existing constraint is too tight.
|
|
46
|
+
|
|
47
|
+
### Strategy 4: Require with tilde constraint
|
|
48
|
+
```bash
|
|
49
|
+
composer require vendor/package:~X.Y --update-with-all-dependencies
|
|
50
|
+
```
|
|
51
|
+
More conservative — only allows patch updates within the minor version.
|
|
52
|
+
|
|
53
|
+
### Strategy 5: Update alongside core
|
|
54
|
+
```bash
|
|
55
|
+
composer update vendor/package drupal/core-recommended drupal/core-composer-scaffold drupal/core-project-message --with-all-dependencies
|
|
56
|
+
```
|
|
57
|
+
Common Drupal pattern — many modules constrain against core version.
|
|
58
|
+
|
|
59
|
+
### Strategy 6: Read the error and adapt
|
|
60
|
+
If all standard strategies fail, READ the actual error output:
|
|
61
|
+
- If it says "package X requires Y ^2.0 but Z is locked at 1.5" → update Z first
|
|
62
|
+
- If it says "root composer.json requires X ^1.0" → you may need to change the constraint
|
|
63
|
+
- If it says "package X conflicts with Y" → check if X has a newer version that resolves it
|
|
64
|
+
- Use `composer why-not` output to understand the exact constraint chain
|
|
65
|
+
|
|
66
|
+
## Version Constraint Reference
|
|
67
|
+
|
|
68
|
+
| Constraint | Meaning |
|
|
69
|
+
|------------|---------|
|
|
70
|
+
| `^1.2.3` | `>=1.2.3 <2.0.0` (allows minor+patch) |
|
|
71
|
+
| `~1.2.3` | `>=1.2.3 <1.3.0` (allows patch only) |
|
|
72
|
+
| `>=1.0 <2.0` | Explicit range |
|
|
73
|
+
| `1.2.*` | Any 1.2.x patch |
|
|
74
|
+
| `*` | Any version |
|
|
75
|
+
|
|
76
|
+
## Key Flags
|
|
77
|
+
|
|
78
|
+
- `--dry-run` — simulate without applying (always safe to run first)
|
|
79
|
+
- `--with-dependencies` — also update deps of specified packages
|
|
80
|
+
- `--with-all-dependencies` — also update deps AND reverse deps
|
|
81
|
+
- `--no-update` — modify composer.json without running update (combine with `composer update` separately)
|
|
82
|
+
- `--prefer-stable` — prefer stable over dev versions
|
|
83
|
+
|
|
84
|
+
## Important Rules
|
|
85
|
+
|
|
86
|
+
- Always run `composer update` with package names specified — never bare `composer update` (too broad)
|
|
87
|
+
- After resolving a conflict, verify with `composer show vendor/package` that the installed version is correct
|
|
88
|
+
- If the target version requires a newer PHP, that's a hard blocker — skip the update
|
|
89
|
+
- If the target version requires a newer Drupal core, check if core can also be updated
|
|
90
|
+
- `composer audit` shows security advisories — security updates should be prioritized
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Configuration Management Safety
|
|
2
|
+
|
|
3
|
+
You are working with Drupal configuration. Follow these safety patterns.
|
|
4
|
+
|
|
5
|
+
## Safe Config Export
|
|
6
|
+
|
|
7
|
+
After a module update, export config changes:
|
|
8
|
+
```bash
|
|
9
|
+
ddev drush cex -y
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Review before committing
|
|
13
|
+
- Check `git diff config/` to see what actually changed
|
|
14
|
+
- Module updates may introduce new config files (new permissions, new field settings)
|
|
15
|
+
- Module updates may modify existing config (schema changes, new default values)
|
|
16
|
+
- Unexpected config changes may indicate a problem
|
|
17
|
+
|
|
18
|
+
### What's normal after an update
|
|
19
|
+
- New config files for new module features
|
|
20
|
+
- Schema version changes in `.info.yml`
|
|
21
|
+
- Updated default values in existing config
|
|
22
|
+
- New permissions added by the module
|
|
23
|
+
|
|
24
|
+
### What's suspicious after an update
|
|
25
|
+
- Config deletions you didn't expect
|
|
26
|
+
- Changes to content type or field config you didn't modify
|
|
27
|
+
- Changes to views or blocks unrelated to the updated module
|
|
28
|
+
- System config changes (site name, mail settings, etc.)
|
|
29
|
+
|
|
30
|
+
## Config Status Check
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Check if config is in sync
|
|
34
|
+
ddev drush config:status
|
|
35
|
+
|
|
36
|
+
# Show diff without importing
|
|
37
|
+
ddev drush cim --no --diff
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Important Rules
|
|
41
|
+
|
|
42
|
+
- NEVER run `ddev drush cim -y` during an update pipeline — only `cex`
|
|
43
|
+
- Config import (`cim`) is for syncing environments, not for updates
|
|
44
|
+
- Always export (`cex`) after `drush updb` — database updates may generate config changes
|
|
45
|
+
- If `config:status` shows unexpected changes, investigate before exporting
|
|
46
|
+
- One module update per commit keeps config changes traceable
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# DDEV Operations
|
|
2
|
+
|
|
3
|
+
All commands run through DDEV. Use `ddev` prefix for all drush and composer commands.
|
|
4
|
+
|
|
5
|
+
## Essential Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ddev drush <command> # Run drush in container
|
|
9
|
+
ddev composer <command> # Run composer in container
|
|
10
|
+
ddev drush cr # Clear all caches
|
|
11
|
+
ddev drush updb -y # Apply database updates
|
|
12
|
+
ddev drush cex -y # Export configuration
|
|
13
|
+
ddev drush status # Check Drupal status
|
|
14
|
+
ddev drush watchdog:show # View recent log entries
|
|
15
|
+
ddev describe # Show project info + URL
|
|
16
|
+
ddev logs # View container logs
|
|
17
|
+
ddev snapshot --name=<name> # Create safety snapshot
|
|
18
|
+
ddev snapshot restore --name=<name> # Restore snapshot
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Safety Patterns
|
|
22
|
+
|
|
23
|
+
- **Snapshot before risky operations:** `ddev snapshot --name=pre-update-$(date +%Y%m%d-%H%M%S)`
|
|
24
|
+
- **Check DDEV is running:** `ddev describe` — if it fails, run `ddev start`
|
|
25
|
+
- **After composer changes:** always run `ddev drush cr` to clear caches
|
|
26
|
+
- **Diagnose problems:** `ddev logs -f` to follow container logs in real-time
|
|
27
|
+
|
|
28
|
+
## Error Diagnosis
|
|
29
|
+
|
|
30
|
+
When something fails:
|
|
31
|
+
1. Check drush watchdog: `ddev drush watchdog:show --count=20 --severity=Error`
|
|
32
|
+
2. Check container logs: `ddev logs`
|
|
33
|
+
3. Verify database connection: `ddev drush status`
|
|
34
|
+
4. Try cache rebuild: `ddev drush cr`
|
|
35
|
+
|
|
36
|
+
## Important Notes
|
|
37
|
+
|
|
38
|
+
- All file paths inside commands are container paths (e.g., `/var/www/html/web/`)
|
|
39
|
+
- Host paths and container paths are different — DDEV maps them automatically
|
|
40
|
+
- `composer install` syncs to lockfile (safe); `composer update` changes lockfile (update operation)
|
|
41
|
+
- DDEV auto-starts services but may need manual restart after config changes: `ddev restart`
|