Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions commands/conductor_exp/analyze.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
description = "Analyzes the project spec DAG and prints relations to track cross-cutting impacts"
prompt = """
## 1.0 SYSTEM DIRECTIVE
You are an AI assistant for the Conductor spec-driven development framework. Your task is to analyze the Spec DAG (Directed Acyclic Graph) to help the user understand dependencies and locking states.

CRITICAL: You must validate the success of every tool call. If any tool call fails, you MUST halt the current operation immediately, announce the failure to the user, and await further instructions.

---

## 2.0 EXECUTION PROTOCOL

1. **Announce Action:** Inform the user you are running the DAG analysis tools.

2. **Execute Impact Analysis:**
- Use the `run_shell_command` tool to execute `node conductor_exp_backend/dist/cli.js analyze`.
- If it fails, report the error.

3. **Execute Graph Visualization:**
- Use the `run_shell_command` tool to execute `node conductor_exp_backend/dist/cli.js graph`.
- If it fails, report the error.

4. **Present Results:**
- Present the combined output of both commands to the user in a clear, formatted markdown response. Highlight any tracks that are explicitly blocked or locked.

"""
238 changes: 238 additions & 0 deletions commands/conductor_exp/implement.toml

Large diffs are not rendered by default.

227 changes: 227 additions & 0 deletions commands/conductor_exp/newTrack.toml

Large diffs are not rendered by default.

591 changes: 591 additions & 0 deletions commands/conductor_exp/setup.toml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions conductor_exp_backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
47 changes: 47 additions & 0 deletions conductor_exp_backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions conductor_exp_backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "spec-management-extension",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node ./dist/cli.js",
"test:local": "npm run build && node ./dist/cli.js"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}
64 changes: 64 additions & 0 deletions conductor_exp_backend/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env node
import { initMasterSpec, addSubSpec, runImpactAnalysis } from './manageSpec.js';
import { initSession, createSessionPlan, createSessionTask, visualizeDAG } from './orchestration.js';

async function run() {
const command = process.argv[2];
const args = process.argv.slice(3);

console.log(`Running CLI Command: ${command} with args: ${args.join(', ')}`);

try {
switch (command) {
case 'init-session':
const [sessId] = args;
console.log(await initSession(sessId));
break;
case 'init':
// Usage: init <specId> <goal> <requirements...>
const [initSpecId, initGoal, ...initReqs] = args;
console.log(await initMasterSpec(initSpecId, initGoal, initReqs, [])); // Empty verifiers for now
break;
case 'sub':
// Usage: sub <specId> <parentId> <goal> <requirements...>
const [subSpecId, parentId, subGoal, ...subReqs] = args;
console.log(await addSubSpec(subSpecId, parentId, subGoal, subReqs, []));
break;
case 'plan':
// Usage: plan [sessionId] <planId> <goal> <specId>
let planSessionId: string | undefined;
let [pId, pGoal, pSpecId] = args;
if (args.length === 4) {
[planSessionId, pId, pGoal, pSpecId] = args;
}
console.log(await createSessionPlan(planSessionId, pId, pGoal, pSpecId));
break;
case 'task':
// Usage: task [sessionId] <taskId> <description> <planId>
let taskSessionId: string | undefined;
let [tId, tDesc, tPlanId] = args;
if (args.length === 4) {
[taskSessionId, tId, tDesc, tPlanId] = args;
}
console.log(await createSessionTask(taskSessionId, tId, tDesc, tPlanId));
break;
case 'graph':
console.log(await visualizeDAG());
break;
case 'analyze':
console.log(await runImpactAnalysis());
break;
default:
console.log("Unknown command or missing arguments.");
console.log("Usage: node cli.js <command> [args]");
console.log("Commands: init-session, init, sub, plan, task, graph, analyze, edit, merge");
break;
}
process.exit(0);
} catch (e: any) {
console.error(`\n❌ Command Failed:`, e.message);
process.exit(1);
}
}

run();
146 changes: 146 additions & 0 deletions conductor_exp_backend/src/manageSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { writeFileSync, readFileSync } from 'fs';
import { exec } from 'child_process';
import path from 'path';


import { existsSync, mkdirSync } from 'fs';

/**
* Ensures the project-level specs/ directory exists for permanent, repo-checked-in spec tracking.
*/
function ensureSpecsDir() {
const specsDir = path.resolve(process.cwd(), 'conductor', 'tracks');
if (!existsSync(specsDir)) {
mkdirSync(specsDir, { recursive: true });
console.error(`Created specs directory at ${specsDir}`);
}
return specsDir;
}

