sequant 1.20.0 → 1.20.2
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/.claude-plugin/plugin.json +1 -1
- package/dist/bin/cli.js +3 -1
- package/dist/src/commands/conventions.d.ts +1 -0
- package/dist/src/commands/conventions.js +14 -0
- package/dist/src/commands/doctor.js +48 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +29 -0
- package/dist/src/commands/run.js +3 -5
- package/dist/src/commands/sync.js +26 -0
- package/dist/src/lib/agents-md.d.ts +42 -0
- package/dist/src/lib/agents-md.js +206 -0
- package/dist/src/lib/workflow/phase-executor.js +27 -7
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -82,6 +82,7 @@ program
|
|
|
82
82
|
.option("-i, --interactive", "Force interactive mode even in non-TTY environment")
|
|
83
83
|
.option("--skip-setup", "Skip the dependency setup wizard")
|
|
84
84
|
.option("--no-symlinks", "Use copies instead of symlinks for scripts/dev/ files")
|
|
85
|
+
.option("--no-agents-md", "Skip AGENTS.md generation")
|
|
85
86
|
.action(initCommand);
|
|
86
87
|
program
|
|
87
88
|
.command("update")
|
|
@@ -137,7 +138,7 @@ program
|
|
|
137
138
|
.option("--no-smart-tests", "Disable smart test detection")
|
|
138
139
|
.option("--testgen", "Run testgen phase after spec")
|
|
139
140
|
.option("--quiet", "Suppress version warnings and non-essential output")
|
|
140
|
-
.option("--chain", "Chain issues: each branches from previous (
|
|
141
|
+
.option("--chain", "Chain issues: each branches from previous (implies --sequential)")
|
|
141
142
|
.option("--qa-gate", "Wait for QA pass before starting next issue in chain (requires --chain)")
|
|
142
143
|
.option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
|
|
143
144
|
.option("--no-mcp", "Disable MCP server injection in headless mode")
|
|
@@ -165,6 +166,7 @@ program
|
|
|
165
166
|
.description("View and manage codebase conventions")
|
|
166
167
|
.option("--detect", "Re-run convention detection")
|
|
167
168
|
.option("--reset", "Clear detected conventions (keep manual)")
|
|
169
|
+
.option("--format <format>", "Output format (agents-md for AGENTS.md format)")
|
|
168
170
|
.action(conventionsCommand);
|
|
169
171
|
program
|
|
170
172
|
.command("logs")
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { detectAndSaveConventions, loadConventions, formatConventions, CONVENTIONS_PATH, } from "../lib/conventions-detector.js";
|
|
6
6
|
import { fileExists, writeFile } from "../lib/fs.js";
|
|
7
|
+
import { formatConventionsAsAgentsMd } from "../lib/agents-md.js";
|
|
7
8
|
export async function conventionsCommand(options) {
|
|
8
9
|
if (options.reset) {
|
|
9
10
|
await handleReset();
|
|
@@ -13,6 +14,10 @@ export async function conventionsCommand(options) {
|
|
|
13
14
|
await handleDetect();
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
17
|
+
if (options.format === "agents-md") {
|
|
18
|
+
await handleFormatAgentsMd();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
16
21
|
// Default: show current conventions
|
|
17
22
|
await handleShow();
|
|
18
23
|
}
|
|
@@ -45,6 +50,15 @@ async function handleReset() {
|
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
}
|
|
53
|
+
async function handleFormatAgentsMd() {
|
|
54
|
+
const conventions = await loadConventions();
|
|
55
|
+
if (!conventions) {
|
|
56
|
+
console.log(chalk.yellow("No conventions detected yet."));
|
|
57
|
+
console.log(chalk.gray("Run 'sequant conventions --detect' first to detect conventions."));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(formatConventionsAsAgentsMd(conventions));
|
|
61
|
+
}
|
|
48
62
|
async function handleShow() {
|
|
49
63
|
if (!(await fileExists(CONVENTIONS_PATH))) {
|
|
50
64
|
console.log(chalk.yellow("No conventions detected yet."));
|
|
@@ -9,6 +9,8 @@ import { getManifest } from "../lib/manifest.js";
|
|
|
9
9
|
import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, getMcpServersConfig, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
|
|
10
10
|
import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
|
|
11
11
|
import { areSkillsOutdated } from "./sync.js";
|
|
12
|
+
import { readAgentsMd, checkAgentsMdConsistency, AGENTS_MD_PATH, } from "../lib/agents-md.js";
|
|
13
|
+
import { readFile } from "../lib/fs.js";
|
|
12
14
|
/**
|
|
13
15
|
* Labels that indicate an issue should be skipped from closed-issue verification
|
|
14
16
|
* (case-insensitive matching)
|
|
@@ -174,6 +176,52 @@ export async function doctorCommand(options = {}) {
|
|
|
174
176
|
message: `Up to date (${skillsStatus.packageVersion})`,
|
|
175
177
|
});
|
|
176
178
|
}
|
|
179
|
+
// Check: AGENTS.md presence and consistency
|
|
180
|
+
const agentsMdContent = await readAgentsMd();
|
|
181
|
+
if (agentsMdContent) {
|
|
182
|
+
// Check consistency with CLAUDE.md if both exist
|
|
183
|
+
if (await fileExists("CLAUDE.md")) {
|
|
184
|
+
try {
|
|
185
|
+
const claudeMdContent = await readFile("CLAUDE.md");
|
|
186
|
+
const inconsistency = checkAgentsMdConsistency(agentsMdContent, claudeMdContent);
|
|
187
|
+
if (inconsistency) {
|
|
188
|
+
checks.push({
|
|
189
|
+
name: "AGENTS.md",
|
|
190
|
+
status: "warn",
|
|
191
|
+
message: `Out of sync with CLAUDE.md: ${inconsistency}. Run: sequant sync --force`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
checks.push({
|
|
196
|
+
name: "AGENTS.md",
|
|
197
|
+
status: "pass",
|
|
198
|
+
message: "Present and consistent with CLAUDE.md",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
checks.push({
|
|
204
|
+
name: "AGENTS.md",
|
|
205
|
+
status: "pass",
|
|
206
|
+
message: "Present (could not verify consistency)",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
checks.push({
|
|
212
|
+
name: "AGENTS.md",
|
|
213
|
+
status: "pass",
|
|
214
|
+
message: "Present",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (manifest) {
|
|
219
|
+
checks.push({
|
|
220
|
+
name: "AGENTS.md",
|
|
221
|
+
status: "warn",
|
|
222
|
+
message: `Missing ${AGENTS_MD_PATH} - run: sequant init --force (or sequant sync --force)`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
177
225
|
// Check 4: Hooks directory
|
|
178
226
|
const hooksExist = await fileExists(".claude/hooks");
|
|
179
227
|
if (hooksExist) {
|
|
@@ -11,6 +11,7 @@ import { saveConfig } from "../lib/config.js";
|
|
|
11
11
|
import { createDefaultSettings } from "../lib/settings.js";
|
|
12
12
|
import { detectAndSaveConventions } from "../lib/conventions-detector.js";
|
|
13
13
|
import { fileExists, ensureDir, readFile, writeFile } from "../lib/fs.js";
|
|
14
|
+
import { generateAgentsMd, writeAgentsMd } from "../lib/agents-md.js";
|
|
14
15
|
import { commandExists, isGhAuthenticated, getInstallHint, } from "../lib/system.js";
|
|
15
16
|
import { shouldUseInteractiveMode, getNonInteractiveReason, } from "../lib/tty.js";
|
|
16
17
|
import { checkAllDependencies, displayDependencyStatus, runSetupWizard, shouldRunSetupWizard, } from "../lib/wizard.js";
|
|
@@ -289,6 +290,9 @@ export async function initCommand(options) {
|
|
|
289
290
|
console.log(chalk.gray(" └── logs/ (workflow run logs)"));
|
|
290
291
|
console.log(chalk.gray(" scripts/dev/"));
|
|
291
292
|
console.log(chalk.gray(" └── *.sh (worktree helpers)"));
|
|
293
|
+
if (options.agentsMd !== false) {
|
|
294
|
+
console.log(chalk.gray(" AGENTS.md (universal agent instructions)"));
|
|
295
|
+
}
|
|
292
296
|
if (!skipPrompts) {
|
|
293
297
|
const { confirm } = await inquirer.prompt([
|
|
294
298
|
{
|
|
@@ -394,6 +398,31 @@ export async function initCommand(options) {
|
|
|
394
398
|
manifestSpinner.start();
|
|
395
399
|
await createManifest(stack, packageManager ?? undefined);
|
|
396
400
|
manifestSpinner.succeed("Created manifest");
|
|
401
|
+
// Generate AGENTS.md (unless --no-agents-md)
|
|
402
|
+
if (options.agentsMd !== false) {
|
|
403
|
+
const agentsSpinner = ui.spinner("Generating AGENTS.md...");
|
|
404
|
+
agentsSpinner.start();
|
|
405
|
+
try {
|
|
406
|
+
const stackConfig = getStackConfig(stack);
|
|
407
|
+
const { getProjectName } = await import("../lib/project-name.js");
|
|
408
|
+
const projectName = await getProjectName();
|
|
409
|
+
const agentsMdContent = await generateAgentsMd({
|
|
410
|
+
projectName,
|
|
411
|
+
stack: stack,
|
|
412
|
+
buildCommand: stackConfig.variables.BUILD_COMMAND,
|
|
413
|
+
testCommand: stackConfig.variables.TEST_COMMAND,
|
|
414
|
+
lintCommand: stackConfig.variables.LINT_COMMAND,
|
|
415
|
+
});
|
|
416
|
+
await writeAgentsMd(agentsMdContent);
|
|
417
|
+
agentsSpinner.succeed("Generated AGENTS.md");
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
agentsSpinner.warn("Could not generate AGENTS.md (non-blocking)");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
console.log(chalk.gray("Skipping AGENTS.md generation (--no-agents-md)"));
|
|
425
|
+
}
|
|
397
426
|
// Build optional suggestions section
|
|
398
427
|
const optionalSuggestions = suggestions.filter((s) => s.startsWith("Optional"));
|
|
399
428
|
const optionalSection = optionalSuggestions.length > 0
|
package/dist/src/commands/run.js
CHANGED
|
@@ -110,16 +110,14 @@ export async function runCommand(issues, options) {
|
|
|
110
110
|
console.log(chalk.gray("\nUsage: npx sequant run <issues...> [options]"));
|
|
111
111
|
console.log(chalk.gray("Example: npx sequant run 1 2 3 --sequential"));
|
|
112
112
|
console.log(chalk.gray('Batch example: npx sequant run --batch "1 2" --batch "3"'));
|
|
113
|
-
console.log(chalk.gray("Chain example: npx sequant run 1 2 3 --
|
|
113
|
+
console.log(chalk.gray("Chain example: npx sequant run 1 2 3 --chain"));
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
// Validate chain mode requirements
|
|
117
117
|
if (mergedOptions.chain) {
|
|
118
|
+
// Chain mode is inherently sequential — imply --sequential automatically
|
|
118
119
|
if (!mergedOptions.sequential) {
|
|
119
|
-
|
|
120
|
-
console.log(chalk.gray(" Chain mode executes issues sequentially, each branching from the previous."));
|
|
121
|
-
console.log(chalk.gray(" Usage: npx sequant run 1 2 3 --sequential --chain"));
|
|
122
|
-
return;
|
|
120
|
+
mergedOptions.sequential = true;
|
|
123
121
|
}
|
|
124
122
|
if (batches) {
|
|
125
123
|
console.log(chalk.red("❌ --chain cannot be used with --batch"));
|
|
@@ -9,6 +9,9 @@ import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest
|
|
|
9
9
|
import { copyTemplates } from "../lib/templates.js";
|
|
10
10
|
import { getConfig } from "../lib/config.js";
|
|
11
11
|
import { writeFile, readFile, fileExists } from "../lib/fs.js";
|
|
12
|
+
import { generateAgentsMd, writeAgentsMd, AGENTS_MD_PATH, } from "../lib/agents-md.js";
|
|
13
|
+
import { getProjectName } from "../lib/project-name.js";
|
|
14
|
+
import { getStackConfig } from "../lib/stacks.js";
|
|
12
15
|
const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
|
|
13
16
|
/**
|
|
14
17
|
* Get the version of skills currently installed
|
|
@@ -86,6 +89,29 @@ export async function syncCommand(options = {}) {
|
|
|
86
89
|
// Update version markers
|
|
87
90
|
await updateSkillsVersion();
|
|
88
91
|
await updateManifest();
|
|
92
|
+
// Regenerate AGENTS.md if it exists
|
|
93
|
+
if (await fileExists(AGENTS_MD_PATH)) {
|
|
94
|
+
try {
|
|
95
|
+
const stackConfig = getStackConfig(manifest.stack);
|
|
96
|
+
const projectName = await getProjectName();
|
|
97
|
+
const agentsMdContent = await generateAgentsMd({
|
|
98
|
+
projectName,
|
|
99
|
+
stack: manifest.stack,
|
|
100
|
+
buildCommand: stackConfig.variables.BUILD_COMMAND,
|
|
101
|
+
testCommand: stackConfig.variables.TEST_COMMAND,
|
|
102
|
+
lintCommand: stackConfig.variables.LINT_COMMAND,
|
|
103
|
+
});
|
|
104
|
+
await writeAgentsMd(agentsMdContent);
|
|
105
|
+
if (!quiet) {
|
|
106
|
+
console.log(chalk.blue("📄 Regenerated AGENTS.md"));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
if (!quiet) {
|
|
111
|
+
console.log(chalk.yellow("⚠️ Could not regenerate AGENTS.md (non-blocking)"));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
89
115
|
if (!quiet) {
|
|
90
116
|
console.log(chalk.green(`\n✅ Synced to v${packageVersion}`));
|
|
91
117
|
console.log(chalk.gray("\nSkills, hooks, and memory files have been updated."));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md generation and management
|
|
3
|
+
*
|
|
4
|
+
* Generates a universal AGENTS.md file that any AI coding agent can consume.
|
|
5
|
+
* AGENTS.md contains the portable subset of CLAUDE.md instructions.
|
|
6
|
+
*/
|
|
7
|
+
import { type ConventionsFile } from "./conventions-detector.js";
|
|
8
|
+
/** Path to AGENTS.md in project root */
|
|
9
|
+
export declare const AGENTS_MD_PATH = "AGENTS.md";
|
|
10
|
+
/** Configuration for generating AGENTS.md */
|
|
11
|
+
export interface AgentsMdConfig {
|
|
12
|
+
projectName: string;
|
|
13
|
+
stack: string;
|
|
14
|
+
buildCommand?: string;
|
|
15
|
+
testCommand?: string;
|
|
16
|
+
lintCommand?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate AGENTS.md content from project configuration
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateAgentsMd(config: AgentsMdConfig): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Extract portable (non-Claude-specific) instructions from CLAUDE.md
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractPortableInstructions(claudeMdContent: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Check if AGENTS.md is consistent with CLAUDE.md content.
|
|
28
|
+
* Returns a description of inconsistencies, or null if consistent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkAgentsMdConsistency(agentsMdContent: string, claudeMdContent: string): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Format conventions for AGENTS.md output
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatConventionsAsAgentsMd(conventions: ConventionsFile): string;
|
|
35
|
+
/**
|
|
36
|
+
* Write AGENTS.md to the project root
|
|
37
|
+
*/
|
|
38
|
+
export declare function writeAgentsMd(content: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Read existing AGENTS.md if present
|
|
41
|
+
*/
|
|
42
|
+
export declare function readAgentsMd(): Promise<string | null>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md generation and management
|
|
3
|
+
*
|
|
4
|
+
* Generates a universal AGENTS.md file that any AI coding agent can consume.
|
|
5
|
+
* AGENTS.md contains the portable subset of CLAUDE.md instructions.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, fileExists } from "./fs.js";
|
|
8
|
+
import { processTemplate } from "./templates.js";
|
|
9
|
+
import { getStackConfig } from "./stacks.js";
|
|
10
|
+
import { loadConventions, } from "./conventions-detector.js";
|
|
11
|
+
/** Sections in CLAUDE.md that are Claude Code-specific and should NOT be ported */
|
|
12
|
+
const CLAUDE_SPECIFIC_PATTERNS = [
|
|
13
|
+
/^##\s*Slash Commands/i,
|
|
14
|
+
/^##\s*Hook Configuration/i,
|
|
15
|
+
/^##\s*Hooks?$/i,
|
|
16
|
+
/^##\s*Claude Code/i,
|
|
17
|
+
/^##\s*MCP\b/i,
|
|
18
|
+
/^##\s*Skills?$/i,
|
|
19
|
+
];
|
|
20
|
+
/** Path to AGENTS.md in project root */
|
|
21
|
+
export const AGENTS_MD_PATH = "AGENTS.md";
|
|
22
|
+
/**
|
|
23
|
+
* The AGENTS.md template with {{TOKEN}} placeholders.
|
|
24
|
+
* Follows the standard format from https://github.com/agentsmd/agents.md
|
|
25
|
+
*/
|
|
26
|
+
const AGENTS_MD_TEMPLATE = `# AGENTS.md
|
|
27
|
+
|
|
28
|
+
## Project Overview
|
|
29
|
+
|
|
30
|
+
**{{PROJECT_NAME}}** is built with **{{STACK}}**.
|
|
31
|
+
|
|
32
|
+
## Development Commands
|
|
33
|
+
|
|
34
|
+
| Command | Purpose |
|
|
35
|
+
|---------|---------|
|
|
36
|
+
| \`{{BUILD_COMMAND}}\` | Build the project |
|
|
37
|
+
| \`{{TEST_COMMAND}}\` | Run tests |
|
|
38
|
+
| \`{{LINT_COMMAND}}\` | Lint the codebase |
|
|
39
|
+
|
|
40
|
+
## Code Conventions
|
|
41
|
+
|
|
42
|
+
{{CONVENTIONS_SECTION}}
|
|
43
|
+
|
|
44
|
+
## Directory Structure
|
|
45
|
+
|
|
46
|
+
Follow existing project conventions for file placement and naming.
|
|
47
|
+
|
|
48
|
+
## Workflow
|
|
49
|
+
|
|
50
|
+
This project uses [Sequant](https://github.com/sequant-io/sequant) for structured AI-assisted development.
|
|
51
|
+
|
|
52
|
+
To work on a GitHub issue:
|
|
53
|
+
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
npx sequant run <issue-number>
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
This runs a structured workflow: spec → exec → qa.
|
|
59
|
+
|
|
60
|
+
{{PORTABLE_INSTRUCTIONS}}
|
|
61
|
+
`;
|
|
62
|
+
/**
|
|
63
|
+
* Generate AGENTS.md content from project configuration
|
|
64
|
+
*/
|
|
65
|
+
export async function generateAgentsMd(config) {
|
|
66
|
+
const stackConfig = getStackConfig(config.stack);
|
|
67
|
+
const buildCmd = config.buildCommand ||
|
|
68
|
+
stackConfig.variables.BUILD_COMMAND ||
|
|
69
|
+
"npm run build";
|
|
70
|
+
const testCmd = config.testCommand || stackConfig.variables.TEST_COMMAND || "npm test";
|
|
71
|
+
const lintCmd = config.lintCommand || stackConfig.variables.LINT_COMMAND || "npm run lint";
|
|
72
|
+
// Load conventions if available
|
|
73
|
+
const conventionsSection = await getConventionsSection();
|
|
74
|
+
// Check for CLAUDE.md and extract portable instructions
|
|
75
|
+
const portableInstructions = await getPortableInstructions();
|
|
76
|
+
const variables = {
|
|
77
|
+
PROJECT_NAME: config.projectName,
|
|
78
|
+
STACK: stackConfig.displayName || config.stack,
|
|
79
|
+
BUILD_COMMAND: buildCmd,
|
|
80
|
+
TEST_COMMAND: testCmd,
|
|
81
|
+
LINT_COMMAND: lintCmd,
|
|
82
|
+
CONVENTIONS_SECTION: conventionsSection,
|
|
83
|
+
PORTABLE_INSTRUCTIONS: portableInstructions,
|
|
84
|
+
};
|
|
85
|
+
let content = processTemplate(AGENTS_MD_TEMPLATE, variables);
|
|
86
|
+
// Clean up empty sections
|
|
87
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
88
|
+
return content.trimEnd() + "\n";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extract portable (non-Claude-specific) instructions from CLAUDE.md
|
|
92
|
+
*/
|
|
93
|
+
export function extractPortableInstructions(claudeMdContent) {
|
|
94
|
+
const lines = claudeMdContent.split("\n");
|
|
95
|
+
const portableLines = [];
|
|
96
|
+
let skipSection = false;
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
// Check if this line starts a Claude-specific section
|
|
99
|
+
if (line.startsWith("## ")) {
|
|
100
|
+
skipSection = CLAUDE_SPECIFIC_PATTERNS.some((pattern) => pattern.test(line));
|
|
101
|
+
}
|
|
102
|
+
if (!skipSection) {
|
|
103
|
+
portableLines.push(line);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const result = portableLines.join("\n").trim();
|
|
107
|
+
// Remove the top-level heading (e.g. "# Sequant") since AGENTS.md has its own
|
|
108
|
+
const withoutTopHeading = result.replace(/^#\s+.*\n*/, "").trim();
|
|
109
|
+
return withoutTopHeading;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if AGENTS.md is consistent with CLAUDE.md content.
|
|
113
|
+
* Returns a description of inconsistencies, or null if consistent.
|
|
114
|
+
*/
|
|
115
|
+
export function checkAgentsMdConsistency(agentsMdContent, claudeMdContent) {
|
|
116
|
+
const issues = [];
|
|
117
|
+
// Extract portable instructions from current CLAUDE.md
|
|
118
|
+
const portable = extractPortableInstructions(claudeMdContent);
|
|
119
|
+
// Check if key sections from CLAUDE.md portable content appear in AGENTS.md
|
|
120
|
+
// We check for commit rules and other conventions that should be shared
|
|
121
|
+
const commitRulePatterns = [/Co-Authored-By/i, /commit rules?/i];
|
|
122
|
+
for (const pattern of commitRulePatterns) {
|
|
123
|
+
const inClaude = pattern.test(portable);
|
|
124
|
+
const inAgents = pattern.test(agentsMdContent);
|
|
125
|
+
if (inClaude && !inAgents) {
|
|
126
|
+
issues.push(`CLAUDE.md contains "${pattern.source}" but AGENTS.md does not`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return issues.length > 0 ? issues.join("; ") : null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Format conventions for AGENTS.md output
|
|
133
|
+
*/
|
|
134
|
+
export function formatConventionsAsAgentsMd(conventions) {
|
|
135
|
+
const lines = ["# AGENTS.md", "", "## Code Conventions", ""];
|
|
136
|
+
const detected = Object.entries(conventions.detected);
|
|
137
|
+
const manual = Object.entries(conventions.manual);
|
|
138
|
+
const all = [...detected, ...manual];
|
|
139
|
+
if (all.length === 0) {
|
|
140
|
+
lines.push("No conventions detected.");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
for (const [key, value] of all) {
|
|
144
|
+
lines.push(`- **${key}**: ${value}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return lines.join("\n") + "\n";
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Write AGENTS.md to the project root
|
|
151
|
+
*/
|
|
152
|
+
export async function writeAgentsMd(content) {
|
|
153
|
+
await writeFile(AGENTS_MD_PATH, content);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read existing AGENTS.md if present
|
|
157
|
+
*/
|
|
158
|
+
export async function readAgentsMd() {
|
|
159
|
+
if (!(await fileExists(AGENTS_MD_PATH))) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
return await readFile(AGENTS_MD_PATH);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// File read failed — treat as missing (non-blocking)
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// -- Internal helpers --
|
|
171
|
+
async function getConventionsSection() {
|
|
172
|
+
try {
|
|
173
|
+
const conventions = await loadConventions();
|
|
174
|
+
if (!conventions)
|
|
175
|
+
return "Follow existing project patterns and naming conventions.";
|
|
176
|
+
const detected = Object.entries(conventions.detected);
|
|
177
|
+
const manual = Object.entries(conventions.manual);
|
|
178
|
+
const all = [...manual, ...detected]; // manual overrides first
|
|
179
|
+
if (all.length === 0)
|
|
180
|
+
return "Follow existing project patterns and naming conventions.";
|
|
181
|
+
const lines = [];
|
|
182
|
+
for (const [key, value] of all) {
|
|
183
|
+
lines.push(`- **${key}**: ${value}`);
|
|
184
|
+
}
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Convention loading failed — fall back to generic guidance (non-blocking)
|
|
189
|
+
return "Follow existing project patterns and naming conventions.";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function getPortableInstructions() {
|
|
193
|
+
try {
|
|
194
|
+
if (!(await fileExists("CLAUDE.md")))
|
|
195
|
+
return "";
|
|
196
|
+
const claudeMd = await readFile("CLAUDE.md");
|
|
197
|
+
const portable = extractPortableInstructions(claudeMd);
|
|
198
|
+
if (!portable)
|
|
199
|
+
return "";
|
|
200
|
+
return `## Project-Specific Instructions\n\n${portable}`;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// CLAUDE.md read/parse failed — skip portable instructions (non-blocking)
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
9
9
|
import { getMcpServersConfig } from "../system.js";
|
|
10
|
+
import { readAgentsMd } from "../agents-md.js";
|
|
10
11
|
/**
|
|
11
12
|
* Natural language prompts for each phase
|
|
12
13
|
* These prompts will invoke the corresponding skills via natural language
|
|
@@ -21,10 +22,19 @@ const PHASE_PROMPTS = {
|
|
|
21
22
|
loop: "Parse test/QA findings for GitHub issue #{issue} and iterate until quality gates pass. Run the /loop {issue} workflow.",
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
24
|
-
* Phases that require worktree isolation
|
|
25
|
-
* Spec runs in main repo since it's planning-only
|
|
25
|
+
* Phases that require worktree isolation.
|
|
26
|
+
* Spec runs in main repo since it's planning-only.
|
|
27
|
+
* security-review and loop must be isolated because they need to read/modify
|
|
28
|
+
* worktree code, and running them in main directory with a session created
|
|
29
|
+
* in the worktree causes the SDK to crash (cwd mismatch on session resume).
|
|
26
30
|
*/
|
|
27
|
-
const ISOLATED_PHASES = [
|
|
31
|
+
const ISOLATED_PHASES = [
|
|
32
|
+
"exec",
|
|
33
|
+
"security-review",
|
|
34
|
+
"test",
|
|
35
|
+
"qa",
|
|
36
|
+
"loop",
|
|
37
|
+
];
|
|
28
38
|
/**
|
|
29
39
|
* Cold-start retry threshold in seconds.
|
|
30
40
|
* Failures under this duration are likely Claude Code subprocess initialization
|
|
@@ -61,10 +71,20 @@ export function formatDuration(seconds) {
|
|
|
61
71
|
return `${mins}m ${secs.toFixed(0)}s`;
|
|
62
72
|
}
|
|
63
73
|
/**
|
|
64
|
-
* Get the prompt for a phase with the issue number substituted
|
|
74
|
+
* Get the prompt for a phase with the issue number substituted.
|
|
75
|
+
* Includes AGENTS.md content as context so non-Claude agents
|
|
76
|
+
* receive project conventions and workflow instructions.
|
|
65
77
|
*/
|
|
66
|
-
function getPhasePrompt(phase, issueNumber) {
|
|
67
|
-
|
|
78
|
+
async function getPhasePrompt(phase, issueNumber) {
|
|
79
|
+
const basePrompt = PHASE_PROMPTS[phase].replace(/\{issue\}/g, String(issueNumber));
|
|
80
|
+
// Include AGENTS.md content in the prompt context for non-Claude agent compatibility.
|
|
81
|
+
// Claude reads CLAUDE.md natively, but other agents (Aider, Codex, Gemini CLI)
|
|
82
|
+
// rely on AGENTS.md for project context.
|
|
83
|
+
const agentsMd = await readAgentsMd();
|
|
84
|
+
if (agentsMd) {
|
|
85
|
+
return `Project context (from AGENTS.md):\n\n${agentsMd}\n\n---\n\n${basePrompt}`;
|
|
86
|
+
}
|
|
87
|
+
return basePrompt;
|
|
68
88
|
}
|
|
69
89
|
/**
|
|
70
90
|
* Execute a single phase for an issue using Claude Agent SDK
|
|
@@ -82,7 +102,7 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
82
102
|
durationSeconds: 0,
|
|
83
103
|
};
|
|
84
104
|
}
|
|
85
|
-
const prompt = getPhasePrompt(phase, issueNumber);
|
|
105
|
+
const prompt = await getPhasePrompt(phase, issueNumber);
|
|
86
106
|
if (config.verbose) {
|
|
87
107
|
console.log(chalk.gray(` Prompt: ${prompt}`));
|
|
88
108
|
if (worktreePath && ISOLATED_PHASES.includes(phase)) {
|