@weldr/runr 0.4.0 → 0.7.3
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/CHANGELOG.md +166 -1
- package/README.md +124 -165
- package/dist/audit/classifier.js +331 -0
- package/dist/cli.js +570 -300
- package/dist/commands/audit.js +259 -0
- package/dist/commands/bundle.js +180 -0
- package/dist/commands/continue.js +276 -0
- package/dist/commands/doctor.js +430 -45
- package/dist/commands/hooks.js +352 -0
- package/dist/commands/init.js +368 -8
- package/dist/commands/intervene.js +109 -0
- package/dist/commands/meta.js +245 -0
- package/dist/commands/mode.js +157 -0
- package/dist/commands/orchestrate.js +29 -0
- package/dist/commands/packs.js +47 -0
- package/dist/commands/preflight.js +8 -5
- package/dist/commands/resume.js +421 -3
- package/dist/commands/run.js +63 -4
- package/dist/commands/status.js +47 -0
- package/dist/commands/submit.js +374 -0
- package/dist/config/schema.js +61 -1
- package/dist/diagnosis/analyzer.js +86 -1
- package/dist/diagnosis/formatter.js +3 -0
- package/dist/diagnosis/index.js +1 -0
- package/dist/diagnosis/stop-explainer.js +267 -0
- package/dist/diagnostics/stop-explainer.js +267 -0
- package/dist/guards/checkpoint.js +119 -0
- package/dist/journal/builder.js +36 -3
- package/dist/journal/renderer.js +19 -0
- package/dist/orchestrator/artifacts.js +17 -2
- package/dist/orchestrator/receipt.js +304 -0
- package/dist/output/stop-footer.js +185 -0
- package/dist/packs/actions.js +176 -0
- package/dist/packs/loader.js +200 -0
- package/dist/packs/renderer.js +46 -0
- package/dist/receipt/intervention.js +465 -0
- package/dist/receipt/writer.js +296 -0
- package/dist/redaction/redactor.js +95 -0
- package/dist/repo/context.js +147 -20
- package/dist/review/check-parser.js +211 -0
- package/dist/store/checkpoint-metadata.js +111 -0
- package/dist/store/run-store.js +21 -0
- package/dist/supervisor/runner.js +130 -10
- package/dist/tasks/task-metadata.js +74 -1
- package/dist/ux/brain.js +528 -0
- package/dist/ux/render.js +123 -0
- package/dist/ux/safe-commands.js +133 -0
- package/dist/ux/state.js +193 -0
- package/dist/ux/telemetry.js +110 -0
- package/package.json +3 -1
- package/packs/pr/pack.json +50 -0
- package/packs/pr/templates/AGENTS.md.tmpl +120 -0
- package/packs/pr/templates/CLAUDE.md.tmpl +101 -0
- package/packs/pr/templates/bundle.md.tmpl +27 -0
- package/packs/solo/pack.json +82 -0
- package/packs/solo/templates/AGENTS.md.tmpl +80 -0
- package/packs/solo/templates/CLAUDE.md.tmpl +126 -0
- package/packs/solo/templates/bundle.md.tmpl +27 -0
- package/packs/solo/templates/claude-cmd-bundle.md.tmpl +40 -0
- package/packs/solo/templates/claude-cmd-resume.md.tmpl +43 -0
- package/packs/solo/templates/claude-cmd-submit.md.tmpl +51 -0
- package/packs/solo/templates/claude-skill.md.tmpl +96 -0
- package/packs/trunk/pack.json +50 -0
- package/packs/trunk/templates/AGENTS.md.tmpl +87 -0
- package/packs/trunk/templates/CLAUDE.md.tmpl +126 -0
- package/packs/trunk/templates/bundle.md.tmpl +27 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { getRunrPaths } from '../store/runs-root.js';
|
|
5
|
+
/**
|
|
6
|
+
* Detect which meta-agent tool is available
|
|
7
|
+
*/
|
|
8
|
+
async function detectTool() {
|
|
9
|
+
// Try Claude Code first
|
|
10
|
+
try {
|
|
11
|
+
const result = await execa('claude', ['--version'], {
|
|
12
|
+
timeout: 5000,
|
|
13
|
+
reject: false
|
|
14
|
+
});
|
|
15
|
+
if (result.exitCode === 0) {
|
|
16
|
+
return 'claude';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Claude not found, continue
|
|
21
|
+
}
|
|
22
|
+
// Try Codex CLI
|
|
23
|
+
try {
|
|
24
|
+
const result = await execa('codex', ['--version'], {
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
reject: false
|
|
27
|
+
});
|
|
28
|
+
if (result.exitCode === 0) {
|
|
29
|
+
return 'codex';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Codex not found
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if working tree is clean
|
|
39
|
+
*/
|
|
40
|
+
async function checkWorkingTree(repoPath) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await execa('git', ['status', '--porcelain'], {
|
|
43
|
+
cwd: repoPath,
|
|
44
|
+
reject: false
|
|
45
|
+
});
|
|
46
|
+
if (result.exitCode !== 0) {
|
|
47
|
+
throw new Error('git status failed');
|
|
48
|
+
}
|
|
49
|
+
const lines = result.stdout.trim().split('\n').filter(line => line.length > 0);
|
|
50
|
+
return {
|
|
51
|
+
clean: lines.length === 0,
|
|
52
|
+
uncommittedCount: lines.length
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error(`Failed to check working tree: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if repository is properly set up for Runr
|
|
61
|
+
*/
|
|
62
|
+
function checkRepoSetup(repoPath) {
|
|
63
|
+
const paths = getRunrPaths(repoPath);
|
|
64
|
+
const configPath = path.join(paths.runr_root, 'runr.config.json');
|
|
65
|
+
const gitignorePath = path.join(repoPath, '.gitignore');
|
|
66
|
+
const agentsMdPath = path.join(repoPath, 'AGENTS.md');
|
|
67
|
+
const configExists = fs.existsSync(configPath);
|
|
68
|
+
const agentsMdExists = fs.existsSync(agentsMdPath);
|
|
69
|
+
// Check gitignore
|
|
70
|
+
let gitignoreOk = false;
|
|
71
|
+
try {
|
|
72
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
73
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
74
|
+
gitignoreOk = lines.some(line => line.startsWith('.runr/') || line === '.runr' || line === '.runr/');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// .gitignore doesn't exist
|
|
78
|
+
}
|
|
79
|
+
const missingSetup = [];
|
|
80
|
+
if (!configExists)
|
|
81
|
+
missingSetup.push('.runr/runr.config.json');
|
|
82
|
+
if (!gitignoreOk)
|
|
83
|
+
missingSetup.push('.gitignore entries');
|
|
84
|
+
if (!agentsMdExists)
|
|
85
|
+
missingSetup.push('AGENTS.md');
|
|
86
|
+
return {
|
|
87
|
+
configExists,
|
|
88
|
+
gitignoreOk,
|
|
89
|
+
agentsMdExists,
|
|
90
|
+
missingSetup
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check Claude Code integration status
|
|
95
|
+
*/
|
|
96
|
+
function checkClaudeIntegration(repoPath) {
|
|
97
|
+
const skillPath = path.join(repoPath, '.claude/skills/runr-workflow/SKILL.md');
|
|
98
|
+
const commandPaths = [
|
|
99
|
+
path.join(repoPath, '.claude/commands/runr-bundle.md'),
|
|
100
|
+
path.join(repoPath, '.claude/commands/runr-submit.md'),
|
|
101
|
+
path.join(repoPath, '.claude/commands/runr-resume.md')
|
|
102
|
+
];
|
|
103
|
+
const skillsPresent = fs.existsSync(skillPath);
|
|
104
|
+
const commandsPresent = commandPaths.every(p => fs.existsSync(p));
|
|
105
|
+
const missingFiles = [];
|
|
106
|
+
if (!skillsPresent)
|
|
107
|
+
missingFiles.push('.claude/skills/runr-workflow/SKILL.md');
|
|
108
|
+
commandPaths.forEach(p => {
|
|
109
|
+
if (!fs.existsSync(p)) {
|
|
110
|
+
missingFiles.push(path.relative(repoPath, p));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
skillsPresent,
|
|
115
|
+
commandsPresent,
|
|
116
|
+
missingFiles
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Launch the meta-agent tool
|
|
121
|
+
*/
|
|
122
|
+
async function launchTool(tool, repoPath, interactive) {
|
|
123
|
+
console.log(`\nLaunching ${tool === 'claude' ? 'Claude Code' : 'Codex CLI'} with Runr workflow...\n`);
|
|
124
|
+
if (tool === 'claude') {
|
|
125
|
+
console.log('Agent will follow rules from:');
|
|
126
|
+
console.log('- AGENTS.md (workflow guide)');
|
|
127
|
+
const claudeCheck = checkClaudeIntegration(repoPath);
|
|
128
|
+
if (claudeCheck.skillsPresent) {
|
|
129
|
+
console.log('- .claude/skills/runr-workflow (safety playbook)');
|
|
130
|
+
}
|
|
131
|
+
if (claudeCheck.commandsPresent) {
|
|
132
|
+
console.log('- .claude/commands/ (runr shortcuts)');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log('Agent will follow rules from:');
|
|
137
|
+
console.log('- AGENTS.md (workflow guide, read by Codex)');
|
|
138
|
+
}
|
|
139
|
+
if (!interactive && tool === 'claude') {
|
|
140
|
+
console.log('\nPermission mode: skip all permissions (allows Bash, Edit, etc.)');
|
|
141
|
+
}
|
|
142
|
+
console.log('Exit with Ctrl+C\n');
|
|
143
|
+
console.log('─'.repeat(60));
|
|
144
|
+
console.log();
|
|
145
|
+
// Launch the tool in interactive mode
|
|
146
|
+
try {
|
|
147
|
+
// Build args based on tool
|
|
148
|
+
const args = [];
|
|
149
|
+
if (tool === 'claude' && !interactive) {
|
|
150
|
+
// Use dangerously-skip-permissions to bypass all confirmation dialogs
|
|
151
|
+
// This matches what Runr uses when running Claude as a worker
|
|
152
|
+
// Allows the agent to use Bash (for runr commands), Edit, and other tools
|
|
153
|
+
args.push('--dangerously-skip-permissions');
|
|
154
|
+
}
|
|
155
|
+
await execa(tool, args, {
|
|
156
|
+
cwd: repoPath,
|
|
157
|
+
stdio: 'inherit'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// User likely pressed Ctrl+C
|
|
162
|
+
console.log('\nMeta-agent session ended.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Main meta command implementation
|
|
167
|
+
*/
|
|
168
|
+
export async function metaCommand(options) {
|
|
169
|
+
const repoPath = path.resolve(options.repo || '.');
|
|
170
|
+
// Step 1: Detect tool
|
|
171
|
+
let tool;
|
|
172
|
+
if (options.tool === 'auto' || !options.tool) {
|
|
173
|
+
const detected = await detectTool();
|
|
174
|
+
if (!detected) {
|
|
175
|
+
console.error('❌ No meta-agent tool found\n');
|
|
176
|
+
console.error('Install one of:');
|
|
177
|
+
console.error(' • Claude Code: https://code.claude.com');
|
|
178
|
+
console.error(' • Codex CLI: https://github.com/openai/codex-cli');
|
|
179
|
+
process.exit(2);
|
|
180
|
+
}
|
|
181
|
+
tool = detected;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
tool = options.tool;
|
|
185
|
+
// Verify the requested tool is available
|
|
186
|
+
const detected = await detectTool();
|
|
187
|
+
if (detected !== tool) {
|
|
188
|
+
console.error(`❌ Requested tool "${tool}" not found`);
|
|
189
|
+
console.error('Run with --tool auto to detect available tools');
|
|
190
|
+
process.exit(2);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Step 2: Check working tree
|
|
194
|
+
try {
|
|
195
|
+
const treeCheck = await checkWorkingTree(repoPath);
|
|
196
|
+
if (!treeCheck.clean) {
|
|
197
|
+
if (options.allowDirty) {
|
|
198
|
+
console.log('⚠️ WARNING: Working tree has uncommitted changes\n');
|
|
199
|
+
console.log('Running agents on uncommitted work risks data loss.');
|
|
200
|
+
console.log('You used --allow-dirty to override this safety check.\n');
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.error('⛔ BLOCKED: Working tree has uncommitted changes\n');
|
|
204
|
+
console.error('Running agents on uncommitted work risks data loss.\n');
|
|
205
|
+
console.error('Fix with:');
|
|
206
|
+
console.error(' git commit -am "WIP: save before agent"');
|
|
207
|
+
console.error(' # OR');
|
|
208
|
+
console.error(' git stash\n');
|
|
209
|
+
console.error('To override (not recommended):');
|
|
210
|
+
console.error(' runr meta --allow-dirty');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(`❌ Failed to check working tree: ${error.message}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
// Step 3: Check repo setup
|
|
220
|
+
const setup = checkRepoSetup(repoPath);
|
|
221
|
+
if (setup.missingSetup.length > 0) {
|
|
222
|
+
console.error('⚠️ Incomplete Runr setup\n');
|
|
223
|
+
console.error('Missing:');
|
|
224
|
+
setup.missingSetup.forEach(item => console.error(` • ${item}`));
|
|
225
|
+
console.error('\nFix with:');
|
|
226
|
+
console.error(' runr init --pack solo');
|
|
227
|
+
console.error();
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
// Step 4: Claude-specific checks
|
|
231
|
+
if (tool === 'claude') {
|
|
232
|
+
const claudeCheck = checkClaudeIntegration(repoPath);
|
|
233
|
+
if (!claudeCheck.skillsPresent || !claudeCheck.commandsPresent) {
|
|
234
|
+
console.log('💡 Tip: Claude Code integration incomplete\n');
|
|
235
|
+
console.log('Missing:');
|
|
236
|
+
claudeCheck.missingFiles.forEach(file => console.log(` • ${file}`));
|
|
237
|
+
console.log('\nFor better integration, run:');
|
|
238
|
+
console.log(' runr init --pack solo --with-claude\n');
|
|
239
|
+
console.log('(This will add Claude Code skills and commands)\n');
|
|
240
|
+
// Don't block, just inform
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Step 5: Launch tool
|
|
244
|
+
await launchTool(tool, repoPath, options.interactive || false);
|
|
245
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runr mode - View or set workflow mode
|
|
3
|
+
*
|
|
4
|
+
* Mode determines how strictly Runr enforces audit trail requirements:
|
|
5
|
+
* - flow: Productivity-first, interventions allowed freely
|
|
6
|
+
* - ledger: Audit-first, stricter controls on interventions
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* runr mode # Show current mode
|
|
10
|
+
* runr mode flow # Set mode to flow
|
|
11
|
+
* runr mode ledger # Set mode to ledger
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { loadConfig, resolveConfigPath } from '../config/load.js';
|
|
16
|
+
const VALID_MODES = ['flow', 'ledger'];
|
|
17
|
+
/**
|
|
18
|
+
* Get current mode from config.
|
|
19
|
+
*/
|
|
20
|
+
export function getCurrentMode(repoPath) {
|
|
21
|
+
try {
|
|
22
|
+
const configPath = resolveConfigPath(repoPath);
|
|
23
|
+
if (!fs.existsSync(configPath)) {
|
|
24
|
+
return 'flow';
|
|
25
|
+
}
|
|
26
|
+
const config = loadConfig(configPath);
|
|
27
|
+
return config.workflow?.mode || 'flow';
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 'flow';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Print mode banner (called at start of commands).
|
|
35
|
+
*/
|
|
36
|
+
export function printModeBanner(repoPath) {
|
|
37
|
+
const mode = getCurrentMode(repoPath);
|
|
38
|
+
const version = '0.7.0'; // TODO: Read from package.json
|
|
39
|
+
console.log(`Runr v${version} | Mode: ${mode}`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if a mode-restricted operation is allowed.
|
|
43
|
+
*/
|
|
44
|
+
export function checkModeRestriction(repoPath, operation, forceOverride) {
|
|
45
|
+
const mode = getCurrentMode(repoPath);
|
|
46
|
+
if (forceOverride) {
|
|
47
|
+
return { allowed: true };
|
|
48
|
+
}
|
|
49
|
+
if (mode === 'ledger') {
|
|
50
|
+
switch (operation) {
|
|
51
|
+
case 'amend_last':
|
|
52
|
+
return {
|
|
53
|
+
allowed: false,
|
|
54
|
+
error: `Error: --amend-last is not allowed in Ledger mode.
|
|
55
|
+
In Ledger mode, use explicit commits:
|
|
56
|
+
runr intervene <run_id> --commit "message" --reason <reason>
|
|
57
|
+
Or switch to Flow mode with: runr config mode flow`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { allowed: true };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Set mode in config file.
|
|
65
|
+
*/
|
|
66
|
+
function setMode(repoPath, newMode) {
|
|
67
|
+
const configPath = resolveConfigPath(repoPath);
|
|
68
|
+
const configExists = fs.existsSync(configPath);
|
|
69
|
+
if (!configExists) {
|
|
70
|
+
// Create new config
|
|
71
|
+
const dir = path.dirname(configPath);
|
|
72
|
+
if (!fs.existsSync(dir)) {
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
const newConfig = {
|
|
76
|
+
workflow: {
|
|
77
|
+
mode: newMode,
|
|
78
|
+
profile: 'solo',
|
|
79
|
+
integration_branch: 'dev',
|
|
80
|
+
release_branch: 'main'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Update existing config
|
|
87
|
+
try {
|
|
88
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
89
|
+
const config = JSON.parse(content);
|
|
90
|
+
if (!config.workflow) {
|
|
91
|
+
config.workflow = {};
|
|
92
|
+
}
|
|
93
|
+
config.workflow.mode = newMode;
|
|
94
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
throw new Error(`Failed to update config: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mode command: View or set workflow mode.
|
|
102
|
+
*/
|
|
103
|
+
export async function modeCommand(options) {
|
|
104
|
+
const { repo, newMode } = options;
|
|
105
|
+
if (newMode) {
|
|
106
|
+
// Validate mode
|
|
107
|
+
if (!VALID_MODES.includes(newMode)) {
|
|
108
|
+
console.error(`Error: Invalid mode '${newMode}'`);
|
|
109
|
+
console.error(`Valid modes: ${VALID_MODES.join(', ')}`);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Set mode
|
|
114
|
+
try {
|
|
115
|
+
setMode(repo, newMode);
|
|
116
|
+
console.log(`Mode set to: ${newMode}`);
|
|
117
|
+
if (newMode === 'ledger') {
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log('Ledger mode restrictions:');
|
|
120
|
+
console.log(' - --amend-last is not allowed');
|
|
121
|
+
console.log(' - All merges should go through runr submit');
|
|
122
|
+
console.log(' - Higher audit coverage expectations');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log('Flow mode enabled:');
|
|
127
|
+
console.log(' - Interventions allowed freely');
|
|
128
|
+
console.log(' - --amend-last allowed');
|
|
129
|
+
console.log(' - Flexible workflow for productivity');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error(err.message);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Show current mode
|
|
139
|
+
const mode = getCurrentMode(repo);
|
|
140
|
+
console.log(`Current mode: ${mode}`);
|
|
141
|
+
console.log('');
|
|
142
|
+
if (mode === 'flow') {
|
|
143
|
+
console.log('Flow mode (productivity-first):');
|
|
144
|
+
console.log(' - Interventions allowed freely');
|
|
145
|
+
console.log(' - --amend-last allowed');
|
|
146
|
+
console.log(' - Flexible workflow');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log('Ledger mode (audit-first):');
|
|
150
|
+
console.log(' - --amend-last not allowed');
|
|
151
|
+
console.log(' - Strict audit trail');
|
|
152
|
+
console.log(' - All changes through runr submit');
|
|
153
|
+
}
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('To change mode: runr config mode <flow|ledger>');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -798,3 +798,32 @@ function outputWaitResult(result, json, elapsedMs) {
|
|
|
798
798
|
}
|
|
799
799
|
}
|
|
800
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Generate and display orchestration receipt.
|
|
803
|
+
*/
|
|
804
|
+
export async function receiptCommand(options) {
|
|
805
|
+
const { getReceipt, writeReceipt, generateReceiptMarkdown } = await import('../orchestrator/receipt.js');
|
|
806
|
+
const repoPath = path.resolve(options.repo);
|
|
807
|
+
// Get receipt (from cache or generate from state)
|
|
808
|
+
const receipt = getReceipt(repoPath, options.orchestratorId);
|
|
809
|
+
if (!receipt) {
|
|
810
|
+
console.error(`Orchestration not found: ${options.orchestratorId}`);
|
|
811
|
+
process.exitCode = 1;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
// Write artifacts if requested
|
|
815
|
+
if (options.write) {
|
|
816
|
+
const paths = writeReceipt(receipt, repoPath);
|
|
817
|
+
console.log(`Receipt written:`);
|
|
818
|
+
console.log(` JSON: ${paths.json}`);
|
|
819
|
+
console.log(` MD: ${paths.md}`);
|
|
820
|
+
console.log('');
|
|
821
|
+
}
|
|
822
|
+
// Output
|
|
823
|
+
if (options.json) {
|
|
824
|
+
console.log(JSON.stringify(receipt, null, 2));
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
console.log(generateReceiptMarkdown(receipt));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { loadAllPacks, getPacksDirectory } from '../packs/loader.js';
|
|
2
|
+
/**
|
|
3
|
+
* List available packs
|
|
4
|
+
*/
|
|
5
|
+
export async function packsCommand(options = {}) {
|
|
6
|
+
const packs = loadAllPacks();
|
|
7
|
+
if (options.verbose) {
|
|
8
|
+
console.log(`Loading packs from: ${getPacksDirectory()}\n`);
|
|
9
|
+
}
|
|
10
|
+
if (packs.length === 0) {
|
|
11
|
+
console.log('No packs found.');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('Packs are workflow presets that provide:');
|
|
14
|
+
console.log(' • Default configuration (branches, verification)');
|
|
15
|
+
console.log(' • Documentation templates (AGENTS.md, CLAUDE.md)');
|
|
16
|
+
console.log(' • Idempotent initialization actions');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const validPacks = packs.filter(p => p.validation.valid);
|
|
20
|
+
const invalidPacks = packs.filter(p => !p.validation.valid);
|
|
21
|
+
console.log('Available workflow packs:\n');
|
|
22
|
+
// Display valid packs
|
|
23
|
+
for (const pack of validPacks) {
|
|
24
|
+
console.log(` \x1b[1m${pack.name}\x1b[0m`);
|
|
25
|
+
console.log(` ${pack.manifest.description}`);
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
28
|
+
if (validPacks.length > 0) {
|
|
29
|
+
console.log('Usage:');
|
|
30
|
+
console.log(` runr init --pack solo # Solo dev workflow (dev→main)`);
|
|
31
|
+
console.log(` runr init --pack pr # PR workflow (feature→main)`);
|
|
32
|
+
console.log(` runr init --pack trunk # Trunk-based (main only)`);
|
|
33
|
+
console.log(` runr init --pack solo --dry-run # Preview changes`);
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
// Display invalid packs
|
|
37
|
+
if (invalidPacks.length > 0) {
|
|
38
|
+
console.log('\x1b[33mInvalid packs:\x1b[0m\n');
|
|
39
|
+
for (const pack of invalidPacks) {
|
|
40
|
+
console.log(` ${pack.name}`);
|
|
41
|
+
for (const error of pack.validation.errors) {
|
|
42
|
+
console.log(` ❌ ${error}`);
|
|
43
|
+
}
|
|
44
|
+
console.log('');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -72,12 +72,15 @@ export async function runPreflight(options) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
// Check worker binaries exist (cheaper than ping, catches "command not found")
|
|
75
|
+
// Only check workers that are actually used by phases
|
|
75
76
|
const workers = options.config.workers;
|
|
77
|
+
const phases = options.config.phases;
|
|
78
|
+
const usedWorkers = new Set([phases.plan, phases.implement, phases.review]);
|
|
76
79
|
const binaryCheckPromises = [];
|
|
77
|
-
if (workers.claude) {
|
|
80
|
+
if (workers.claude && usedWorkers.has('claude')) {
|
|
78
81
|
binaryCheckPromises.push(checkWorkerBinary('claude', workers.claude));
|
|
79
82
|
}
|
|
80
|
-
if (workers.codex) {
|
|
83
|
+
if (workers.codex && usedWorkers.has('codex')) {
|
|
81
84
|
binaryCheckPromises.push(checkWorkerBinary('codex', workers.codex));
|
|
82
85
|
}
|
|
83
86
|
const binaryResults = await Promise.all(binaryCheckPromises);
|
|
@@ -103,12 +106,12 @@ export async function runPreflight(options) {
|
|
|
103
106
|
}
|
|
104
107
|
else {
|
|
105
108
|
const pingResults = [];
|
|
106
|
-
// Ping all
|
|
109
|
+
// Ping all workers that are actually used by phases
|
|
107
110
|
const pingPromises = [];
|
|
108
|
-
if (workers.claude) {
|
|
111
|
+
if (workers.claude && usedWorkers.has('claude')) {
|
|
109
112
|
pingPromises.push(pingClaude(workers.claude));
|
|
110
113
|
}
|
|
111
|
-
if (workers.codex) {
|
|
114
|
+
if (workers.codex && usedWorkers.has('codex')) {
|
|
112
115
|
pingPromises.push(pingCodex(workers.codex));
|
|
113
116
|
}
|
|
114
117
|
const results = await Promise.all(pingPromises);
|