@cluesmith/codev 3.0.0 → 3.0.1
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/LICENSE +201 -0
- package/bin/af.js +2 -2
- package/bin/afx.js +8 -0
- package/bin/consult.js +2 -1
- package/bin/team.js +2 -1
- package/{dashboard/dist/assets/index-nr0cXxtf.css → dashboard-dist/assets/index-BWYZtvwm.css} +1 -1
- package/dashboard-dist/assets/index-Dn9zmcKp.js +195 -0
- package/dashboard-dist/assets/index-Dn9zmcKp.js.map +1 -0
- package/{dashboard/dist → dashboard-dist}/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +13 -14
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts +4 -4
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +23 -12
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.js +8 -8
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/bench.d.ts +1 -1
- package/dist/agent-farm/commands/bench.js +1 -1
- package/dist/agent-farm/commands/cron.js +1 -1
- package/dist/agent-farm/commands/cron.js.map +1 -1
- package/dist/agent-farm/commands/db.d.ts +3 -3
- package/dist/agent-farm/commands/db.js +4 -4
- package/dist/agent-farm/commands/db.js.map +1 -1
- package/dist/agent-farm/commands/open.js +2 -2
- package/dist/agent-farm/commands/open.js.map +1 -1
- package/dist/agent-farm/commands/rename.js +1 -1
- package/dist/agent-farm/commands/rename.js.map +1 -1
- package/dist/agent-farm/commands/send.js +3 -3
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/shell.js +3 -3
- package/dist/agent-farm/commands/shell.js.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts +8 -4
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.js +48 -28
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +80 -13
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +6 -6
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +90 -53
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +48 -3
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/team-update.d.ts +1 -1
- package/dist/agent-farm/commands/team-update.js +1 -1
- package/dist/agent-farm/commands/team.d.ts +1 -1
- package/dist/agent-farm/commands/team.js +1 -1
- package/dist/agent-farm/commands/tower-cloud.d.ts +2 -2
- package/dist/agent-farm/commands/tower-cloud.js +3 -3
- package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
- package/dist/agent-farm/commands/tower.js +2 -2
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/lib/cloud-config.js +2 -2
- package/dist/agent-farm/lib/cloud-config.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +6 -230
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +6 -293
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +1 -1
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +24 -0
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
- package/dist/agent-farm/servers/message-write.js +63 -0
- package/dist/agent-farm/servers/message-write.js.map +1 -0
- package/dist/agent-farm/servers/overview.d.ts +4 -1
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +38 -5
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
- package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
- package/dist/agent-farm/servers/send-buffer.js +7 -4
- package/dist/agent-farm/servers/send-buffer.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
- package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +4 -5
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +6 -5
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +19 -16
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +47 -18
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +2 -2
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +12 -6
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/types.d.ts +9 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +11 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +21 -0
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/harness.d.ts +85 -0
- package/dist/agent-farm/utils/harness.d.ts.map +1 -0
- package/dist/agent-farm/utils/harness.js +212 -0
- package/dist/agent-farm/utils/harness.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.js +2 -2
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +37 -3
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +220 -150
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +54 -8
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/artifacts.d.ts +69 -0
- package/dist/commands/porch/artifacts.d.ts.map +1 -0
- package/dist/commands/porch/artifacts.js +368 -0
- package/dist/commands/porch/artifacts.js.map +1 -0
- package/dist/commands/porch/checks.d.ts +8 -1
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +98 -1
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/index.d.ts +12 -7
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +216 -61
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +72 -74
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/notify.d.ts +2 -2
- package/dist/commands/porch/notify.js +5 -5
- package/dist/commands/porch/notify.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts +2 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +19 -7
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/state.d.ts +27 -2
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +96 -9
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +8 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +7 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/forge-contracts.d.ts +9 -0
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge.js +1 -1
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/team-github.d.ts +49 -0
- package/dist/lib/team-github.d.ts.map +1 -1
- package/dist/lib/team-github.js +115 -6
- package/dist/lib/team-github.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +2 -0
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/ws-protocol.d.ts +10 -8
- package/dist/terminal/ws-protocol.d.ts.map +1 -1
- package/dist/terminal/ws-protocol.js +3 -0
- package/dist/terminal/ws-protocol.js.map +1 -1
- package/package.json +28 -25
- package/scripts/forge/gitea/auth-status.sh +0 -0
- package/scripts/forge/gitea/issue-comment.sh +0 -0
- package/scripts/forge/gitea/issue-list.sh +0 -0
- package/scripts/forge/gitea/issue-view.sh +0 -0
- package/scripts/forge/gitea/pr-exists.sh +4 -1
- package/scripts/forge/gitea/pr-list.sh +0 -0
- package/scripts/forge/gitea/pr-merge.sh +0 -0
- package/scripts/forge/gitea/pr-view.sh +0 -0
- package/scripts/forge/gitea/recently-closed.sh +0 -0
- package/scripts/forge/gitea/recently-merged.sh +0 -0
- package/scripts/forge/gitea/repo-archive.sh +0 -0
- package/scripts/forge/gitea/user-identity.sh +0 -0
- package/scripts/forge/github/auth-status.sh +0 -0
- package/scripts/forge/github/issue-comment.sh +0 -0
- package/scripts/forge/github/issue-list.sh +2 -2
- package/scripts/forge/github/issue-view.sh +0 -0
- package/scripts/forge/github/on-it-timestamps.sh +0 -0
- package/scripts/forge/github/pr-diff.sh +0 -0
- package/scripts/forge/github/pr-exists.sh +3 -1
- package/scripts/forge/github/pr-list.sh +2 -2
- package/scripts/forge/github/pr-merge.sh +0 -0
- package/scripts/forge/github/pr-search.sh +0 -0
- package/scripts/forge/github/pr-view.sh +0 -0
- package/scripts/forge/github/recently-closed.sh +0 -0
- package/scripts/forge/github/recently-merged.sh +0 -0
- package/scripts/forge/github/repo-archive.sh +0 -0
- package/scripts/forge/github/team-activity.sh +0 -0
- package/scripts/forge/github/user-identity.sh +0 -0
- package/scripts/forge/gitlab/auth-status.sh +0 -0
- package/scripts/forge/gitlab/issue-comment.sh +0 -0
- package/scripts/forge/gitlab/issue-list.sh +0 -0
- package/scripts/forge/gitlab/issue-view.sh +0 -0
- package/scripts/forge/gitlab/pr-diff.sh +0 -0
- package/scripts/forge/gitlab/pr-exists.sh +3 -1
- package/scripts/forge/gitlab/pr-list.sh +0 -0
- package/scripts/forge/gitlab/pr-merge.sh +0 -0
- package/scripts/forge/gitlab/pr-search.sh +0 -0
- package/scripts/forge/gitlab/pr-view.sh +0 -0
- package/scripts/forge/gitlab/recently-closed.sh +0 -0
- package/scripts/forge/gitlab/recently-merged.sh +0 -0
- package/scripts/forge/gitlab/repo-archive.sh +0 -0
- package/scripts/forge/gitlab/user-identity.sh +0 -0
- package/scripts/postinstall.mjs +51 -0
- package/skeleton/.claude/skills/afx/SKILL.md +144 -0
- package/skeleton/.claude/skills/codev/SKILL.md +3 -3
- package/skeleton/.claude/skills/consult/SKILL.md +1 -1
- package/skeleton/.claude/skills/porch/SKILL.md +2 -2
- package/skeleton/DEPENDENCIES.md +1 -1
- package/skeleton/builders.md +4 -4
- package/skeleton/protocol-schema.json +2 -2
- package/skeleton/protocols/air/builder-prompt.md +6 -6
- package/skeleton/protocols/air/prompts/pr.md +17 -5
- package/skeleton/protocols/air/protocol.md +1 -2
- package/skeleton/protocols/aspir/builder-prompt.md +28 -6
- package/skeleton/protocols/aspir/prompts/review.md +11 -0
- package/skeleton/protocols/aspir/protocol.json +9 -1
- package/skeleton/protocols/aspir/protocol.md +1 -1
- package/skeleton/protocols/aspir/templates/plan.md +0 -20
- package/skeleton/protocols/aspir/templates/spec.md +0 -28
- package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
- package/skeleton/protocols/bugfix/prompts/pr.md +16 -4
- package/skeleton/protocols/experiment/builder-prompt.md +15 -0
- package/skeleton/protocols/experiment/protocol.md +1 -6
- package/skeleton/protocols/maintain/builder-prompt.md +12 -11
- package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
- package/skeleton/protocols/maintain/prompts/review.md +84 -0
- package/skeleton/protocols/maintain/protocol.json +14 -68
- package/skeleton/protocols/maintain/protocol.md +111 -409
- package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
- package/skeleton/protocols/protocol-schema.json +1 -1
- package/skeleton/protocols/research/builder-prompt.md +85 -0
- package/skeleton/protocols/research/protocol.json +118 -0
- package/skeleton/protocols/research/protocol.md +169 -0
- package/skeleton/protocols/spike/builder-prompt.md +1 -1
- package/skeleton/protocols/spike/protocol.md +4 -4
- package/skeleton/protocols/spir/builder-prompt.md +28 -5
- package/skeleton/protocols/spir/prompts/review.md +11 -0
- package/skeleton/protocols/spir/protocol.json +9 -1
- package/skeleton/protocols/spir/protocol.md +2 -2
- package/skeleton/protocols/spir/templates/plan.md +0 -20
- package/skeleton/protocols/spir/templates/spec.md +0 -28
- package/skeleton/resources/commands/agent-farm.md +84 -84
- package/skeleton/resources/commands/codev.md +1 -1
- package/skeleton/resources/commands/consult.md +16 -7
- package/skeleton/resources/commands/overview.md +14 -15
- package/skeleton/resources/risk-triage.md +1 -1
- package/skeleton/resources/workflow-reference.md +14 -14
- package/skeleton/roles/architect.md +39 -39
- package/skeleton/roles/builder.md +22 -14
- package/skeleton/templates/AGENTS.md +5 -3
- package/skeleton/templates/CLAUDE.md +5 -3
- package/skeleton/templates/cheatsheet.md +12 -12
- package/templates/3d-viewer.html +16 -18
- package/templates/tower.html +1 -1
- package/dashboard/dist/assets/index-CTrlY11L.js +0 -194
- package/dashboard/dist/assets/index-CTrlY11L.js.map +0 -1
- package/skeleton/.claude/skills/af/SKILL.md +0 -146
- package/skeleton/porch/prompts/understand.md +0 -61
- package/skeleton/porch/prompts/verify.md +0 -58
- package/skeleton/protocols/maintain/prompts/audit.md +0 -111
- package/skeleton/protocols/maintain/prompts/clean.md +0 -91
- package/skeleton/protocols/maintain/prompts/sync.md +0 -113
- package/skeleton/protocols/maintain/prompts/verify.md +0 -110
- package/skeleton/protocols/tick/builder-prompt.md +0 -65
- package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
- package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
- package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
- package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
- package/skeleton/protocols/tick/protocol.json +0 -151
- package/skeleton/protocols/tick/protocol.md +0 -277
- package/skeleton/protocols/tick/templates/plan.md +0 -67
- package/skeleton/protocols/tick/templates/review.md +0 -89
- package/skeleton/protocols/tick/templates/spec.md +0 -61
|
@@ -8,9 +8,10 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { globSync } from 'glob';
|
|
11
|
-
import { readState,
|
|
11
|
+
import { readState, writeStateAndCommit, createInitialState, findStatusPath, getArtifactRoot, getProjectDir, getStatusPath, resolveProjectId, resolveArtifactBaseName, } from './state.js';
|
|
12
12
|
import { loadProtocol, getPhaseConfig, getNextPhase, getPhaseChecks, getPhaseGate, isPhased, isBuildVerify, getVerifyConfig, } from './protocol.js';
|
|
13
|
-
import { findPlanFile,
|
|
13
|
+
import { findPlanFile, extractPlanPhases, getCurrentPlanPhase, getPhaseContent, allPlanPhasesComplete, } from './plan.js';
|
|
14
|
+
import { getResolver, LocalResolver } from './artifacts.js';
|
|
14
15
|
import { runPhaseChecks, formatCheckResults, allChecksPassed, } from './checks.js';
|
|
15
16
|
import { loadCheckOverrides } from './config.js';
|
|
16
17
|
import { loadConfig } from '../../lib/config.js';
|
|
@@ -24,6 +25,26 @@ function header(text) {
|
|
|
24
25
|
function section(title, content) {
|
|
25
26
|
return `\n${chalk.bold(title)}:\n${content}`;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Return a resolver scoped to `artifactRoot` when it differs from the caller's
|
|
30
|
+
* cwd-rooted resolver. The incoming `resolver` is typically built from
|
|
31
|
+
* `process.cwd()` (the main workspace), but `findStatusPath` may resolve a
|
|
32
|
+
* project to a builder worktree under `.builders/`. Checks like `plan_exists`
|
|
33
|
+
* must read from that worktree, not from the main tree (bugfix #676).
|
|
34
|
+
*
|
|
35
|
+
* For the LOCAL backend we rebuild the resolver against `artifactRoot` so file
|
|
36
|
+
* lookups point at `<artifactRoot>/codev/...`. For other backends (e.g. CLI)
|
|
37
|
+
* artifact location is already independent of the filesystem, so we keep the
|
|
38
|
+
* caller's resolver.
|
|
39
|
+
*/
|
|
40
|
+
function scopeResolver(workspaceRoot, artifactRoot, resolver) {
|
|
41
|
+
if (artifactRoot === workspaceRoot)
|
|
42
|
+
return resolver;
|
|
43
|
+
// Only rebuild if the existing resolver is a LocalResolver (path-dependent).
|
|
44
|
+
if (resolver && !(resolver instanceof LocalResolver))
|
|
45
|
+
return resolver;
|
|
46
|
+
return getResolver(workspaceRoot, artifactRoot);
|
|
47
|
+
}
|
|
27
48
|
/**
|
|
28
49
|
* Log override/skip notices before running checks.
|
|
29
50
|
* Only emits output when overrides are actually in use.
|
|
@@ -58,7 +79,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
|
|
|
58
79
|
* porch status <id>
|
|
59
80
|
* Shows current state and prescriptive next steps.
|
|
60
81
|
*/
|
|
61
|
-
export async function status(workspaceRoot, projectId) {
|
|
82
|
+
export async function status(workspaceRoot, projectId, resolver) {
|
|
62
83
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
63
84
|
if (!statusPath) {
|
|
64
85
|
throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
|
|
@@ -79,7 +100,8 @@ export async function status(workspaceRoot, projectId) {
|
|
|
79
100
|
// Status icons
|
|
80
101
|
const icon = (status) => {
|
|
81
102
|
switch (status) {
|
|
82
|
-
case '
|
|
103
|
+
case 'verified': return chalk.green('✓');
|
|
104
|
+
case 'complete': return chalk.green('✓'); // backward compat
|
|
83
105
|
case 'in_progress': return chalk.yellow('►');
|
|
84
106
|
default: return chalk.gray('○');
|
|
85
107
|
}
|
|
@@ -95,11 +117,11 @@ export async function status(workspaceRoot, projectId) {
|
|
|
95
117
|
if (currentPlanPhase) {
|
|
96
118
|
console.log('');
|
|
97
119
|
console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
98
|
-
// Show phase content from plan
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const phaseContent = getPhaseContent(
|
|
120
|
+
// Show phase content from plan (via resolver if available)
|
|
121
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
122
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
123
|
+
if (planContent) {
|
|
124
|
+
const phaseContent = getPhaseContent(planContent, currentPlanPhase.id);
|
|
103
125
|
if (phaseContent) {
|
|
104
126
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
|
|
105
127
|
}
|
|
@@ -146,11 +168,16 @@ export async function status(workspaceRoot, projectId) {
|
|
|
146
168
|
* porch check <id>
|
|
147
169
|
* Runs the phase checks and reports results.
|
|
148
170
|
*/
|
|
149
|
-
export async function check(workspaceRoot, projectId) {
|
|
171
|
+
export async function check(workspaceRoot, projectId, resolver) {
|
|
150
172
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
151
173
|
if (!statusPath) {
|
|
152
174
|
throw new Error(`Project ${projectId} not found.`);
|
|
153
175
|
}
|
|
176
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml.
|
|
177
|
+
// `findStatusPath` searches `.builders/*` first; resolver/cwd must match so
|
|
178
|
+
// checks like `plan_exists` see files in the same worktree (bugfix #676).
|
|
179
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
180
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
154
181
|
const state = readState(statusPath);
|
|
155
182
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
156
183
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
@@ -161,7 +188,7 @@ export async function check(workspaceRoot, projectId) {
|
|
|
161
188
|
console.log(chalk.dim('No checks defined for this phase.'));
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
164
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
191
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
165
192
|
console.log('');
|
|
166
193
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
167
194
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
@@ -174,7 +201,7 @@ export async function check(workspaceRoot, projectId) {
|
|
|
174
201
|
console.log('');
|
|
175
202
|
return;
|
|
176
203
|
}
|
|
177
|
-
const results = await runPhaseChecks(checks,
|
|
204
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
178
205
|
console.log(formatCheckResults(results));
|
|
179
206
|
console.log('');
|
|
180
207
|
if (allChecksPassed(results)) {
|
|
@@ -191,17 +218,50 @@ export async function check(workspaceRoot, projectId) {
|
|
|
191
218
|
* porch done <id>
|
|
192
219
|
* Advances to next phase if checks pass. Refuses if checks fail.
|
|
193
220
|
*/
|
|
194
|
-
export async function done(workspaceRoot, projectId) {
|
|
221
|
+
export async function done(workspaceRoot, projectId, resolver, options) {
|
|
195
222
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
196
223
|
if (!statusPath) {
|
|
197
224
|
throw new Error(`Project ${projectId} not found.`);
|
|
198
225
|
}
|
|
199
226
|
let state = readState(statusPath);
|
|
227
|
+
// Record-only mode: --pr or --merged writes PR metadata and exits immediately.
|
|
228
|
+
// Does NOT run checks, does NOT advance the phase, does NOT mark build_complete.
|
|
229
|
+
if (options?.pr !== undefined) {
|
|
230
|
+
if (!options.branch)
|
|
231
|
+
throw new Error('--pr requires --branch <name>');
|
|
232
|
+
if (!state.pr_history)
|
|
233
|
+
state.pr_history = [];
|
|
234
|
+
state.pr_history.push({
|
|
235
|
+
phase: state.phase,
|
|
236
|
+
pr_number: options.pr,
|
|
237
|
+
branch: options.branch,
|
|
238
|
+
created_at: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} record PR #${options.pr}`);
|
|
241
|
+
console.log(chalk.green(`Recorded PR #${options.pr} (branch: ${options.branch}) in pr_history.`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (options?.merged !== undefined) {
|
|
245
|
+
if (!state.pr_history)
|
|
246
|
+
throw new Error(`No PR history found for project ${projectId}`);
|
|
247
|
+
const entry = state.pr_history.find(e => e.pr_number === options.merged);
|
|
248
|
+
if (!entry)
|
|
249
|
+
throw new Error(`PR #${options.merged} not found in pr_history`);
|
|
250
|
+
entry.merged = true;
|
|
251
|
+
entry.merged_at = new Date().toISOString();
|
|
252
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} PR #${options.merged} merged`);
|
|
253
|
+
console.log(chalk.green(`Marked PR #${options.merged} as merged.`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
200
256
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
201
257
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
202
258
|
const phaseConfig = getPhaseConfig(protocol, state.phase);
|
|
203
259
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
204
260
|
const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
|
|
261
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml
|
|
262
|
+
// (bugfix #676 — see check() for rationale).
|
|
263
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
264
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
205
265
|
// Run checks first — but skip if the gate was just approved (approve already ran them)
|
|
206
266
|
if (phaseCheckNames.length > 0) {
|
|
207
267
|
const gate = getPhaseGate(protocol, state.phase);
|
|
@@ -213,12 +273,12 @@ export async function done(workspaceRoot, projectId) {
|
|
|
213
273
|
console.log(chalk.dim('Checks skipped (gate approved <60s ago).'));
|
|
214
274
|
}
|
|
215
275
|
else {
|
|
216
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
276
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
217
277
|
console.log('');
|
|
218
278
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
219
279
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
220
280
|
if (Object.keys(checks).length > 0) {
|
|
221
|
-
const results = await runPhaseChecks(checks,
|
|
281
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
222
282
|
console.log(formatCheckResults(results));
|
|
223
283
|
if (!allChecksPassed(results)) {
|
|
224
284
|
console.log('');
|
|
@@ -235,7 +295,7 @@ export async function done(workspaceRoot, projectId) {
|
|
|
235
295
|
// For build_verify phases: mark build as complete for verification
|
|
236
296
|
if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
|
|
237
297
|
state.build_complete = true;
|
|
238
|
-
|
|
298
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${state.phase} build-complete`);
|
|
239
299
|
console.log('');
|
|
240
300
|
console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
|
|
241
301
|
console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
|
|
@@ -298,9 +358,17 @@ export async function done(workspaceRoot, projectId) {
|
|
|
298
358
|
}
|
|
299
359
|
}
|
|
300
360
|
}
|
|
301
|
-
// Check for gate
|
|
361
|
+
// Check for gate — auto-request if not yet requested
|
|
302
362
|
const gate = getPhaseGate(protocol, state.phase);
|
|
303
363
|
if (gate && state.gates[gate]?.status !== 'approved') {
|
|
364
|
+
// Auto-request the gate if it hasn't been requested yet
|
|
365
|
+
if (!state.gates[gate]) {
|
|
366
|
+
state.gates[gate] = { status: 'pending' };
|
|
367
|
+
}
|
|
368
|
+
if (!state.gates[gate].requested_at) {
|
|
369
|
+
state.gates[gate].requested_at = new Date().toISOString();
|
|
370
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gate} gate-requested`);
|
|
371
|
+
}
|
|
304
372
|
console.log('');
|
|
305
373
|
console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
|
|
306
374
|
console.log(`\n Run: porch gate ${state.id}`);
|
|
@@ -320,13 +388,13 @@ export async function done(workspaceRoot, projectId) {
|
|
|
320
388
|
}
|
|
321
389
|
}
|
|
322
390
|
// Advance to next protocol phase
|
|
323
|
-
advanceProtocolPhase(workspaceRoot, state, protocol, statusPath);
|
|
391
|
+
await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, scopedResolver);
|
|
324
392
|
}
|
|
325
|
-
function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
393
|
+
async function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver) {
|
|
326
394
|
const nextPhase = getNextPhase(protocol, state.phase);
|
|
327
395
|
if (!nextPhase) {
|
|
328
|
-
state.phase = '
|
|
329
|
-
|
|
396
|
+
state.phase = 'verified';
|
|
397
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} protocol complete`);
|
|
330
398
|
console.log('');
|
|
331
399
|
console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
|
|
332
400
|
console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
|
|
@@ -337,16 +405,17 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
337
405
|
state.iteration = 1;
|
|
338
406
|
// If entering a phased phase (implement), extract plan phases
|
|
339
407
|
if (isPhased(protocol, nextPhase.id)) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
408
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
409
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
410
|
+
if (planContent) {
|
|
411
|
+
state.plan_phases = extractPlanPhases(planContent);
|
|
412
|
+
// extractPlanPhases already marks first phase as in_progress
|
|
344
413
|
if (state.plan_phases.length > 0) {
|
|
345
414
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
346
415
|
}
|
|
347
416
|
}
|
|
348
417
|
}
|
|
349
|
-
|
|
418
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${nextPhase.id} phase-transition`);
|
|
350
419
|
console.log('');
|
|
351
420
|
console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
|
|
352
421
|
// If we just entered implement phase, show phase 1 info and the critical warning
|
|
@@ -355,11 +424,11 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
355
424
|
const nextPlanPhase = state.plan_phases[1];
|
|
356
425
|
console.log('');
|
|
357
426
|
console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
|
|
358
|
-
// Show phase content from plan
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const phaseContent = getPhaseContent(
|
|
427
|
+
// Show phase content from plan (via resolver if available)
|
|
428
|
+
const planContentForDisplay = resolver?.getPlanContent(state.id, state.title)
|
|
429
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
430
|
+
if (planContentForDisplay) {
|
|
431
|
+
const phaseContent = getPhaseContent(planContentForDisplay, firstPhase.id);
|
|
363
432
|
if (phaseContent) {
|
|
364
433
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
|
|
365
434
|
}
|
|
@@ -383,7 +452,7 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
383
452
|
* porch gate <id>
|
|
384
453
|
* Requests human approval for current gate.
|
|
385
454
|
*/
|
|
386
|
-
export async function gate(workspaceRoot, projectId) {
|
|
455
|
+
export async function gate(workspaceRoot, projectId, resolver) {
|
|
387
456
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
388
457
|
if (!statusPath) {
|
|
389
458
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -402,22 +471,22 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
402
471
|
}
|
|
403
472
|
if (!state.gates[gateName].requested_at) {
|
|
404
473
|
state.gates[gateName].requested_at = new Date().toISOString();
|
|
405
|
-
|
|
474
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-requested`);
|
|
406
475
|
}
|
|
407
476
|
console.log('');
|
|
408
477
|
console.log(chalk.bold(`GATE: ${gateName}`));
|
|
409
478
|
console.log('');
|
|
410
479
|
// Show relevant artifact and open it for review
|
|
411
|
-
const artifact = getArtifactForPhase(workspaceRoot, state);
|
|
480
|
+
const artifact = getArtifactForPhase(workspaceRoot, state, resolver);
|
|
412
481
|
if (artifact) {
|
|
413
482
|
const fullPath = path.join(workspaceRoot, artifact);
|
|
414
483
|
if (fs.existsSync(fullPath)) {
|
|
415
484
|
console.log(` Artifact: ${artifact}`);
|
|
416
485
|
console.log('');
|
|
417
486
|
console.log(chalk.cyan(' Opening artifact for human review...'));
|
|
418
|
-
// Use
|
|
487
|
+
// Use afx open to display in annotation viewer
|
|
419
488
|
const { spawn } = await import('node:child_process');
|
|
420
|
-
spawn('
|
|
489
|
+
spawn('afx', ['open', fullPath], {
|
|
421
490
|
stdio: 'inherit',
|
|
422
491
|
detached: true
|
|
423
492
|
}).unref();
|
|
@@ -436,15 +505,34 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
436
505
|
* porch approve <id> <gate> --a-human-explicitly-approved-this
|
|
437
506
|
* Human approves a gate. Requires explicit flag to prevent automated approvals.
|
|
438
507
|
*/
|
|
439
|
-
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag) {
|
|
508
|
+
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag, resolver) {
|
|
440
509
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
441
510
|
if (!statusPath) {
|
|
442
511
|
throw new Error(`Project ${projectId} not found.`);
|
|
443
512
|
}
|
|
513
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml
|
|
514
|
+
// (bugfix #676 — see check() for rationale).
|
|
515
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
516
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
444
517
|
const state = readState(statusPath);
|
|
518
|
+
// Convenience: for verify-approval, auto-complete porch done if build_complete is false
|
|
519
|
+
if (gateName === 'verify-approval' && state.phase === 'verify' && !state.build_complete) {
|
|
520
|
+
state.build_complete = true;
|
|
521
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} verify build-complete (auto)`);
|
|
522
|
+
}
|
|
523
|
+
// Auto-create gate entry for upgraded projects (e.g., verify-approval missing after upgrade)
|
|
445
524
|
if (!state.gates[gateName]) {
|
|
446
|
-
const
|
|
447
|
-
|
|
525
|
+
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
526
|
+
const phaseGate = getPhaseGate(protocol, state.phase);
|
|
527
|
+
if (phaseGate === gateName) {
|
|
528
|
+
// Gate belongs to the current phase — initialize it
|
|
529
|
+
state.gates[gateName] = { status: 'pending', requested_at: new Date().toISOString() };
|
|
530
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-created (upgrade)`);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
const knownGates = Object.keys(state.gates).join(', ');
|
|
534
|
+
throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
|
|
535
|
+
}
|
|
448
536
|
}
|
|
449
537
|
if (state.gates[gateName].status === 'approved') {
|
|
450
538
|
console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
|
|
@@ -468,12 +556,12 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
468
556
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
469
557
|
const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
|
|
470
558
|
if (phaseCheckNames.length > 0) {
|
|
471
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
559
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
472
560
|
console.log('');
|
|
473
561
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
474
562
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
475
563
|
if (Object.keys(checks).length > 0) {
|
|
476
|
-
const results = await runPhaseChecks(checks,
|
|
564
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
477
565
|
console.log(formatCheckResults(results));
|
|
478
566
|
if (!allChecksPassed(results)) {
|
|
479
567
|
console.log('');
|
|
@@ -488,17 +576,27 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
488
576
|
}
|
|
489
577
|
state.gates[gateName].status = 'approved';
|
|
490
578
|
state.gates[gateName].approved_at = new Date().toISOString();
|
|
491
|
-
|
|
579
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-approved`);
|
|
492
580
|
console.log('');
|
|
493
581
|
console.log(chalk.green(`Gate ${gateName} approved.`));
|
|
494
|
-
|
|
582
|
+
// For verify-approval: auto-advance to terminal state (convenience — one command)
|
|
583
|
+
// NOTE: The 'verified' state is committed to the builder branch, which may not
|
|
584
|
+
// be merged back to main. The closed GitHub Issue serves as the canonical "done"
|
|
585
|
+
// signal on main. State alignment (making status.yaml on main authoritative) is
|
|
586
|
+
// tracked as future work per spec 653.
|
|
587
|
+
if (gateName === 'verify-approval') {
|
|
588
|
+
await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
592
|
+
}
|
|
495
593
|
console.log('');
|
|
496
594
|
}
|
|
497
595
|
/**
|
|
498
596
|
* porch rollback <id> <phase>
|
|
499
597
|
* Rewinds project to an earlier phase, clearing downstream gates and resetting build state.
|
|
500
598
|
*/
|
|
501
|
-
export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
599
|
+
export async function rollback(workspaceRoot, projectId, targetPhase, resolver) {
|
|
502
600
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
503
601
|
if (!statusPath) {
|
|
504
602
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -515,7 +613,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
515
613
|
const currentIndex = protocol.phases.findIndex(p => p.id === state.phase);
|
|
516
614
|
const targetIndex = protocol.phases.findIndex(p => p.id === targetPhase);
|
|
517
615
|
// Handle completed projects (phase not in protocol phases array)
|
|
518
|
-
if (state.phase === 'complete') {
|
|
616
|
+
if (state.phase === 'verified' || state.phase === 'complete') {
|
|
519
617
|
// Allow rollback from complete state to any valid phase
|
|
520
618
|
}
|
|
521
619
|
else if (currentIndex === -1) {
|
|
@@ -538,11 +636,12 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
538
636
|
state.iteration = 1;
|
|
539
637
|
state.build_complete = false;
|
|
540
638
|
state.history = [];
|
|
541
|
-
// If rolling back to a phased phase, re-extract plan phases
|
|
639
|
+
// If rolling back to a phased phase, re-extract plan phases
|
|
542
640
|
if (isPhased(protocol, targetPhase)) {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
641
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
642
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
643
|
+
if (planContent) {
|
|
644
|
+
state.plan_phases = extractPlanPhases(planContent);
|
|
546
645
|
if (state.plan_phases.length > 0) {
|
|
547
646
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
548
647
|
}
|
|
@@ -559,7 +658,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
559
658
|
state.plan_phases = [];
|
|
560
659
|
state.current_plan_phase = null;
|
|
561
660
|
}
|
|
562
|
-
|
|
661
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} rollback ${previousPhase} → ${targetPhase}`);
|
|
563
662
|
console.log('');
|
|
564
663
|
console.log(chalk.green(`ROLLED BACK: ${previousPhase} → ${targetPhase}`));
|
|
565
664
|
console.log(` Project: ${state.id}`);
|
|
@@ -572,7 +671,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
572
671
|
* Initialize a new project.
|
|
573
672
|
*
|
|
574
673
|
* Idempotent: if status.yaml already exists, preserves it and reports
|
|
575
|
-
* current state. This supports `
|
|
674
|
+
* current state. This supports `afx spawn --resume` where the builder
|
|
576
675
|
* may re-run `porch init` after a session restart.
|
|
577
676
|
*/
|
|
578
677
|
export async function init(workspaceRoot, protocolName, projectId, projectName) {
|
|
@@ -608,7 +707,7 @@ export async function init(workspaceRoot, protocolName, projectId, projectName)
|
|
|
608
707
|
return;
|
|
609
708
|
}
|
|
610
709
|
const state = createInitialState(protocol, projectId, projectName, workspaceRoot);
|
|
611
|
-
|
|
710
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} init ${protocolName}`);
|
|
612
711
|
console.log('');
|
|
613
712
|
console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
|
|
614
713
|
console.log(` Protocol: ${protocolName}`);
|
|
@@ -647,8 +746,8 @@ function getNextAction(state, protocol) {
|
|
|
647
746
|
}
|
|
648
747
|
return `Complete the phase work, then run: porch done ${state.id}`;
|
|
649
748
|
}
|
|
650
|
-
function getArtifactForPhase(workspaceRoot, state) {
|
|
651
|
-
const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title);
|
|
749
|
+
function getArtifactForPhase(workspaceRoot, state, resolver) {
|
|
750
|
+
const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title, resolver);
|
|
652
751
|
switch (state.phase) {
|
|
653
752
|
case 'specify':
|
|
654
753
|
return `codev/specs/${baseName}.md`;
|
|
@@ -666,6 +765,7 @@ function getArtifactForPhase(workspaceRoot, state) {
|
|
|
666
765
|
export async function cli(args) {
|
|
667
766
|
const [command, ...rest] = args;
|
|
668
767
|
const workspaceRoot = process.cwd();
|
|
768
|
+
const resolver = getResolver(workspaceRoot);
|
|
669
769
|
// Auto-detect project ID for commands that need it
|
|
670
770
|
function getProjectId(provided) {
|
|
671
771
|
const { id, source } = resolveProjectId(provided, process.cwd(), workspaceRoot);
|
|
@@ -691,28 +791,80 @@ export async function cli(args) {
|
|
|
691
791
|
process.exit(1);
|
|
692
792
|
break;
|
|
693
793
|
case 'status':
|
|
694
|
-
await status(workspaceRoot, getProjectId(rest[0]));
|
|
794
|
+
await status(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
695
795
|
break;
|
|
696
796
|
case 'check':
|
|
697
|
-
await check(workspaceRoot, getProjectId(rest[0]));
|
|
797
|
+
await check(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
698
798
|
break;
|
|
699
|
-
case 'done':
|
|
700
|
-
|
|
799
|
+
case 'done': {
|
|
800
|
+
const doneOpts = {};
|
|
801
|
+
const prIdx = rest.indexOf('--pr');
|
|
802
|
+
const brIdx = rest.indexOf('--branch');
|
|
803
|
+
const mergedIdx = rest.indexOf('--merged');
|
|
804
|
+
if (prIdx !== -1) {
|
|
805
|
+
const val = parseInt(rest[prIdx + 1], 10);
|
|
806
|
+
if (!Number.isInteger(val) || val <= 0)
|
|
807
|
+
throw new Error('--pr requires a positive integer PR number');
|
|
808
|
+
doneOpts.pr = val;
|
|
809
|
+
}
|
|
810
|
+
if (brIdx !== -1) {
|
|
811
|
+
if (!rest[brIdx + 1] || rest[brIdx + 1].startsWith('--'))
|
|
812
|
+
throw new Error('--branch requires a branch name');
|
|
813
|
+
doneOpts.branch = rest[brIdx + 1];
|
|
814
|
+
}
|
|
815
|
+
if (mergedIdx !== -1) {
|
|
816
|
+
const val = parseInt(rest[mergedIdx + 1], 10);
|
|
817
|
+
if (!Number.isInteger(val) || val <= 0)
|
|
818
|
+
throw new Error('--merged requires a positive integer PR number');
|
|
819
|
+
doneOpts.merged = val;
|
|
820
|
+
}
|
|
821
|
+
if (doneOpts.pr !== undefined && doneOpts.merged !== undefined) {
|
|
822
|
+
throw new Error('--pr and --merged are mutually exclusive');
|
|
823
|
+
}
|
|
824
|
+
const hasRecordFlags = doneOpts.pr !== undefined || doneOpts.merged !== undefined;
|
|
825
|
+
// For project ID: use first positional arg, or fall back to auto-detection
|
|
826
|
+
const projectIdArg = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
|
|
827
|
+
await done(workspaceRoot, getProjectId(projectIdArg), resolver, hasRecordFlags ? doneOpts : undefined);
|
|
701
828
|
break;
|
|
829
|
+
}
|
|
702
830
|
case 'gate':
|
|
703
|
-
await gate(workspaceRoot, getProjectId(rest[0]));
|
|
831
|
+
await gate(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
704
832
|
break;
|
|
705
833
|
case 'approve':
|
|
706
834
|
if (!rest[0] || !rest[1])
|
|
707
835
|
throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
|
|
708
836
|
const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
|
|
709
|
-
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag);
|
|
837
|
+
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag, resolver);
|
|
710
838
|
break;
|
|
711
839
|
case 'rollback':
|
|
712
840
|
if (!rest[0] || !rest[1])
|
|
713
841
|
throw new Error('Usage: porch rollback <id> <phase>');
|
|
714
|
-
await rollback(workspaceRoot, rest[0], rest[1]);
|
|
842
|
+
await rollback(workspaceRoot, rest[0], rest[1], resolver);
|
|
715
843
|
break;
|
|
844
|
+
case 'verify': {
|
|
845
|
+
const verifyProjectId = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
|
|
846
|
+
const skipIdx = rest.indexOf('--skip');
|
|
847
|
+
if (skipIdx === -1)
|
|
848
|
+
throw new Error('Usage: porch verify <id> --skip "reason"');
|
|
849
|
+
const skipReason = rest[skipIdx + 1];
|
|
850
|
+
if (!skipReason || skipReason.startsWith('--'))
|
|
851
|
+
throw new Error('--skip requires a reason');
|
|
852
|
+
const pid = getProjectId(verifyProjectId);
|
|
853
|
+
const sp = findStatusPath(workspaceRoot, pid);
|
|
854
|
+
if (!sp)
|
|
855
|
+
throw new Error(`Project ${pid} not found.`);
|
|
856
|
+
const st = readState(sp);
|
|
857
|
+
if (st.phase !== 'verify') {
|
|
858
|
+
throw new Error(`porch verify --skip can only be used in the verify phase (current: ${st.phase}). The PR must be merged first.`);
|
|
859
|
+
}
|
|
860
|
+
st.phase = 'verified';
|
|
861
|
+
st.context = { ...st.context, verify_skip_reason: skipReason };
|
|
862
|
+
await writeStateAndCommit(sp, st, `chore(porch): ${st.id} verify skipped: ${skipReason}`);
|
|
863
|
+
console.log('');
|
|
864
|
+
console.log(chalk.green(`VERIFIED (skipped): ${st.id}`));
|
|
865
|
+
console.log(` Reason: ${skipReason}`);
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
716
868
|
case 'init':
|
|
717
869
|
if (!rest[0] || !rest[1] || !rest[2]) {
|
|
718
870
|
throw new Error('Usage: porch init <protocol> <id> <name>');
|
|
@@ -727,8 +879,11 @@ export async function cli(args) {
|
|
|
727
879
|
console.log(' status [id] Show current state and instructions');
|
|
728
880
|
console.log(' check [id] Run checks for current phase');
|
|
729
881
|
console.log(' done [id] Signal build complete (validates checks, advances)');
|
|
882
|
+
console.log(' done [id] --pr N --branch NAME Record PR creation (no phase advancement)');
|
|
883
|
+
console.log(' done [id] --merged N Mark PR as merged (no phase advancement)');
|
|
730
884
|
console.log(' gate [id] Request human approval');
|
|
731
885
|
console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
|
|
886
|
+
console.log(' verify <id> --skip "reason" Skip verification and mark as verified');
|
|
732
887
|
console.log(' rollback <id> <phase> Rewind project to an earlier phase');
|
|
733
888
|
console.log(' init <protocol> <id> <name> Initialize a new project');
|
|
734
889
|
console.log('');
|