@cluesmith/codev 2.0.0-rc.1 → 2.0.0-rc.11
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/bin/porch.js +9 -4
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +2 -14
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/kickoff.d.ts +1 -0
- package/dist/agent-farm/commands/kickoff.d.ts.map +1 -1
- package/dist/agent-farm/commands/kickoff.js +82 -78
- package/dist/agent-farm/commands/kickoff.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +30 -54
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +8 -50
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +0 -14
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts +0 -10
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +0 -24
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -17
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +17 -1
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +2 -1
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +17 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +16 -29
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +90 -144
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/claude.d.ts +29 -0
- package/dist/commands/porch/claude.d.ts.map +1 -0
- package/dist/commands/porch/claude.js +80 -0
- package/dist/commands/porch/claude.js.map +1 -0
- package/dist/commands/porch/index.d.ts +21 -43
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +449 -753
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/plan.d.ts +60 -0
- package/dist/commands/porch/plan.d.ts.map +1 -0
- package/dist/commands/porch/plan.js +162 -0
- package/dist/commands/porch/plan.js.map +1 -0
- package/dist/commands/porch/prompts.d.ts +19 -0
- package/dist/commands/porch/prompts.d.ts.map +1 -0
- package/dist/commands/porch/prompts.js +270 -0
- package/dist/commands/porch/prompts.js.map +1 -0
- package/dist/commands/porch/protocol.d.ts +59 -0
- package/dist/commands/porch/protocol.d.ts.map +1 -0
- package/dist/commands/porch/protocol.js +252 -0
- package/dist/commands/porch/protocol.js.map +1 -0
- package/dist/commands/porch/repl.d.ts +33 -0
- package/dist/commands/porch/repl.d.ts.map +1 -0
- package/dist/commands/porch/repl.js +206 -0
- package/dist/commands/porch/repl.js.map +1 -0
- package/dist/commands/porch/run.d.ts +15 -0
- package/dist/commands/porch/run.d.ts.map +1 -0
- package/dist/commands/porch/run.js +718 -0
- package/dist/commands/porch/run.js.map +1 -0
- package/dist/commands/porch/signals.d.ts +38 -0
- package/dist/commands/porch/signals.d.ts.map +1 -0
- package/dist/commands/porch/signals.js +81 -0
- package/dist/commands/porch/signals.js.map +1 -0
- package/dist/commands/porch/state.d.ts +19 -112
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +78 -685
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +69 -173
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/commands/porch/types.js +2 -1
- package/dist/commands/porch/types.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +19 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/scaffold.d.ts +24 -0
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +78 -0
- package/dist/lib/scaffold.js.map +1 -1
- package/package.json +7 -2
- package/skeleton/protocols/bugfix/protocol.json +1 -1
- package/skeleton/protocols/spider/prompts/implement.md +201 -0
- package/skeleton/protocols/spider/prompts/plan.md +214 -0
- package/skeleton/protocols/spider/prompts/review.md +217 -0
- package/skeleton/protocols/spider/prompts/specify.md +174 -0
- package/skeleton/protocols/spider/protocol.json +81 -147
- package/skeleton/protocols/spider/protocol.md +26 -16
- package/skeleton/protocols/spider/templates/plan.md +14 -0
- package/skeleton/protocols/tick/protocol.json +1 -1
- package/skeleton/roles/architect.md +40 -48
- package/skeleton/roles/builder.md +135 -213
- package/templates/dashboard/index.html +0 -27
- package/templates/dashboard/js/utils.js +0 -86
- package/dist/agent-farm/commands/rename.d.ts +0 -13
- package/dist/agent-farm/commands/rename.d.ts.map +0 -1
- package/dist/agent-farm/commands/rename.js +0 -33
- package/dist/agent-farm/commands/rename.js.map +0 -1
- package/dist/commands/pcheck/cache.d.ts +0 -48
- package/dist/commands/pcheck/cache.d.ts.map +0 -1
- package/dist/commands/pcheck/cache.js +0 -170
- package/dist/commands/pcheck/cache.js.map +0 -1
- package/dist/commands/pcheck/evaluator.d.ts +0 -15
- package/dist/commands/pcheck/evaluator.d.ts.map +0 -1
- package/dist/commands/pcheck/evaluator.js +0 -246
- package/dist/commands/pcheck/evaluator.js.map +0 -1
- package/dist/commands/pcheck/index.d.ts +0 -12
- package/dist/commands/pcheck/index.d.ts.map +0 -1
- package/dist/commands/pcheck/index.js +0 -249
- package/dist/commands/pcheck/index.js.map +0 -1
- package/dist/commands/pcheck/parser.d.ts +0 -39
- package/dist/commands/pcheck/parser.d.ts.map +0 -1
- package/dist/commands/pcheck/parser.js +0 -155
- package/dist/commands/pcheck/parser.js.map +0 -1
- package/dist/commands/pcheck/types.d.ts +0 -82
- package/dist/commands/pcheck/types.d.ts.map +0 -1
- package/dist/commands/pcheck/types.js +0 -5
- package/dist/commands/pcheck/types.js.map +0 -1
- package/dist/commands/porch/consultation.d.ts +0 -56
- package/dist/commands/porch/consultation.d.ts.map +0 -1
- package/dist/commands/porch/consultation.js +0 -330
- package/dist/commands/porch/consultation.js.map +0 -1
- package/dist/commands/porch/notifications.d.ts +0 -99
- package/dist/commands/porch/notifications.d.ts.map +0 -1
- package/dist/commands/porch/notifications.js +0 -223
- package/dist/commands/porch/notifications.js.map +0 -1
- package/dist/commands/porch/plan-parser.d.ts +0 -38
- package/dist/commands/porch/plan-parser.d.ts.map +0 -1
- package/dist/commands/porch/plan-parser.js +0 -166
- package/dist/commands/porch/plan-parser.js.map +0 -1
- package/dist/commands/porch/protocol-loader.d.ts +0 -46
- package/dist/commands/porch/protocol-loader.d.ts.map +0 -1
- package/dist/commands/porch/protocol-loader.js +0 -249
- package/dist/commands/porch/protocol-loader.js.map +0 -1
- package/dist/commands/porch/signal-parser.d.ts +0 -88
- package/dist/commands/porch/signal-parser.d.ts.map +0 -1
- package/dist/commands/porch/signal-parser.js +0 -148
- package/dist/commands/porch/signal-parser.js.map +0 -1
- package/skeleton/porch/protocols/bugfix.json +0 -85
- package/skeleton/porch/protocols/spider.json +0 -135
- package/skeleton/porch/protocols/tick.json +0 -76
- package/templates/dashboard/css/activity.css +0 -151
- package/templates/dashboard/js/activity.js +0 -112
|
@@ -1,828 +1,524 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Porch - Protocol Orchestrator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* context per iteration with state persisted to files.
|
|
4
|
+
* Claude calls porch as a tool; porch returns prescriptive instructions.
|
|
5
|
+
* All commands produce clear, actionable output.
|
|
7
6
|
*/
|
|
8
7
|
import * as fs from 'node:fs';
|
|
9
8
|
import * as path from 'node:path';
|
|
10
|
-
import { spawn } from 'node:child_process';
|
|
11
9
|
import chalk from 'chalk';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { runPhaseChecks, formatCheckResults } from './checks.js';
|
|
17
|
-
import { runConsultationLoop, formatConsultationResults, hasConsultation, } from './consultation.js';
|
|
18
|
-
import { loadProtocol as loadProtocolFromLoader, listProtocols as listProtocolsFromLoader, } from './protocol-loader.js';
|
|
19
|
-
import { createNotifier, } from './notifications.js';
|
|
10
|
+
import { readState, writeState, createInitialState, findStatusPath, getStatusPath, detectProjectId, } from './state.js';
|
|
11
|
+
import { loadProtocol, getPhaseConfig, getNextPhase, getPhaseChecks, getPhaseGate, isPhased, getPhaseCompletionChecks, } from './protocol.js';
|
|
12
|
+
import { findPlanFile, extractPhasesFromFile, getCurrentPlanPhase, getPhaseContent, advancePlanPhase, allPlanPhasesComplete, } from './plan.js';
|
|
13
|
+
import { runPhaseChecks, formatCheckResults, allChecksPassed, } from './checks.js';
|
|
20
14
|
// ============================================================================
|
|
21
|
-
//
|
|
15
|
+
// Output Helpers
|
|
22
16
|
// ============================================================================
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
export function listProtocols(projectRoot) {
|
|
28
|
-
const root = projectRoot || findProjectRoot();
|
|
29
|
-
return listProtocolsFromLoader(root);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Load a protocol definition
|
|
33
|
-
* Delegates to protocol-loader.ts which properly converts steps→substates
|
|
34
|
-
*/
|
|
35
|
-
export function loadProtocol(name, projectRoot) {
|
|
36
|
-
const root = projectRoot || findProjectRoot();
|
|
37
|
-
const protocol = loadProtocolFromLoader(root, name);
|
|
38
|
-
if (!protocol) {
|
|
39
|
-
throw new Error(`Protocol not found: ${name}\nAvailable protocols: ${listProtocols(root).join(', ')}`);
|
|
40
|
-
}
|
|
41
|
-
return protocol;
|
|
17
|
+
function header(text) {
|
|
18
|
+
const line = '═'.repeat(50);
|
|
19
|
+
return `${line}\n ${text}\n${line}`;
|
|
42
20
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*/
|
|
46
|
-
function loadPrompt(protocol, phaseId, projectRoot) {
|
|
47
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
48
|
-
if (!phase?.prompt) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
// New structure: protocols/<protocol>/prompts/<prompt>.md
|
|
52
|
-
const promptPaths = [
|
|
53
|
-
path.join(projectRoot, 'codev', 'protocols', protocol.name, 'prompts', phase.prompt),
|
|
54
|
-
path.join(getSkeletonDir(), 'protocols', protocol.name, 'prompts', phase.prompt),
|
|
55
|
-
// Legacy paths
|
|
56
|
-
path.join(projectRoot, 'codev', 'porch', 'prompts', phase.prompt),
|
|
57
|
-
path.join(getSkeletonDir(), 'porch', 'prompts', phase.prompt),
|
|
58
|
-
];
|
|
59
|
-
for (const promptPath of promptPaths) {
|
|
60
|
-
if (fs.existsSync(promptPath)) {
|
|
61
|
-
return fs.readFileSync(promptPath, 'utf-8');
|
|
62
|
-
}
|
|
63
|
-
// Try with .md extension
|
|
64
|
-
if (fs.existsSync(`${promptPath}.md`)) {
|
|
65
|
-
return fs.readFileSync(`${promptPath}.md`, 'utf-8');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
21
|
+
function section(title, content) {
|
|
22
|
+
return `\n${chalk.bold(title)}:\n${content}`;
|
|
69
23
|
}
|
|
70
24
|
// ============================================================================
|
|
71
|
-
//
|
|
25
|
+
// Commands
|
|
72
26
|
// ============================================================================
|
|
73
27
|
/**
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
function isTerminalPhase(protocol, phaseId) {
|
|
77
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
78
|
-
return phase?.terminal === true;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Find the phase that has a gate blocking after the given state
|
|
82
|
-
*/
|
|
83
|
-
function getGateForState(protocol, state) {
|
|
84
|
-
const [phaseId, substate] = state.split(':');
|
|
85
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
86
|
-
if (phase?.gate && phase.gate.after === substate) {
|
|
87
|
-
const gateId = `${phaseId}_approval`;
|
|
88
|
-
return { gateId, phase };
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Get next state after gate passes
|
|
94
|
-
*/
|
|
95
|
-
function getGateNextState(protocol, phaseId) {
|
|
96
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
97
|
-
return phase?.gate?.next || null;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Get signal-based next state
|
|
101
|
-
*/
|
|
102
|
-
function getSignalNextState(protocol, phaseId, signal) {
|
|
103
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
104
|
-
return phase?.signals?.[signal] || null;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Get the default next state for a phase (first substate or next phase)
|
|
28
|
+
* porch status <id>
|
|
29
|
+
* Shows current state and prescriptive next steps.
|
|
108
30
|
*/
|
|
109
|
-
function
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return null;
|
|
114
|
-
// If phase has substates, move to next substate
|
|
115
|
-
if (phase.substates && substate) {
|
|
116
|
-
const currentIdx = phase.substates.indexOf(substate);
|
|
117
|
-
if (currentIdx >= 0 && currentIdx < phase.substates.length - 1) {
|
|
118
|
-
return `${phaseId}:${phase.substates[currentIdx + 1]}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// Move to next phase
|
|
122
|
-
const phaseIdx = protocol.phases.findIndex(p => p.id === phaseId);
|
|
123
|
-
if (phaseIdx >= 0 && phaseIdx < protocol.phases.length - 1) {
|
|
124
|
-
const nextPhase = protocol.phases[phaseIdx + 1];
|
|
125
|
-
if (nextPhase.substates && nextPhase.substates.length > 0) {
|
|
126
|
-
return `${nextPhase.id}:${nextPhase.substates[0]}`;
|
|
127
|
-
}
|
|
128
|
-
return nextPhase.id;
|
|
31
|
+
export async function status(projectRoot, projectId) {
|
|
32
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
33
|
+
if (!statusPath) {
|
|
34
|
+
throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
|
|
129
35
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
console.log(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
state
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
`;
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
const args = ['--print', '-p', fullPrompt, '--dangerously-skip-permissions'];
|
|
182
|
-
const proc = spawn('claude', args, {
|
|
183
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
184
|
-
timeout,
|
|
185
|
-
});
|
|
186
|
-
let output = '';
|
|
187
|
-
let stderr = '';
|
|
188
|
-
proc.stdout.on('data', (data) => {
|
|
189
|
-
output += data.toString();
|
|
190
|
-
process.stdout.write(data);
|
|
191
|
-
});
|
|
192
|
-
proc.stderr.on('data', (data) => {
|
|
193
|
-
stderr += data.toString();
|
|
194
|
-
process.stderr.write(data);
|
|
195
|
-
});
|
|
196
|
-
proc.on('close', (code) => {
|
|
197
|
-
if (code !== 0) {
|
|
198
|
-
reject(new Error(`Claude exited with code ${code}\n${stderr}`));
|
|
36
|
+
const state = readState(statusPath);
|
|
37
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
38
|
+
const phaseConfig = getPhaseConfig(protocol, state.phase);
|
|
39
|
+
// Header
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(header(`PROJECT: ${state.id} - ${state.title}`));
|
|
42
|
+
console.log(` PROTOCOL: ${state.protocol}`);
|
|
43
|
+
console.log(` PHASE: ${state.phase} (${phaseConfig?.name || 'unknown'})`);
|
|
44
|
+
// For phased protocols, show plan phase status
|
|
45
|
+
if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk.bold('PLAN PHASES:'));
|
|
48
|
+
console.log('');
|
|
49
|
+
// Status icons
|
|
50
|
+
const icon = (status) => {
|
|
51
|
+
switch (status) {
|
|
52
|
+
case 'complete': return chalk.green('✓');
|
|
53
|
+
case 'in_progress': return chalk.yellow('►');
|
|
54
|
+
default: return chalk.gray('○');
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// Show phases
|
|
58
|
+
for (const phase of state.plan_phases) {
|
|
59
|
+
const isCurrent = phase.status === 'in_progress';
|
|
60
|
+
const prefix = isCurrent ? chalk.cyan('→ ') : ' ';
|
|
61
|
+
const title = isCurrent ? chalk.bold(phase.title) : phase.title;
|
|
62
|
+
console.log(`${prefix}${icon(phase.status)} ${phase.id}: ${title}`);
|
|
63
|
+
}
|
|
64
|
+
const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
|
|
65
|
+
if (currentPlanPhase) {
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
68
|
+
// Show phase content from plan
|
|
69
|
+
const planPath = findPlanFile(projectRoot, state.id, state.title);
|
|
70
|
+
if (planPath) {
|
|
71
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
72
|
+
const phaseContent = getPhaseContent(content, currentPlanPhase.id);
|
|
73
|
+
if (phaseContent) {
|
|
74
|
+
console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Find the next phase name for the warning
|
|
78
|
+
const currentIdx = state.plan_phases.findIndex(p => p.id === currentPlanPhase.id);
|
|
79
|
+
const nextPlanPhase = state.plan_phases[currentIdx + 1];
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
|
|
82
|
+
console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
|
|
83
|
+
if (nextPlanPhase) {
|
|
84
|
+
console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
|
|
199
85
|
}
|
|
200
86
|
else {
|
|
201
|
-
|
|
87
|
+
console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
|
|
202
88
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
// ============================================================================
|
|
208
|
-
// Commands
|
|
209
|
-
// ============================================================================
|
|
210
|
-
/**
|
|
211
|
-
* Initialize a new project with a protocol
|
|
212
|
-
*/
|
|
213
|
-
export async function init(protocolName, projectId, projectName, options = {}) {
|
|
214
|
-
const projectRoot = findProjectRoot();
|
|
215
|
-
const protocol = loadProtocol(protocolName, projectRoot);
|
|
216
|
-
// Create project directory
|
|
217
|
-
const projectDir = getProjectDir(projectRoot, projectId, projectName);
|
|
218
|
-
fs.mkdirSync(projectDir, { recursive: true });
|
|
219
|
-
// Create initial state
|
|
220
|
-
const state = createInitialState(protocol, projectId, projectName, options.worktree);
|
|
221
|
-
const statusPath = path.join(projectDir, 'status.yaml');
|
|
222
|
-
await writeState(statusPath, state);
|
|
223
|
-
console.log(chalk.green(`[porch] Initialized project ${projectId} with protocol ${protocolName}`));
|
|
224
|
-
console.log(chalk.blue(`[porch] Project directory: ${projectDir}`));
|
|
225
|
-
console.log(chalk.blue(`[porch] Initial state: ${state.current_state}`));
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Check if a protocol phase is a "phased" phase (runs per plan-phase)
|
|
229
|
-
*/
|
|
230
|
-
function isPhasedPhase(protocol, phaseId) {
|
|
231
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
232
|
-
return phase?.phased === true;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Get the IDE phases (implement, defend, evaluate) that run per plan-phase
|
|
236
|
-
*/
|
|
237
|
-
function getIDEPhases(protocol) {
|
|
238
|
-
return protocol.phases
|
|
239
|
-
.filter(p => p.phased === true)
|
|
240
|
-
.map(p => p.id);
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Parse the current plan-phase from state like "implement:phase_1"
|
|
244
|
-
*/
|
|
245
|
-
function parsePlanPhaseFromState(state) {
|
|
246
|
-
const parts = state.split(':');
|
|
247
|
-
const phaseId = parts[0];
|
|
248
|
-
// Check if second part is a plan phase (phase_N) or a substate
|
|
249
|
-
if (parts.length > 1) {
|
|
250
|
-
if (parts[1].startsWith('phase_')) {
|
|
251
|
-
return { phaseId, planPhaseId: parts[1], substate: parts[2] || null };
|
|
89
|
+
console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
|
|
90
|
+
console.log(chalk.red.bold('║ 3. After completing this phase, run: porch done ' + state.id.padEnd(12) + '║'));
|
|
91
|
+
console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
|
|
252
92
|
}
|
|
253
|
-
return { phaseId, planPhaseId: null, substate: parts[1] };
|
|
254
93
|
}
|
|
255
|
-
|
|
94
|
+
// Show checks status
|
|
95
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
96
|
+
if (Object.keys(checks).length > 0) {
|
|
97
|
+
const checkLines = Object.keys(checks).map(name => ` ○ ${name} (not yet run)`);
|
|
98
|
+
console.log(section('CRITERIA', checkLines.join('\n')));
|
|
99
|
+
}
|
|
100
|
+
// Instructions
|
|
101
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
102
|
+
if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
|
|
103
|
+
console.log(section('STATUS', chalk.yellow('WAITING FOR HUMAN APPROVAL')));
|
|
104
|
+
console.log(`\n Gate: ${gate}`);
|
|
105
|
+
console.log(' Do not proceed until gate is approved.');
|
|
106
|
+
console.log(`\n To approve: porch approve ${state.id} ${gate}`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log(section('INSTRUCTIONS', getInstructions(state, protocol)));
|
|
110
|
+
}
|
|
111
|
+
console.log(section('NEXT ACTION', getNextAction(state, protocol)));
|
|
112
|
+
console.log('');
|
|
256
113
|
}
|
|
257
114
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
115
|
+
* porch check <id>
|
|
116
|
+
* Runs the phase checks and reports results.
|
|
260
117
|
*/
|
|
261
|
-
function
|
|
262
|
-
const
|
|
263
|
-
if (!
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
118
|
+
export async function check(projectRoot, projectId) {
|
|
119
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
120
|
+
if (!statusPath) {
|
|
121
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
122
|
+
}
|
|
123
|
+
const state = readState(statusPath);
|
|
124
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
125
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
126
|
+
if (Object.keys(checks).length === 0) {
|
|
127
|
+
console.log(chalk.dim('No checks defined for this phase.'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
133
|
+
console.log('');
|
|
134
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
135
|
+
console.log(formatCheckResults(results));
|
|
136
|
+
console.log('');
|
|
137
|
+
if (allChecksPassed(results)) {
|
|
138
|
+
console.log(chalk.green('RESULT: ALL CHECKS PASSED'));
|
|
139
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(chalk.red('RESULT: CHECKS FAILED'));
|
|
143
|
+
console.log(`\n Fix the failures and run: porch check ${state.id}`);
|
|
144
|
+
}
|
|
145
|
+
console.log('');
|
|
288
146
|
}
|
|
289
147
|
/**
|
|
290
|
-
*
|
|
148
|
+
* porch done <id>
|
|
149
|
+
* Advances to next phase if checks pass. Refuses if checks fail.
|
|
291
150
|
*/
|
|
292
|
-
export async function
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (!statusFilePath) {
|
|
297
|
-
throw new Error(`Status file not found for project: ${projectId}\n` +
|
|
298
|
-
`Run: porch init <protocol> ${projectId} <project-name>`);
|
|
299
|
-
}
|
|
300
|
-
// Read state and load protocol
|
|
301
|
-
const state = readState(statusFilePath);
|
|
302
|
-
if (!state) {
|
|
303
|
-
throw new Error(`Could not read state from: ${statusFilePath}`);
|
|
304
|
-
}
|
|
305
|
-
const protocol = loadProtocol(state.protocol, projectRoot);
|
|
306
|
-
const pollInterval = options.pollInterval || protocol.config?.poll_interval || 30;
|
|
307
|
-
const maxIterations = protocol.config?.max_iterations || 100;
|
|
308
|
-
// Create notifier for this project (desktop notifications for important events)
|
|
309
|
-
const notifier = createNotifier(projectId, { desktop: true });
|
|
310
|
-
console.log(chalk.blue(`[porch] Starting ${state.protocol} loop for project ${projectId}`));
|
|
311
|
-
console.log(chalk.blue(`[porch] Status file: ${statusFilePath}`));
|
|
312
|
-
console.log(chalk.blue(`[porch] Poll interval: ${pollInterval}s`));
|
|
313
|
-
let currentState = state;
|
|
314
|
-
// Extract plan phases if not already done and we're past planning
|
|
315
|
-
if (!currentState.plan_phases || currentState.plan_phases.length === 0) {
|
|
316
|
-
const planFile = findPlanFile(projectRoot, projectId, currentState.title);
|
|
317
|
-
if (planFile) {
|
|
318
|
-
try {
|
|
319
|
-
const planPhases = extractPhasesFromPlanFile(planFile);
|
|
320
|
-
currentState = setPlanPhases(currentState, planPhases);
|
|
321
|
-
await writeState(statusFilePath, currentState);
|
|
322
|
-
console.log(chalk.blue(`[porch] Extracted ${planPhases.length} phases from plan`));
|
|
323
|
-
for (const phase of planPhases) {
|
|
324
|
-
console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
catch (e) {
|
|
328
|
-
console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
|
|
329
|
-
}
|
|
330
|
-
}
|
|
151
|
+
export async function done(projectRoot, projectId) {
|
|
152
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
153
|
+
if (!statusPath) {
|
|
154
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
331
155
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
console.log(
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const planPhases = extractPhasesFromPlanFile(planFile);
|
|
348
|
-
currentState = setPlanPhases(currentState, planPhases);
|
|
349
|
-
await writeState(statusFilePath, currentState);
|
|
350
|
-
console.log(chalk.blue(`[porch] Late discovery: Extracted ${planPhases.length} phases from plan`));
|
|
351
|
-
for (const phase of planPhases) {
|
|
352
|
-
console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch (e) {
|
|
356
|
-
console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
console.log(chalk.yellow(`[porch] Warning: Entering phased phase '${phaseId}' but no plan file found`));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// Check if terminal phase
|
|
364
|
-
if (isTerminalPhase(protocol, phaseId)) {
|
|
365
|
-
console.log(chalk.green('━'.repeat(40)));
|
|
366
|
-
console.log(chalk.green(`[porch] ${state.protocol} loop COMPLETE`));
|
|
367
|
-
console.log(chalk.green(`[porch] Project ${projectId} finished all phases`));
|
|
368
|
-
console.log(chalk.green('━'.repeat(40)));
|
|
369
|
-
return;
|
|
156
|
+
let state = readState(statusPath);
|
|
157
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
158
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
159
|
+
// Run checks first
|
|
160
|
+
if (Object.keys(checks).length > 0) {
|
|
161
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
162
|
+
console.log('');
|
|
163
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
164
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
165
|
+
console.log(formatCheckResults(results));
|
|
166
|
+
if (!allChecksPassed(results)) {
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(chalk.red('CHECKS FAILED. Cannot advance.'));
|
|
169
|
+
console.log(`\n Fix the failures and try again.`);
|
|
170
|
+
process.exit(1);
|
|
370
171
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
172
|
+
}
|
|
173
|
+
// Check for gate
|
|
174
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
175
|
+
if (gate && state.gates[gate]?.status !== 'approved') {
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
|
|
178
|
+
console.log(`\n Run: porch gate ${state.id}`);
|
|
179
|
+
console.log(' Wait for human approval before advancing.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Handle phased protocols (plan phases with checks at completion)
|
|
183
|
+
if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
|
|
184
|
+
const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
|
|
185
|
+
if (currentPlanPhase && !allPlanPhasesComplete(state.plan_phases)) {
|
|
186
|
+
// Run phase completion checks (implement + defend + evaluate all at once)
|
|
187
|
+
const completionChecks = getPhaseCompletionChecks(protocol);
|
|
188
|
+
if (Object.keys(completionChecks).length > 0) {
|
|
189
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
190
|
+
console.log('');
|
|
191
|
+
console.log(chalk.bold(`RUNNING PHASE COMPLETION CHECKS (${currentPlanPhase.id})...`));
|
|
192
|
+
const results = await runPhaseChecks(completionChecks, projectRoot, checkEnv);
|
|
193
|
+
console.log(formatCheckResults(results));
|
|
194
|
+
if (!allChecksPassed(results)) {
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log(chalk.red('PHASE COMPLETION CHECKS FAILED. Cannot advance.'));
|
|
197
|
+
console.log(`\n Ensure your commit includes:`);
|
|
198
|
+
console.log(` - Implementation code`);
|
|
199
|
+
console.log(` - Tests`);
|
|
200
|
+
console.log(` - 3-way review results in commit message`);
|
|
201
|
+
console.log(`\n Then try again.`);
|
|
202
|
+
process.exit(1);
|
|
395
203
|
}
|
|
396
204
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (planPhaseId && currentState.plan_phases) {
|
|
411
|
-
const planPhase = currentState.plan_phases.find(p => p.id === planPhaseId);
|
|
412
|
-
if (planPhase) {
|
|
413
|
-
console.log(chalk.cyan(`[phase] IDE Phase: ${phaseId} | Plan Phase: ${planPhase.title}`));
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
|
|
421
|
-
}
|
|
422
|
-
// Notify phase start
|
|
423
|
-
await notifier.phaseStart(phaseId);
|
|
424
|
-
// Execute phase
|
|
425
|
-
const output = await invokeClaude(protocol, phaseId, currentState, statusFilePath, projectRoot, options);
|
|
426
|
-
const signal = extractSignal(output);
|
|
427
|
-
// Run phase checks (build/test) if defined
|
|
428
|
-
if (phase?.checks && !options.dryRun) {
|
|
429
|
-
console.log(chalk.blue(`[porch] Running checks for phase ${phaseId}...`));
|
|
430
|
-
const checkResult = await runPhaseChecks(phase, {
|
|
431
|
-
cwd: projectRoot,
|
|
432
|
-
dryRun: options.dryRun,
|
|
433
|
-
});
|
|
434
|
-
console.log(formatCheckResults(checkResult));
|
|
435
|
-
if (!checkResult.success) {
|
|
436
|
-
// Notify about check failure
|
|
437
|
-
const failedCheck = checkResult.checks.find(c => !c.success);
|
|
438
|
-
await notifier.checkFailed(phaseId, failedCheck?.name || 'build/test', failedCheck?.error || 'Check failed');
|
|
439
|
-
// If checks fail, handle based on check configuration
|
|
440
|
-
if (checkResult.returnTo) {
|
|
441
|
-
console.log(chalk.yellow(`[porch] Checks failed, returning to ${checkResult.returnTo}`));
|
|
442
|
-
if (planPhaseId) {
|
|
443
|
-
currentState = updateState(currentState, `${checkResult.returnTo}:${planPhaseId}`);
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
currentState = updateState(currentState, checkResult.returnTo);
|
|
447
|
-
}
|
|
448
|
-
await writeState(statusFilePath, currentState);
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
// No returnTo means we should retry (already handled in checks)
|
|
205
|
+
// Advance to next plan phase
|
|
206
|
+
const { phases: updatedPhases, moveToReview } = advancePlanPhase(state.plan_phases, currentPlanPhase.id);
|
|
207
|
+
state.plan_phases = updatedPhases;
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(chalk.green(`PLAN PHASE COMPLETE: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
210
|
+
// Check if moving to review (all plan phases done)
|
|
211
|
+
if (moveToReview) {
|
|
212
|
+
state.phase = 'review';
|
|
213
|
+
state.current_plan_phase = null;
|
|
214
|
+
writeState(statusPath, state);
|
|
215
|
+
console.log(chalk.cyan('All plan phases complete. Moving to REVIEW phase.'));
|
|
216
|
+
console.log(`\n Run: porch status ${state.id}`);
|
|
217
|
+
return;
|
|
452
218
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (consultConfig.on === currentSubstate || consultConfig.on === phaseId) {
|
|
460
|
-
const maxRounds = consultConfig.max_rounds || 3;
|
|
461
|
-
const stateKey = currentState.current_state;
|
|
462
|
-
// Get attempt count from state (persisted across porch iterations)
|
|
463
|
-
const attemptCount = getConsultationAttempts(currentState, stateKey) + 1;
|
|
464
|
-
console.log(chalk.blue(`[porch] Consultation triggered for phase ${phaseId} (attempt ${attemptCount}/${maxRounds})`));
|
|
465
|
-
await notifier.consultationStart(phaseId, consultConfig.models || ['gemini', 'codex', 'claude']);
|
|
466
|
-
const consultResult = await runConsultationLoop(consultConfig, {
|
|
467
|
-
subcommand: consultConfig.type.includes('pr') ? 'pr' : consultConfig.type.includes('spec') ? 'spec' : 'plan',
|
|
468
|
-
identifier: projectId,
|
|
469
|
-
cwd: projectRoot,
|
|
470
|
-
timeout: protocol.config?.consultation_timeout,
|
|
471
|
-
dryRun: options.dryRun,
|
|
472
|
-
});
|
|
473
|
-
console.log(formatConsultationResults(consultResult));
|
|
474
|
-
await notifier.consultationComplete(phaseId, consultResult.feedback, consultResult.allApproved);
|
|
475
|
-
// If not all approved, track attempt and check for escalation
|
|
476
|
-
if (!consultResult.allApproved) {
|
|
477
|
-
// Increment attempt count in state (persists across iterations)
|
|
478
|
-
currentState = incrementConsultationAttempts(currentState, stateKey);
|
|
479
|
-
await writeState(statusFilePath, currentState);
|
|
480
|
-
// Check if we've reached max attempts
|
|
481
|
-
if (attemptCount >= maxRounds) {
|
|
482
|
-
// Create escalation gate - requires human intervention
|
|
483
|
-
const escalationGateId = `${phaseId}_consultation_escalation`;
|
|
484
|
-
// Check if escalation gate was already approved (human override)
|
|
485
|
-
if (currentState.gates[escalationGateId]?.status === 'passed') {
|
|
486
|
-
console.log(chalk.green(`[porch] Consultation escalation gate already approved, continuing`));
|
|
487
|
-
// Reset attempts and fall through to next state handling
|
|
488
|
-
currentState = resetConsultationAttempts(currentState, stateKey);
|
|
489
|
-
await writeState(statusFilePath, currentState);
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
console.log(chalk.red(`[porch] Consultation failed after ${attemptCount} attempts - escalating to human`));
|
|
493
|
-
console.log(chalk.yellow(`[porch] To override and continue: porch approve ${projectId} ${escalationGateId}`));
|
|
494
|
-
// Request human gate if not already requested
|
|
495
|
-
if (!currentState.gates[escalationGateId]?.requested_at) {
|
|
496
|
-
currentState = requestGateApproval(currentState, escalationGateId);
|
|
497
|
-
await writeState(statusFilePath, currentState);
|
|
498
|
-
await notifier.gatePending(phaseId, escalationGateId);
|
|
499
|
-
}
|
|
500
|
-
// Wait for human approval
|
|
501
|
-
await new Promise(r => setTimeout(r, pollInterval * 1000));
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
console.log(chalk.yellow(`[porch] Consultation requested changes (attempt ${attemptCount}/${maxRounds}), continuing for revision`));
|
|
507
|
-
// Stay in same state for Claude to revise on next iteration
|
|
508
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
// All approved - reset attempt counter
|
|
514
|
-
currentState = resetConsultationAttempts(currentState, stateKey);
|
|
515
|
-
await writeState(statusFilePath, currentState);
|
|
516
|
-
}
|
|
517
|
-
// All approved (or escalation gate passed) - use consultation's next state if defined
|
|
518
|
-
if (consultConfig.next) {
|
|
519
|
-
if (planPhaseId) {
|
|
520
|
-
currentState = updateState(currentState, `${consultConfig.next}:${planPhaseId}`);
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
currentState = updateState(currentState, consultConfig.next);
|
|
524
|
-
}
|
|
525
|
-
await writeState(statusFilePath, currentState);
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
219
|
+
// Update current plan phase tracker
|
|
220
|
+
const newCurrentPhase = getCurrentPlanPhase(state.plan_phases);
|
|
221
|
+
state.current_plan_phase = newCurrentPhase?.id || null;
|
|
222
|
+
writeState(statusPath, state);
|
|
223
|
+
if (newCurrentPhase) {
|
|
224
|
+
console.log(chalk.cyan(`NEXT: ${newCurrentPhase.id} - ${newCurrentPhase.title}`));
|
|
528
225
|
}
|
|
226
|
+
console.log(`\n Run: porch status ${state.id}`);
|
|
227
|
+
return;
|
|
529
228
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
229
|
+
}
|
|
230
|
+
// Advance to next protocol phase
|
|
231
|
+
advanceProtocolPhase(state, protocol, statusPath);
|
|
232
|
+
}
|
|
233
|
+
function advanceProtocolPhase(state, protocol, statusPath) {
|
|
234
|
+
const nextPhase = getNextPhase(protocol, state.phase);
|
|
235
|
+
if (!nextPhase) {
|
|
236
|
+
console.log('');
|
|
237
|
+
console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
|
|
238
|
+
console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
state.phase = nextPhase.id;
|
|
242
|
+
// If entering a phased phase (implement), extract plan phases
|
|
243
|
+
if (isPhased(protocol, nextPhase.id)) {
|
|
244
|
+
const planPath = findPlanFile(process.cwd(), state.id, state.title);
|
|
245
|
+
if (planPath) {
|
|
246
|
+
state.plan_phases = extractPhasesFromFile(planPath);
|
|
247
|
+
// extractPhasesFromFile already marks first phase as in_progress
|
|
248
|
+
if (state.plan_phases.length > 0) {
|
|
249
|
+
state.current_plan_phase = state.plan_phases[0].id;
|
|
548
250
|
}
|
|
549
251
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
// Use default transition
|
|
573
|
-
nextState = getDefaultNextState(protocol, currentState.current_state);
|
|
252
|
+
}
|
|
253
|
+
writeState(statusPath, state);
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
|
|
256
|
+
// If we just entered implement phase, show phase 1 info and the critical warning
|
|
257
|
+
if (isPhased(protocol, nextPhase.id) && state.plan_phases.length > 0) {
|
|
258
|
+
const firstPhase = state.plan_phases[0];
|
|
259
|
+
const nextPlanPhase = state.plan_phases[1];
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
|
|
262
|
+
// Show phase content from plan
|
|
263
|
+
const planPath = findPlanFile(process.cwd(), state.id, state.title);
|
|
264
|
+
if (planPath) {
|
|
265
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
266
|
+
const phaseContent = getPhaseContent(content, firstPhase.id);
|
|
267
|
+
if (phaseContent) {
|
|
268
|
+
console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
|
|
574
269
|
}
|
|
575
270
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
|
|
273
|
+
console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
|
|
274
|
+
if (nextPlanPhase) {
|
|
275
|
+
console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
|
|
579
276
|
}
|
|
580
277
|
else {
|
|
581
|
-
console.log(chalk.
|
|
278
|
+
console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
|
|
582
279
|
}
|
|
583
|
-
|
|
280
|
+
console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
|
|
281
|
+
console.log(chalk.red.bold('║ 3. When phase complete, run: porch done ' + state.id.padEnd(20) + '║'));
|
|
282
|
+
console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
|
|
584
283
|
}
|
|
585
|
-
|
|
284
|
+
console.log(`\n Run: porch status ${state.id}`);
|
|
586
285
|
}
|
|
587
286
|
/**
|
|
588
|
-
*
|
|
287
|
+
* porch gate <id>
|
|
288
|
+
* Requests human approval for current gate.
|
|
589
289
|
*/
|
|
590
|
-
export async function
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
throw new Error(`Status file not found for project: ${projectId}`);
|
|
595
|
-
}
|
|
596
|
-
const state = readState(statusFilePath);
|
|
597
|
-
if (!state) {
|
|
598
|
-
throw new Error(`Could not read state from: ${statusFilePath}`);
|
|
599
|
-
}
|
|
600
|
-
const updatedState = approveGate(state, gateId);
|
|
601
|
-
await writeState(statusFilePath, updatedState);
|
|
602
|
-
console.log(chalk.green(`[porch] Approved: ${gateId}`));
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Show project status
|
|
606
|
-
*/
|
|
607
|
-
export async function status(projectId) {
|
|
608
|
-
const projectRoot = findProjectRoot();
|
|
609
|
-
if (projectId) {
|
|
610
|
-
// Show specific project
|
|
611
|
-
const statusFilePath = findStatusFile(projectRoot, projectId);
|
|
612
|
-
if (!statusFilePath) {
|
|
613
|
-
throw new Error(`Status file not found for project: ${projectId}`);
|
|
614
|
-
}
|
|
615
|
-
const state = readState(statusFilePath);
|
|
616
|
-
if (!state) {
|
|
617
|
-
throw new Error(`Could not read state from: ${statusFilePath}`);
|
|
618
|
-
}
|
|
619
|
-
console.log(chalk.blue(`[porch] Status for project ${projectId}:`));
|
|
620
|
-
console.log('');
|
|
621
|
-
console.log(` ID: ${state.id}`);
|
|
622
|
-
console.log(` Title: ${state.title}`);
|
|
623
|
-
console.log(` Protocol: ${state.protocol}`);
|
|
624
|
-
console.log(` State: ${state.current_state}`);
|
|
625
|
-
console.log(` Iteration: ${state.iteration}`);
|
|
626
|
-
console.log(` Started: ${state.started_at}`);
|
|
627
|
-
console.log(` Updated: ${state.last_updated}`);
|
|
628
|
-
console.log('');
|
|
629
|
-
if (Object.keys(state.gates).length > 0) {
|
|
630
|
-
console.log(' Gates:');
|
|
631
|
-
for (const [gateId, gateStatus] of Object.entries(state.gates)) {
|
|
632
|
-
const icon = gateStatus.status === 'passed' ? '✓' : gateStatus.status === 'failed' ? '✗' : '⏳';
|
|
633
|
-
console.log(` ${icon} ${gateId}: ${gateStatus.status}`);
|
|
634
|
-
}
|
|
635
|
-
console.log('');
|
|
636
|
-
}
|
|
637
|
-
if (state.plan_phases && state.plan_phases.length > 0) {
|
|
638
|
-
console.log(' Plan Phases:');
|
|
639
|
-
for (const phase of state.plan_phases) {
|
|
640
|
-
const phaseStatus = state.phases[phase.id]?.status || 'pending';
|
|
641
|
-
const icon = phaseStatus === 'complete' ? '✓' : phaseStatus === 'in_progress' ? '🔄' : '○';
|
|
642
|
-
console.log(` ${icon} ${phase.id}: ${phase.title}`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
290
|
+
export async function gate(projectRoot, projectId) {
|
|
291
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
292
|
+
if (!statusPath) {
|
|
293
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
645
294
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
295
|
+
const state = readState(statusPath);
|
|
296
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
297
|
+
const gateName = getPhaseGate(protocol, state.phase);
|
|
298
|
+
if (!gateName) {
|
|
299
|
+
console.log(chalk.dim('No gate required for this phase.'));
|
|
300
|
+
console.log(`\n Run: porch done ${state.id}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Mark gate as requested
|
|
304
|
+
if (!state.gates[gateName]) {
|
|
305
|
+
state.gates[gateName] = { status: 'pending' };
|
|
306
|
+
}
|
|
307
|
+
if (!state.gates[gateName].requested_at) {
|
|
308
|
+
state.gates[gateName].requested_at = new Date().toISOString();
|
|
309
|
+
writeState(statusPath, state);
|
|
310
|
+
}
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(chalk.bold(`GATE: ${gateName}`));
|
|
313
|
+
console.log('');
|
|
314
|
+
// Show relevant artifact and open it for review
|
|
315
|
+
const artifact = getArtifactForPhase(projectRoot, state);
|
|
316
|
+
if (artifact) {
|
|
317
|
+
const fullPath = path.join(projectRoot, artifact);
|
|
318
|
+
if (fs.existsSync(fullPath)) {
|
|
319
|
+
console.log(` Artifact: ${artifact}`);
|
|
666
320
|
console.log('');
|
|
667
|
-
console.log(chalk.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
321
|
+
console.log(chalk.cyan(' Opening artifact for human review...'));
|
|
322
|
+
// Use af open to display in annotation viewer
|
|
323
|
+
const { spawn } = await import('node:child_process');
|
|
324
|
+
spawn('af', ['open', fullPath], {
|
|
325
|
+
stdio: 'inherit',
|
|
326
|
+
detached: true
|
|
327
|
+
}).unref();
|
|
674
328
|
}
|
|
675
329
|
}
|
|
330
|
+
console.log('');
|
|
331
|
+
console.log(chalk.yellow(' Human approval required. STOP and wait.'));
|
|
332
|
+
console.log(' Do not proceed until gate is approved.');
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(chalk.bold('STATUS: WAITING FOR HUMAN APPROVAL'));
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(chalk.dim(` To approve: porch approve ${state.id} ${gateName}`));
|
|
337
|
+
console.log('');
|
|
676
338
|
}
|
|
677
339
|
/**
|
|
678
|
-
*
|
|
340
|
+
* porch approve <id> <gate> --a-human-explicitly-approved-this
|
|
341
|
+
* Human approves a gate. Requires explicit flag to prevent automated approvals.
|
|
679
342
|
*/
|
|
680
|
-
export async function
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
343
|
+
export async function approve(projectRoot, projectId, gateName, hasHumanFlag) {
|
|
344
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
345
|
+
if (!statusPath) {
|
|
346
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
347
|
+
}
|
|
348
|
+
const state = readState(statusPath);
|
|
349
|
+
if (!state.gates[gateName]) {
|
|
350
|
+
const knownGates = Object.keys(state.gates).join(', ');
|
|
351
|
+
throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
|
|
352
|
+
}
|
|
353
|
+
if (state.gates[gateName].status === 'approved') {
|
|
354
|
+
console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
|
|
685
355
|
return;
|
|
686
356
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
357
|
+
// Require explicit human flag
|
|
358
|
+
if (!hasHumanFlag) {
|
|
359
|
+
console.log('');
|
|
360
|
+
console.log(chalk.red('ERROR: Human approval required.'));
|
|
361
|
+
console.log('');
|
|
362
|
+
console.log(' To approve, please run:');
|
|
363
|
+
console.log('');
|
|
364
|
+
console.log(chalk.cyan(` porch approve ${projectId} ${gateName} --a-human-explicitly-approved-this`));
|
|
365
|
+
console.log('');
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
// Run phase checks before approving
|
|
369
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
370
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
371
|
+
if (Object.keys(checks).length > 0) {
|
|
372
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
373
|
+
console.log('');
|
|
374
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
375
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
376
|
+
console.log(formatCheckResults(results));
|
|
377
|
+
if (!allChecksPassed(results)) {
|
|
378
|
+
console.log('');
|
|
379
|
+
console.log(chalk.red('CHECKS FAILED. Cannot approve gate.'));
|
|
380
|
+
console.log(`\n Fix the failures and try again.`);
|
|
381
|
+
process.exit(1);
|
|
695
382
|
}
|
|
696
383
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const protocol = loadProtocol(protocolName, projectRoot);
|
|
704
|
-
console.log(chalk.blue(`[porch] Protocol: ${protocolName}`));
|
|
384
|
+
state.gates[gateName].status = 'approved';
|
|
385
|
+
state.gates[gateName].approved_at = new Date().toISOString();
|
|
386
|
+
writeState(statusPath, state);
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(chalk.green(`Gate ${gateName} approved.`));
|
|
389
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
705
390
|
console.log('');
|
|
706
|
-
console.log(JSON.stringify(protocol, null, 2));
|
|
707
391
|
}
|
|
708
392
|
/**
|
|
709
|
-
*
|
|
393
|
+
* porch init <protocol> <id> <name>
|
|
394
|
+
* Initialize a new project.
|
|
710
395
|
*/
|
|
711
|
-
export async function
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
console.log(chalk.yellow('[porch] Pending gates:'));
|
|
719
|
-
for (const gate of gates) {
|
|
720
|
-
const requestedAt = gate.requestedAt ? ` (requested ${gate.requestedAt})` : '';
|
|
721
|
-
console.log(` ${gate.projectId}: ${gate.gateId}${requestedAt}`);
|
|
722
|
-
console.log(` → porch approve ${gate.projectId} ${gate.gateId}`);
|
|
396
|
+
export async function init(projectRoot, protocolName, projectId, projectName) {
|
|
397
|
+
const protocol = loadProtocol(projectRoot, protocolName);
|
|
398
|
+
const statusPath = getStatusPath(projectRoot, projectId, projectName);
|
|
399
|
+
// Check if already exists
|
|
400
|
+
if (fs.existsSync(statusPath)) {
|
|
401
|
+
throw new Error(`Project ${projectId}-${projectName} already exists.`);
|
|
723
402
|
}
|
|
403
|
+
const state = createInitialState(protocol, projectId, projectName);
|
|
404
|
+
writeState(statusPath, state);
|
|
405
|
+
console.log('');
|
|
406
|
+
console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
|
|
407
|
+
console.log(` Protocol: ${protocolName}`);
|
|
408
|
+
console.log(` Initial phase: ${state.phase}`);
|
|
409
|
+
console.log(`\n Run: porch status ${projectId}`);
|
|
410
|
+
console.log('');
|
|
724
411
|
}
|
|
725
412
|
// ============================================================================
|
|
726
|
-
//
|
|
413
|
+
// Helpers
|
|
727
414
|
// ============================================================================
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
* 3. Check for .porch-project marker file
|
|
735
|
-
*/
|
|
736
|
-
function autoDetectProject() {
|
|
737
|
-
const cwd = process.cwd();
|
|
738
|
-
// Method 1: Check path pattern for builder worktree
|
|
739
|
-
// Pattern: .builders/<id> or .builders/<id>-<name>
|
|
740
|
-
const buildersMatch = cwd.match(/[/\\]\.builders[/\\](\d+)(?:-[^/\\]*)?(?:[/\\]|$)/);
|
|
741
|
-
if (buildersMatch) {
|
|
742
|
-
return buildersMatch[1];
|
|
743
|
-
}
|
|
744
|
-
// Pattern: worktrees/<protocol>_<id>_<name>
|
|
745
|
-
const worktreeMatch = cwd.match(/[/\\]worktrees[/\\]\w+_(\d+)_[^/\\]*(?:[/\\]|$)/);
|
|
746
|
-
if (worktreeMatch) {
|
|
747
|
-
return worktreeMatch[1];
|
|
748
|
-
}
|
|
749
|
-
// Method 2: Check for .porch-project marker file
|
|
750
|
-
const markerPath = path.join(cwd, '.porch-project');
|
|
751
|
-
if (fs.existsSync(markerPath)) {
|
|
752
|
-
const content = fs.readFileSync(markerPath, 'utf-8').trim();
|
|
753
|
-
if (content) {
|
|
754
|
-
return content;
|
|
415
|
+
function getInstructions(state, protocol) {
|
|
416
|
+
const phase = state.phase;
|
|
417
|
+
if (isPhased(protocol, phase) && state.plan_phases.length > 0) {
|
|
418
|
+
const current = getCurrentPlanPhase(state.plan_phases);
|
|
419
|
+
if (current) {
|
|
420
|
+
return ` You are implementing ${current.id}: "${current.title}".\n\n Complete the work, then run: porch check ${state.id}`;
|
|
755
421
|
}
|
|
756
422
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
423
|
+
const phaseConfig = getPhaseConfig(protocol, phase);
|
|
424
|
+
return ` You are in the ${phaseConfig?.name || phase} phase.\n\n When complete, run: porch done ${state.id}`;
|
|
425
|
+
}
|
|
426
|
+
function getNextAction(state, protocol) {
|
|
427
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
428
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
429
|
+
if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
|
|
430
|
+
return chalk.yellow('Wait for human to approve the gate.');
|
|
431
|
+
}
|
|
432
|
+
if (isPhased(protocol, state.phase)) {
|
|
433
|
+
const current = getCurrentPlanPhase(state.plan_phases);
|
|
434
|
+
if (current) {
|
|
435
|
+
return `Implement ${current.title} as specified in the plan.`;
|
|
763
436
|
}
|
|
764
437
|
}
|
|
765
|
-
|
|
766
|
-
|
|
438
|
+
if (Object.keys(checks).length > 0) {
|
|
439
|
+
return `Complete the phase work, then run: porch check ${state.id}`;
|
|
440
|
+
}
|
|
441
|
+
return `Complete the phase work, then run: porch done ${state.id}`;
|
|
442
|
+
}
|
|
443
|
+
function getArtifactForPhase(projectRoot, state) {
|
|
444
|
+
switch (state.phase) {
|
|
445
|
+
case 'specify':
|
|
446
|
+
return `codev/specs/${state.id}-${state.title}.md`;
|
|
447
|
+
case 'plan':
|
|
448
|
+
return `codev/plans/${state.id}-${state.title}.md`;
|
|
449
|
+
case 'review':
|
|
450
|
+
return `codev/reviews/${state.id}-${state.title}.md`;
|
|
451
|
+
default:
|
|
452
|
+
return null;
|
|
767
453
|
}
|
|
768
|
-
return null;
|
|
769
454
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// CLI
|
|
457
|
+
// ============================================================================
|
|
458
|
+
export async function cli(args) {
|
|
459
|
+
const [command, ...rest] = args;
|
|
460
|
+
const projectRoot = process.cwd();
|
|
461
|
+
// Auto-detect project ID for commands that need it
|
|
462
|
+
function getProjectId(provided) {
|
|
463
|
+
if (provided)
|
|
464
|
+
return provided;
|
|
465
|
+
const detected = detectProjectId(projectRoot);
|
|
466
|
+
if (detected) {
|
|
467
|
+
console.log(chalk.dim(`[auto-detected project: ${detected}]`));
|
|
468
|
+
return detected;
|
|
469
|
+
}
|
|
470
|
+
throw new Error('No project ID provided and could not auto-detect.\nProvide ID explicitly or ensure exactly one project exists in codev/projects/');
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
switch (command) {
|
|
474
|
+
case 'run':
|
|
475
|
+
const { run } = await import('./run.js');
|
|
476
|
+
await run(projectRoot, getProjectId(rest[0]));
|
|
477
|
+
break;
|
|
478
|
+
case 'status':
|
|
479
|
+
await status(projectRoot, getProjectId(rest[0]));
|
|
480
|
+
break;
|
|
481
|
+
case 'check':
|
|
482
|
+
await check(projectRoot, getProjectId(rest[0]));
|
|
483
|
+
break;
|
|
484
|
+
case 'done':
|
|
485
|
+
await done(projectRoot, getProjectId(rest[0]));
|
|
486
|
+
break;
|
|
487
|
+
case 'gate':
|
|
488
|
+
await gate(projectRoot, getProjectId(rest[0]));
|
|
489
|
+
break;
|
|
490
|
+
case 'approve':
|
|
491
|
+
if (!rest[0] || !rest[1])
|
|
492
|
+
throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
|
|
493
|
+
const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
|
|
494
|
+
await approve(projectRoot, rest[0], rest[1], hasHumanFlag);
|
|
495
|
+
break;
|
|
496
|
+
case 'init':
|
|
497
|
+
if (!rest[0] || !rest[1] || !rest[2]) {
|
|
498
|
+
throw new Error('Usage: porch init <protocol> <id> <name>');
|
|
781
499
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
500
|
+
await init(projectRoot, rest[0], rest[1], rest[2]);
|
|
501
|
+
break;
|
|
502
|
+
default:
|
|
503
|
+
console.log('porch - Protocol Orchestrator');
|
|
504
|
+
console.log('');
|
|
505
|
+
console.log('Commands:');
|
|
506
|
+
console.log(' run [id] Run the protocol (auto-detects if one project)');
|
|
507
|
+
console.log(' status [id] Show current state and instructions');
|
|
508
|
+
console.log(' check [id] Run checks for current phase');
|
|
509
|
+
console.log(' done [id] Advance to next phase (if checks pass)');
|
|
510
|
+
console.log(' gate [id] Request human approval');
|
|
511
|
+
console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
|
|
512
|
+
console.log(' init <protocol> <id> <name> Initialize a new project');
|
|
513
|
+
console.log('');
|
|
514
|
+
console.log('Project ID is auto-detected when exactly one project exists.');
|
|
515
|
+
console.log('');
|
|
516
|
+
process.exit(command ? 1 : 0);
|
|
787
517
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
await init(args[0], args[1], args[2], { description, worktree });
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
case 'approve': {
|
|
796
|
-
if (args.length < 2) {
|
|
797
|
-
throw new Error('Usage: porch approve <project-id> <gate-id>');
|
|
798
|
-
}
|
|
799
|
-
await approve(args[0], args[1]);
|
|
800
|
-
break;
|
|
801
|
-
}
|
|
802
|
-
case 'status': {
|
|
803
|
-
await status(args[0]);
|
|
804
|
-
break;
|
|
805
|
-
}
|
|
806
|
-
case 'pending': {
|
|
807
|
-
await pending();
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
case 'list':
|
|
811
|
-
case 'list-protocols': {
|
|
812
|
-
await list();
|
|
813
|
-
break;
|
|
814
|
-
}
|
|
815
|
-
case 'show':
|
|
816
|
-
case 'show-protocol': {
|
|
817
|
-
if (args.length < 1) {
|
|
818
|
-
throw new Error('Usage: porch show <protocol>');
|
|
819
|
-
}
|
|
820
|
-
await show(args[0]);
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
default:
|
|
824
|
-
throw new Error(`Unknown subcommand: ${subcommand}\n` +
|
|
825
|
-
'Valid subcommands: run, init, approve, status, pending, list, show');
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
521
|
+
process.exit(1);
|
|
826
522
|
}
|
|
827
523
|
}
|
|
828
524
|
//# sourceMappingURL=index.js.map
|