@cluesmith/codev 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +27 -19
- 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.d.ts.map +1 -1
- package/dist/agent-farm/commands/attach.js +101 -11
- 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 +9 -5
- 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 +5 -5
- 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/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +47 -0
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +1 -1
- package/dist/agent-farm/db/types.d.ts +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 +19 -4
- package/dist/agent-farm/servers/tower-server.js.map +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/state.js +1 -1
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +11 -2
- 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/agent-farm/utils/server-utils.d.ts +13 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +38 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- 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 +18 -7
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +299 -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 +23 -14
- 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 +10 -1
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +25 -7
- 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/forge/linear/auth-status.sh +15 -0
- package/scripts/forge/linear/issue-comment.sh +38 -0
- package/scripts/forge/linear/issue-list.sh +35 -0
- package/scripts/forge/linear/issue-view.sh +37 -0
- package/scripts/forge/linear/recently-closed.sh +45 -0
- package/scripts/forge/linear/user-identity.sh +15 -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/.claude/skills/update-arch-docs/SKILL.md +107 -0
- 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 +163 -394
- 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 +6 -5
- 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 +2 -2
- 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/arch.md +100 -30
- package/skeleton/templates/cheatsheet.md +12 -12
- package/skeleton/templates/lessons-learned.md +45 -7
- 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,12 +8,14 @@ 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';
|
|
18
|
+
import { version } from '../../version.js';
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Output Helpers
|
|
19
21
|
// ============================================================================
|
|
@@ -24,6 +26,26 @@ function header(text) {
|
|
|
24
26
|
function section(title, content) {
|
|
25
27
|
return `\n${chalk.bold(title)}:\n${content}`;
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Return a resolver scoped to `artifactRoot` when it differs from the caller's
|
|
31
|
+
* cwd-rooted resolver. The incoming `resolver` is typically built from
|
|
32
|
+
* `process.cwd()` (the main workspace), but `findStatusPath` may resolve a
|
|
33
|
+
* project to a builder worktree under `.builders/`. Checks like `plan_exists`
|
|
34
|
+
* must read from that worktree, not from the main tree (bugfix #676).
|
|
35
|
+
*
|
|
36
|
+
* For the LOCAL backend we rebuild the resolver against `artifactRoot` so file
|
|
37
|
+
* lookups point at `<artifactRoot>/codev/...`. For other backends (e.g. CLI)
|
|
38
|
+
* artifact location is already independent of the filesystem, so we keep the
|
|
39
|
+
* caller's resolver.
|
|
40
|
+
*/
|
|
41
|
+
function scopeResolver(workspaceRoot, artifactRoot, resolver) {
|
|
42
|
+
if (artifactRoot === workspaceRoot)
|
|
43
|
+
return resolver;
|
|
44
|
+
// Only rebuild if the existing resolver is a LocalResolver (path-dependent).
|
|
45
|
+
if (resolver && !(resolver instanceof LocalResolver))
|
|
46
|
+
return resolver;
|
|
47
|
+
return getResolver(workspaceRoot, artifactRoot);
|
|
48
|
+
}
|
|
27
49
|
/**
|
|
28
50
|
* Log override/skip notices before running checks.
|
|
29
51
|
* Only emits output when overrides are actually in use.
|
|
@@ -58,7 +80,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
|
|
|
58
80
|
* porch status <id>
|
|
59
81
|
* Shows current state and prescriptive next steps.
|
|
60
82
|
*/
|
|
61
|
-
export async function status(workspaceRoot, projectId) {
|
|
83
|
+
export async function status(workspaceRoot, projectId, resolver) {
|
|
62
84
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
63
85
|
if (!statusPath) {
|
|
64
86
|
throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
|
|
@@ -79,7 +101,8 @@ export async function status(workspaceRoot, projectId) {
|
|
|
79
101
|
// Status icons
|
|
80
102
|
const icon = (status) => {
|
|
81
103
|
switch (status) {
|
|
82
|
-
case '
|
|
104
|
+
case 'verified': return chalk.green('✓');
|
|
105
|
+
case 'complete': return chalk.green('✓'); // backward compat
|
|
83
106
|
case 'in_progress': return chalk.yellow('►');
|
|
84
107
|
default: return chalk.gray('○');
|
|
85
108
|
}
|
|
@@ -95,11 +118,11 @@ export async function status(workspaceRoot, projectId) {
|
|
|
95
118
|
if (currentPlanPhase) {
|
|
96
119
|
console.log('');
|
|
97
120
|
console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
98
|
-
// Show phase content from plan
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const phaseContent = getPhaseContent(
|
|
121
|
+
// Show phase content from plan (via resolver if available)
|
|
122
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
123
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
124
|
+
if (planContent) {
|
|
125
|
+
const phaseContent = getPhaseContent(planContent, currentPlanPhase.id);
|
|
103
126
|
if (phaseContent) {
|
|
104
127
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
|
|
105
128
|
}
|
|
@@ -146,11 +169,16 @@ export async function status(workspaceRoot, projectId) {
|
|
|
146
169
|
* porch check <id>
|
|
147
170
|
* Runs the phase checks and reports results.
|
|
148
171
|
*/
|
|
149
|
-
export async function check(workspaceRoot, projectId) {
|
|
172
|
+
export async function check(workspaceRoot, projectId, resolver) {
|
|
150
173
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
151
174
|
if (!statusPath) {
|
|
152
175
|
throw new Error(`Project ${projectId} not found.`);
|
|
153
176
|
}
|
|
177
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml.
|
|
178
|
+
// `findStatusPath` searches `.builders/*` first; resolver/cwd must match so
|
|
179
|
+
// checks like `plan_exists` see files in the same worktree (bugfix #676).
|
|
180
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
181
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
154
182
|
const state = readState(statusPath);
|
|
155
183
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
156
184
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
@@ -161,7 +189,7 @@ export async function check(workspaceRoot, projectId) {
|
|
|
161
189
|
console.log(chalk.dim('No checks defined for this phase.'));
|
|
162
190
|
return;
|
|
163
191
|
}
|
|
164
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
192
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
165
193
|
console.log('');
|
|
166
194
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
167
195
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
@@ -174,7 +202,7 @@ export async function check(workspaceRoot, projectId) {
|
|
|
174
202
|
console.log('');
|
|
175
203
|
return;
|
|
176
204
|
}
|
|
177
|
-
const results = await runPhaseChecks(checks,
|
|
205
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
178
206
|
console.log(formatCheckResults(results));
|
|
179
207
|
console.log('');
|
|
180
208
|
if (allChecksPassed(results)) {
|
|
@@ -191,17 +219,50 @@ export async function check(workspaceRoot, projectId) {
|
|
|
191
219
|
* porch done <id>
|
|
192
220
|
* Advances to next phase if checks pass. Refuses if checks fail.
|
|
193
221
|
*/
|
|
194
|
-
export async function done(workspaceRoot, projectId) {
|
|
222
|
+
export async function done(workspaceRoot, projectId, resolver, options) {
|
|
195
223
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
196
224
|
if (!statusPath) {
|
|
197
225
|
throw new Error(`Project ${projectId} not found.`);
|
|
198
226
|
}
|
|
199
227
|
let state = readState(statusPath);
|
|
228
|
+
// Record-only mode: --pr or --merged writes PR metadata and exits immediately.
|
|
229
|
+
// Does NOT run checks, does NOT advance the phase, does NOT mark build_complete.
|
|
230
|
+
if (options?.pr !== undefined) {
|
|
231
|
+
if (!options.branch)
|
|
232
|
+
throw new Error('--pr requires --branch <name>');
|
|
233
|
+
if (!state.pr_history)
|
|
234
|
+
state.pr_history = [];
|
|
235
|
+
state.pr_history.push({
|
|
236
|
+
phase: state.phase,
|
|
237
|
+
pr_number: options.pr,
|
|
238
|
+
branch: options.branch,
|
|
239
|
+
created_at: new Date().toISOString(),
|
|
240
|
+
});
|
|
241
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} record PR #${options.pr}`);
|
|
242
|
+
console.log(chalk.green(`Recorded PR #${options.pr} (branch: ${options.branch}) in pr_history.`));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (options?.merged !== undefined) {
|
|
246
|
+
if (!state.pr_history)
|
|
247
|
+
throw new Error(`No PR history found for project ${projectId}`);
|
|
248
|
+
const entry = state.pr_history.find(e => e.pr_number === options.merged);
|
|
249
|
+
if (!entry)
|
|
250
|
+
throw new Error(`PR #${options.merged} not found in pr_history`);
|
|
251
|
+
entry.merged = true;
|
|
252
|
+
entry.merged_at = new Date().toISOString();
|
|
253
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} PR #${options.merged} merged`);
|
|
254
|
+
console.log(chalk.green(`Marked PR #${options.merged} as merged.`));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
200
257
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
201
258
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
202
259
|
const phaseConfig = getPhaseConfig(protocol, state.phase);
|
|
203
260
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
204
261
|
const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
|
|
262
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml
|
|
263
|
+
// (bugfix #676 — see check() for rationale).
|
|
264
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
265
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
205
266
|
// Run checks first — but skip if the gate was just approved (approve already ran them)
|
|
206
267
|
if (phaseCheckNames.length > 0) {
|
|
207
268
|
const gate = getPhaseGate(protocol, state.phase);
|
|
@@ -213,12 +274,12 @@ export async function done(workspaceRoot, projectId) {
|
|
|
213
274
|
console.log(chalk.dim('Checks skipped (gate approved <60s ago).'));
|
|
214
275
|
}
|
|
215
276
|
else {
|
|
216
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
277
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
217
278
|
console.log('');
|
|
218
279
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
219
280
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
220
281
|
if (Object.keys(checks).length > 0) {
|
|
221
|
-
const results = await runPhaseChecks(checks,
|
|
282
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
222
283
|
console.log(formatCheckResults(results));
|
|
223
284
|
if (!allChecksPassed(results)) {
|
|
224
285
|
console.log('');
|
|
@@ -235,7 +296,7 @@ export async function done(workspaceRoot, projectId) {
|
|
|
235
296
|
// For build_verify phases: mark build as complete for verification
|
|
236
297
|
if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
|
|
237
298
|
state.build_complete = true;
|
|
238
|
-
|
|
299
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${state.phase} build-complete`);
|
|
239
300
|
console.log('');
|
|
240
301
|
console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
|
|
241
302
|
console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
|
|
@@ -298,9 +359,17 @@ export async function done(workspaceRoot, projectId) {
|
|
|
298
359
|
}
|
|
299
360
|
}
|
|
300
361
|
}
|
|
301
|
-
// Check for gate
|
|
362
|
+
// Check for gate — auto-request if not yet requested
|
|
302
363
|
const gate = getPhaseGate(protocol, state.phase);
|
|
303
364
|
if (gate && state.gates[gate]?.status !== 'approved') {
|
|
365
|
+
// Auto-request the gate if it hasn't been requested yet
|
|
366
|
+
if (!state.gates[gate]) {
|
|
367
|
+
state.gates[gate] = { status: 'pending' };
|
|
368
|
+
}
|
|
369
|
+
if (!state.gates[gate].requested_at) {
|
|
370
|
+
state.gates[gate].requested_at = new Date().toISOString();
|
|
371
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gate} gate-requested`);
|
|
372
|
+
}
|
|
304
373
|
console.log('');
|
|
305
374
|
console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
|
|
306
375
|
console.log(`\n Run: porch gate ${state.id}`);
|
|
@@ -320,13 +389,13 @@ export async function done(workspaceRoot, projectId) {
|
|
|
320
389
|
}
|
|
321
390
|
}
|
|
322
391
|
// Advance to next protocol phase
|
|
323
|
-
advanceProtocolPhase(workspaceRoot, state, protocol, statusPath);
|
|
392
|
+
await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, scopedResolver);
|
|
324
393
|
}
|
|
325
|
-
function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
394
|
+
async function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver) {
|
|
326
395
|
const nextPhase = getNextPhase(protocol, state.phase);
|
|
327
396
|
if (!nextPhase) {
|
|
328
|
-
state.phase = '
|
|
329
|
-
|
|
397
|
+
state.phase = 'verified';
|
|
398
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} protocol complete`);
|
|
330
399
|
console.log('');
|
|
331
400
|
console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
|
|
332
401
|
console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
|
|
@@ -337,16 +406,17 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
337
406
|
state.iteration = 1;
|
|
338
407
|
// If entering a phased phase (implement), extract plan phases
|
|
339
408
|
if (isPhased(protocol, nextPhase.id)) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
409
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
410
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
411
|
+
if (planContent) {
|
|
412
|
+
state.plan_phases = extractPlanPhases(planContent);
|
|
413
|
+
// extractPlanPhases already marks first phase as in_progress
|
|
344
414
|
if (state.plan_phases.length > 0) {
|
|
345
415
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
346
416
|
}
|
|
347
417
|
}
|
|
348
418
|
}
|
|
349
|
-
|
|
419
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${nextPhase.id} phase-transition`);
|
|
350
420
|
console.log('');
|
|
351
421
|
console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
|
|
352
422
|
// If we just entered implement phase, show phase 1 info and the critical warning
|
|
@@ -355,11 +425,11 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
355
425
|
const nextPlanPhase = state.plan_phases[1];
|
|
356
426
|
console.log('');
|
|
357
427
|
console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
|
|
358
|
-
// Show phase content from plan
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const phaseContent = getPhaseContent(
|
|
428
|
+
// Show phase content from plan (via resolver if available)
|
|
429
|
+
const planContentForDisplay = resolver?.getPlanContent(state.id, state.title)
|
|
430
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
431
|
+
if (planContentForDisplay) {
|
|
432
|
+
const phaseContent = getPhaseContent(planContentForDisplay, firstPhase.id);
|
|
363
433
|
if (phaseContent) {
|
|
364
434
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
|
|
365
435
|
}
|
|
@@ -383,7 +453,7 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
383
453
|
* porch gate <id>
|
|
384
454
|
* Requests human approval for current gate.
|
|
385
455
|
*/
|
|
386
|
-
export async function gate(workspaceRoot, projectId) {
|
|
456
|
+
export async function gate(workspaceRoot, projectId, resolver) {
|
|
387
457
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
388
458
|
if (!statusPath) {
|
|
389
459
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -402,22 +472,22 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
402
472
|
}
|
|
403
473
|
if (!state.gates[gateName].requested_at) {
|
|
404
474
|
state.gates[gateName].requested_at = new Date().toISOString();
|
|
405
|
-
|
|
475
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-requested`);
|
|
406
476
|
}
|
|
407
477
|
console.log('');
|
|
408
478
|
console.log(chalk.bold(`GATE: ${gateName}`));
|
|
409
479
|
console.log('');
|
|
410
480
|
// Show relevant artifact and open it for review
|
|
411
|
-
const artifact = getArtifactForPhase(workspaceRoot, state);
|
|
481
|
+
const artifact = getArtifactForPhase(workspaceRoot, state, resolver);
|
|
412
482
|
if (artifact) {
|
|
413
483
|
const fullPath = path.join(workspaceRoot, artifact);
|
|
414
484
|
if (fs.existsSync(fullPath)) {
|
|
415
485
|
console.log(` Artifact: ${artifact}`);
|
|
416
486
|
console.log('');
|
|
417
487
|
console.log(chalk.cyan(' Opening artifact for human review...'));
|
|
418
|
-
// Use
|
|
488
|
+
// Use afx open to display in annotation viewer
|
|
419
489
|
const { spawn } = await import('node:child_process');
|
|
420
|
-
spawn('
|
|
490
|
+
spawn('afx', ['open', fullPath], {
|
|
421
491
|
stdio: 'inherit',
|
|
422
492
|
detached: true
|
|
423
493
|
}).unref();
|
|
@@ -436,15 +506,34 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
436
506
|
* porch approve <id> <gate> --a-human-explicitly-approved-this
|
|
437
507
|
* Human approves a gate. Requires explicit flag to prevent automated approvals.
|
|
438
508
|
*/
|
|
439
|
-
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag) {
|
|
509
|
+
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag, resolver) {
|
|
440
510
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
441
511
|
if (!statusPath) {
|
|
442
512
|
throw new Error(`Project ${projectId} not found.`);
|
|
443
513
|
}
|
|
514
|
+
// Scope artifact reads + check cwd to the worktree that owns this status.yaml
|
|
515
|
+
// (bugfix #676 — see check() for rationale).
|
|
516
|
+
const artifactRoot = getArtifactRoot(statusPath);
|
|
517
|
+
const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
|
|
444
518
|
const state = readState(statusPath);
|
|
519
|
+
// Convenience: for verify-approval, auto-complete porch done if build_complete is false
|
|
520
|
+
if (gateName === 'verify-approval' && state.phase === 'verify' && !state.build_complete) {
|
|
521
|
+
state.build_complete = true;
|
|
522
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} verify build-complete (auto)`);
|
|
523
|
+
}
|
|
524
|
+
// Auto-create gate entry for upgraded projects (e.g., verify-approval missing after upgrade)
|
|
445
525
|
if (!state.gates[gateName]) {
|
|
446
|
-
const
|
|
447
|
-
|
|
526
|
+
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
527
|
+
const phaseGate = getPhaseGate(protocol, state.phase);
|
|
528
|
+
if (phaseGate === gateName) {
|
|
529
|
+
// Gate belongs to the current phase — initialize it
|
|
530
|
+
state.gates[gateName] = { status: 'pending', requested_at: new Date().toISOString() };
|
|
531
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-created (upgrade)`);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
const knownGates = Object.keys(state.gates).join(', ');
|
|
535
|
+
throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
|
|
536
|
+
}
|
|
448
537
|
}
|
|
449
538
|
if (state.gates[gateName].status === 'approved') {
|
|
450
539
|
console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
|
|
@@ -468,12 +557,12 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
468
557
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
469
558
|
const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
|
|
470
559
|
if (phaseCheckNames.length > 0) {
|
|
471
|
-
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(
|
|
560
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
|
|
472
561
|
console.log('');
|
|
473
562
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
474
563
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
475
564
|
if (Object.keys(checks).length > 0) {
|
|
476
|
-
const results = await runPhaseChecks(checks,
|
|
565
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
477
566
|
console.log(formatCheckResults(results));
|
|
478
567
|
if (!allChecksPassed(results)) {
|
|
479
568
|
console.log('');
|
|
@@ -488,17 +577,27 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
488
577
|
}
|
|
489
578
|
state.gates[gateName].status = 'approved';
|
|
490
579
|
state.gates[gateName].approved_at = new Date().toISOString();
|
|
491
|
-
|
|
580
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-approved`);
|
|
492
581
|
console.log('');
|
|
493
582
|
console.log(chalk.green(`Gate ${gateName} approved.`));
|
|
494
|
-
|
|
583
|
+
// For verify-approval: auto-advance to terminal state (convenience — one command)
|
|
584
|
+
// NOTE: The 'verified' state is committed to the builder branch, which may not
|
|
585
|
+
// be merged back to main. The closed GitHub Issue serves as the canonical "done"
|
|
586
|
+
// signal on main. State alignment (making status.yaml on main authoritative) is
|
|
587
|
+
// tracked as future work per spec 653.
|
|
588
|
+
if (gateName === 'verify-approval') {
|
|
589
|
+
await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
593
|
+
}
|
|
495
594
|
console.log('');
|
|
496
595
|
}
|
|
497
596
|
/**
|
|
498
597
|
* porch rollback <id> <phase>
|
|
499
598
|
* Rewinds project to an earlier phase, clearing downstream gates and resetting build state.
|
|
500
599
|
*/
|
|
501
|
-
export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
600
|
+
export async function rollback(workspaceRoot, projectId, targetPhase, resolver) {
|
|
502
601
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
503
602
|
if (!statusPath) {
|
|
504
603
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -515,7 +614,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
515
614
|
const currentIndex = protocol.phases.findIndex(p => p.id === state.phase);
|
|
516
615
|
const targetIndex = protocol.phases.findIndex(p => p.id === targetPhase);
|
|
517
616
|
// Handle completed projects (phase not in protocol phases array)
|
|
518
|
-
if (state.phase === 'complete') {
|
|
617
|
+
if (state.phase === 'verified' || state.phase === 'complete') {
|
|
519
618
|
// Allow rollback from complete state to any valid phase
|
|
520
619
|
}
|
|
521
620
|
else if (currentIndex === -1) {
|
|
@@ -538,11 +637,12 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
538
637
|
state.iteration = 1;
|
|
539
638
|
state.build_complete = false;
|
|
540
639
|
state.history = [];
|
|
541
|
-
// If rolling back to a phased phase, re-extract plan phases
|
|
640
|
+
// If rolling back to a phased phase, re-extract plan phases
|
|
542
641
|
if (isPhased(protocol, targetPhase)) {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
642
|
+
const planContent = resolver?.getPlanContent(state.id, state.title)
|
|
643
|
+
?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
|
|
644
|
+
if (planContent) {
|
|
645
|
+
state.plan_phases = extractPlanPhases(planContent);
|
|
546
646
|
if (state.plan_phases.length > 0) {
|
|
547
647
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
548
648
|
}
|
|
@@ -559,7 +659,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
559
659
|
state.plan_phases = [];
|
|
560
660
|
state.current_plan_phase = null;
|
|
561
661
|
}
|
|
562
|
-
|
|
662
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} rollback ${previousPhase} → ${targetPhase}`);
|
|
563
663
|
console.log('');
|
|
564
664
|
console.log(chalk.green(`ROLLED BACK: ${previousPhase} → ${targetPhase}`));
|
|
565
665
|
console.log(` Project: ${state.id}`);
|
|
@@ -572,7 +672,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
572
672
|
* Initialize a new project.
|
|
573
673
|
*
|
|
574
674
|
* Idempotent: if status.yaml already exists, preserves it and reports
|
|
575
|
-
* current state. This supports `
|
|
675
|
+
* current state. This supports `afx spawn --resume` where the builder
|
|
576
676
|
* may re-run `porch init` after a session restart.
|
|
577
677
|
*/
|
|
578
678
|
export async function init(workspaceRoot, protocolName, projectId, projectName) {
|
|
@@ -608,7 +708,7 @@ export async function init(workspaceRoot, protocolName, projectId, projectName)
|
|
|
608
708
|
return;
|
|
609
709
|
}
|
|
610
710
|
const state = createInitialState(protocol, projectId, projectName, workspaceRoot);
|
|
611
|
-
|
|
711
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} init ${protocolName}`);
|
|
612
712
|
console.log('');
|
|
613
713
|
console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
|
|
614
714
|
console.log(` Protocol: ${protocolName}`);
|
|
@@ -647,8 +747,77 @@ function getNextAction(state, protocol) {
|
|
|
647
747
|
}
|
|
648
748
|
return `Complete the phase work, then run: porch done ${state.id}`;
|
|
649
749
|
}
|
|
650
|
-
|
|
651
|
-
|
|
750
|
+
/**
|
|
751
|
+
* porch pending
|
|
752
|
+
* List all projects with gates awaiting human approval.
|
|
753
|
+
* Scans both the local workspace and any builder worktrees under .builders/*.
|
|
754
|
+
*/
|
|
755
|
+
export async function pending(workspaceRoot) {
|
|
756
|
+
const seen = new Set(); // dedupe by status.yaml path
|
|
757
|
+
const results = [];
|
|
758
|
+
// Build the list of project directories to scan: main + every builder worktree.
|
|
759
|
+
const projectsDirs = [path.join(workspaceRoot, 'codev', 'projects')];
|
|
760
|
+
const buildersDir = path.join(workspaceRoot, '.builders');
|
|
761
|
+
if (fs.existsSync(buildersDir)) {
|
|
762
|
+
for (const wt of fs.readdirSync(buildersDir, { withFileTypes: true })) {
|
|
763
|
+
if (!wt.isDirectory())
|
|
764
|
+
continue;
|
|
765
|
+
projectsDirs.push(path.join(buildersDir, wt.name, 'codev', 'projects'));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
for (const projectsDir of projectsDirs) {
|
|
769
|
+
if (!fs.existsSync(projectsDir))
|
|
770
|
+
continue;
|
|
771
|
+
for (const entry of fs.readdirSync(projectsDir, { withFileTypes: true })) {
|
|
772
|
+
if (!entry.isDirectory())
|
|
773
|
+
continue;
|
|
774
|
+
const statusPath = path.join(projectsDir, entry.name, 'status.yaml');
|
|
775
|
+
if (!fs.existsSync(statusPath))
|
|
776
|
+
continue;
|
|
777
|
+
const realPath = fs.realpathSync(statusPath);
|
|
778
|
+
if (seen.has(realPath))
|
|
779
|
+
continue;
|
|
780
|
+
seen.add(realPath);
|
|
781
|
+
let state;
|
|
782
|
+
try {
|
|
783
|
+
state = readState(statusPath);
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
continue; // skip corrupted status files
|
|
787
|
+
}
|
|
788
|
+
for (const [gateName, gateStatus] of Object.entries(state.gates)) {
|
|
789
|
+
if (gateStatus?.status === 'pending' && gateStatus.requested_at) {
|
|
790
|
+
results.push({
|
|
791
|
+
id: state.id,
|
|
792
|
+
title: state.title,
|
|
793
|
+
phase: state.phase,
|
|
794
|
+
gate: gateName,
|
|
795
|
+
requested_at: gateStatus.requested_at,
|
|
796
|
+
statusPath,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
console.log('');
|
|
803
|
+
if (results.length === 0) {
|
|
804
|
+
console.log(chalk.dim('No gates pending approval.'));
|
|
805
|
+
console.log('');
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
// Sort oldest-first so the longest-waiting gates surface at the top.
|
|
809
|
+
results.sort((a, b) => (a.requested_at ?? '').localeCompare(b.requested_at ?? ''));
|
|
810
|
+
console.log(chalk.bold(`${results.length} gate${results.length === 1 ? '' : 's'} pending approval:`));
|
|
811
|
+
console.log('');
|
|
812
|
+
for (const r of results) {
|
|
813
|
+
console.log(` ${chalk.cyan(r.id)} ${chalk.dim('—')} ${r.title}`);
|
|
814
|
+
console.log(` phase: ${r.phase} gate: ${chalk.yellow(r.gate)} requested: ${r.requested_at}`);
|
|
815
|
+
console.log(chalk.dim(` approve: porch approve ${r.id} ${r.gate} --a-human-explicitly-approved-this`));
|
|
816
|
+
console.log('');
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function getArtifactForPhase(workspaceRoot, state, resolver) {
|
|
820
|
+
const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title, resolver);
|
|
652
821
|
switch (state.phase) {
|
|
653
822
|
case 'specify':
|
|
654
823
|
return `codev/specs/${baseName}.md`;
|
|
@@ -665,7 +834,14 @@ function getArtifactForPhase(workspaceRoot, state) {
|
|
|
665
834
|
// ============================================================================
|
|
666
835
|
export async function cli(args) {
|
|
667
836
|
const [command, ...rest] = args;
|
|
837
|
+
// Handle --version / -v before any project resolution — version output
|
|
838
|
+
// must work from any directory and never require a project context.
|
|
839
|
+
if (command === '--version' || command === '-v') {
|
|
840
|
+
console.log(version);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
668
843
|
const workspaceRoot = process.cwd();
|
|
844
|
+
const resolver = getResolver(workspaceRoot);
|
|
669
845
|
// Auto-detect project ID for commands that need it
|
|
670
846
|
function getProjectId(provided) {
|
|
671
847
|
const { id, source } = resolveProjectId(provided, process.cwd(), workspaceRoot);
|
|
@@ -679,6 +855,9 @@ export async function cli(args) {
|
|
|
679
855
|
}
|
|
680
856
|
try {
|
|
681
857
|
switch (command) {
|
|
858
|
+
case 'pending':
|
|
859
|
+
await pending(workspaceRoot);
|
|
860
|
+
break;
|
|
682
861
|
case 'next': {
|
|
683
862
|
const { next: porchNext } = await import('./next.js');
|
|
684
863
|
const result = await porchNext(workspaceRoot, getProjectId(rest[0]));
|
|
@@ -691,28 +870,80 @@ export async function cli(args) {
|
|
|
691
870
|
process.exit(1);
|
|
692
871
|
break;
|
|
693
872
|
case 'status':
|
|
694
|
-
await status(workspaceRoot, getProjectId(rest[0]));
|
|
873
|
+
await status(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
695
874
|
break;
|
|
696
875
|
case 'check':
|
|
697
|
-
await check(workspaceRoot, getProjectId(rest[0]));
|
|
876
|
+
await check(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
698
877
|
break;
|
|
699
|
-
case 'done':
|
|
700
|
-
|
|
878
|
+
case 'done': {
|
|
879
|
+
const doneOpts = {};
|
|
880
|
+
const prIdx = rest.indexOf('--pr');
|
|
881
|
+
const brIdx = rest.indexOf('--branch');
|
|
882
|
+
const mergedIdx = rest.indexOf('--merged');
|
|
883
|
+
if (prIdx !== -1) {
|
|
884
|
+
const val = parseInt(rest[prIdx + 1], 10);
|
|
885
|
+
if (!Number.isInteger(val) || val <= 0)
|
|
886
|
+
throw new Error('--pr requires a positive integer PR number');
|
|
887
|
+
doneOpts.pr = val;
|
|
888
|
+
}
|
|
889
|
+
if (brIdx !== -1) {
|
|
890
|
+
if (!rest[brIdx + 1] || rest[brIdx + 1].startsWith('--'))
|
|
891
|
+
throw new Error('--branch requires a branch name');
|
|
892
|
+
doneOpts.branch = rest[brIdx + 1];
|
|
893
|
+
}
|
|
894
|
+
if (mergedIdx !== -1) {
|
|
895
|
+
const val = parseInt(rest[mergedIdx + 1], 10);
|
|
896
|
+
if (!Number.isInteger(val) || val <= 0)
|
|
897
|
+
throw new Error('--merged requires a positive integer PR number');
|
|
898
|
+
doneOpts.merged = val;
|
|
899
|
+
}
|
|
900
|
+
if (doneOpts.pr !== undefined && doneOpts.merged !== undefined) {
|
|
901
|
+
throw new Error('--pr and --merged are mutually exclusive');
|
|
902
|
+
}
|
|
903
|
+
const hasRecordFlags = doneOpts.pr !== undefined || doneOpts.merged !== undefined;
|
|
904
|
+
// For project ID: use first positional arg, or fall back to auto-detection
|
|
905
|
+
const projectIdArg = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
|
|
906
|
+
await done(workspaceRoot, getProjectId(projectIdArg), resolver, hasRecordFlags ? doneOpts : undefined);
|
|
701
907
|
break;
|
|
908
|
+
}
|
|
702
909
|
case 'gate':
|
|
703
|
-
await gate(workspaceRoot, getProjectId(rest[0]));
|
|
910
|
+
await gate(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
704
911
|
break;
|
|
705
912
|
case 'approve':
|
|
706
913
|
if (!rest[0] || !rest[1])
|
|
707
914
|
throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
|
|
708
915
|
const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
|
|
709
|
-
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag);
|
|
916
|
+
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag, resolver);
|
|
710
917
|
break;
|
|
711
918
|
case 'rollback':
|
|
712
919
|
if (!rest[0] || !rest[1])
|
|
713
920
|
throw new Error('Usage: porch rollback <id> <phase>');
|
|
714
|
-
await rollback(workspaceRoot, rest[0], rest[1]);
|
|
921
|
+
await rollback(workspaceRoot, rest[0], rest[1], resolver);
|
|
922
|
+
break;
|
|
923
|
+
case 'verify': {
|
|
924
|
+
const verifyProjectId = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
|
|
925
|
+
const skipIdx = rest.indexOf('--skip');
|
|
926
|
+
if (skipIdx === -1)
|
|
927
|
+
throw new Error('Usage: porch verify <id> --skip "reason"');
|
|
928
|
+
const skipReason = rest[skipIdx + 1];
|
|
929
|
+
if (!skipReason || skipReason.startsWith('--'))
|
|
930
|
+
throw new Error('--skip requires a reason');
|
|
931
|
+
const pid = getProjectId(verifyProjectId);
|
|
932
|
+
const sp = findStatusPath(workspaceRoot, pid);
|
|
933
|
+
if (!sp)
|
|
934
|
+
throw new Error(`Project ${pid} not found.`);
|
|
935
|
+
const st = readState(sp);
|
|
936
|
+
if (st.phase !== 'verify') {
|
|
937
|
+
throw new Error(`porch verify --skip can only be used in the verify phase (current: ${st.phase}). The PR must be merged first.`);
|
|
938
|
+
}
|
|
939
|
+
st.phase = 'verified';
|
|
940
|
+
st.context = { ...st.context, verify_skip_reason: skipReason };
|
|
941
|
+
await writeStateAndCommit(sp, st, `chore(porch): ${st.id} verify skipped: ${skipReason}`);
|
|
942
|
+
console.log('');
|
|
943
|
+
console.log(chalk.green(`VERIFIED (skipped): ${st.id}`));
|
|
944
|
+
console.log(` Reason: ${skipReason}`);
|
|
715
945
|
break;
|
|
946
|
+
}
|
|
716
947
|
case 'init':
|
|
717
948
|
if (!rest[0] || !rest[1] || !rest[2]) {
|
|
718
949
|
throw new Error('Usage: porch init <protocol> <id> <name>');
|
|
@@ -727,11 +958,18 @@ export async function cli(args) {
|
|
|
727
958
|
console.log(' status [id] Show current state and instructions');
|
|
728
959
|
console.log(' check [id] Run checks for current phase');
|
|
729
960
|
console.log(' done [id] Signal build complete (validates checks, advances)');
|
|
961
|
+
console.log(' done [id] --pr N --branch NAME Record PR creation (no phase advancement)');
|
|
962
|
+
console.log(' done [id] --merged N Mark PR as merged (no phase advancement)');
|
|
730
963
|
console.log(' gate [id] Request human approval');
|
|
964
|
+
console.log(' pending List all gates awaiting approval across projects');
|
|
731
965
|
console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
|
|
966
|
+
console.log(' verify <id> --skip "reason" Skip verification and mark as verified');
|
|
732
967
|
console.log(' rollback <id> <phase> Rewind project to an earlier phase');
|
|
733
968
|
console.log(' init <protocol> <id> <name> Initialize a new project');
|
|
734
969
|
console.log('');
|
|
970
|
+
console.log('Flags:');
|
|
971
|
+
console.log(' --version, -v Print porch (codev) version');
|
|
972
|
+
console.log('');
|
|
735
973
|
console.log('Project ID is auto-detected from worktree path or when exactly one project exists.');
|
|
736
974
|
console.log('');
|
|
737
975
|
process.exit(command && command !== '--help' && command !== '-h' ? 1 : 0);
|