/**
* Initializes a Master Spec at the project root with YAML frontmatter.
* This is used for greenfield or brownfield project goal scoping.
*/
export async function initMasterSpec(specId: string, goal: string, requirements: string[], verifiers: any[]): Promise<string> {
const specsDir = ensureSpecsDir();
const specPath = path.resolve(specsDir, `${specId}.md`);

const yamlFrontmatter = `---
id: ${specId}
type: master-spec
status: draft
requirements:
${requirements.map(r => ` - "${String(r).replace(/"/g, '\\"')}"`).join('\n')}
verifiers:
${verifiers.map(v => ` - type: ${v.type}\n cmd: "${v.cmd}"`).join('\n')}
---
# Master Spec: ${specId}

## Goal
${goal}

## Requirements
${requirements.map(r => `- ${r}`).join('\n')}
`;

writeFileSync(specPath, yamlFrontmatter);
console.error(`Initialized Master Spec at ${specPath}`);
return `Master Spec ${specId} successfully initialized at project level.`;
}

/**
* Creates a Sub-Spec linked back to a parent Master Spec.
* Automatically adds an inline hyperlink in the parent spec for navigation.
*/
export async function addSubSpec(specId: string, parentId: string, goal: string, requirements: string[], verifiers: any[]): Promise<string> {
const specsDir = ensureSpecsDir();
const specPath = path.resolve(specsDir, `${specId}.md`);

// Verify parent exists before linking to ensure graph integrity
const parentPath = path.resolve(specsDir, `${parentId}.md`);
if (!existsSync(parentPath)) {
throw new Error(`Parent spec ID '${parentId}' not found at '${parentPath}'. Cannot link.`);
}

const yamlFrontmatter = `---
id: ${specId}
type: sub-spec
parent: ${parentId}
status: draft
requirements:
${requirements.map(r => ` - "${String(r).replace(/"/g, '\\"')}"`).join('\n')}
verifiers:
${verifiers.map(v => ` - type: ${v.type}\n cmd: "${v.cmd}"`).join('\n')}
---
# Sub-Spec: ${specId}

## Parent Link
[Parent Spec (${parentId})](file://${parentPath})

## Context / Goal
${goal}

## Requirements
${requirements.map(r => `- ${r}`).join('\n')}
`;

writeFileSync(specPath, yamlFrontmatter);

// Now modify parent to add link
let parentContent = readFileSync(parentPath, 'utf-8');

// Append the link at the end or in a "Sub-specs" section
if (!parentContent.includes('## Sub-Specs')) {
parentContent += `\n\n## Sub-Specs\n`;
}
parentContent += `- [Sub-Spec ${specId}](file://${specPath})\n`;

writeFileSync(parentPath, parentContent);

console.error(`Added Sub-Spec at ${specPath} and linked in ${parentPath}`);
return `Sub-Spec ${specId} successfully linked to ${parentId}.`;
}

import { readdirSync, readFileSync as readFsFile } from 'fs';

/**
* Reads all specs in the specs/ folder to build a relational graph of dependencies.
* This is used by agents before modification to predict cross-cutting impacts.
*/
export async function runImpactAnalysis(): Promise<string> {
const specsDir = ensureSpecsDir();
const files = readdirSync(specsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => path.join(dirent.name, 'spec.md'))
.filter(relPath => existsSync(path.resolve(specsDir, relPath)));

let summary = "### Project Spec Hierarchy Graph\n\n";

for (const file of files) {
const filePath = path.resolve(specsDir, file);
const content = readFsFile(filePath, 'utf-8');

// Extract Title and metadata manually to avoid external heavy YAML parser dependencies
const titleMatch = content.match(/^# (.*)$/m);
const title = titleMatch ? titleMatch[1] : file;

const idMatch = content.match(/^id:\s*(.*)$/m);
const typeMatch = content.match(/^type:\s*(.*)$/m);
const parentMatch = content.match(/^parent:\s*(.*)$/m);

summary += `- **${idMatch ? idMatch[1].trim() : 'Unknown'}** (${typeMatch ? typeMatch[1].trim() : 'unknown'})\n`;
summary += ` Title: ${title}\n`;
if (parentMatch) {
summary += ` Parent: ${parentMatch[1].trim()}\n`;
}
summary += ` Path: ${file}\n\n`;
}

if (files.length === 0) {
return "No specs found in the repository. Initialize using create_master_spec.";
}

return summary;
}


Loading
Loading