@cluesmith/codev 3.0.0-rc.1 → 3.0.0-rc.10
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-Bs8I7N2s.css} +1 -1
- package/dashboard-dist/assets/index-CkNo2z6t.js +195 -0
- package/dashboard-dist/assets/index-CkNo2z6t.js.map +1 -0
- package/dashboard-dist/dist/assets/index-Bs8I7N2s.css +32 -0
- package/dashboard-dist/dist/assets/index-CkNo2z6t.js +195 -0
- package/dashboard-dist/dist/assets/index-CkNo2z6t.js.map +1 -0
- package/{dashboard → dashboard-dist}/dist/index.html +2 -2
- package/dashboard-dist/index.html +14 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +17 -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/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +10 -5
- package/dist/agent-farm/commands/cleanup.js.map +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.d.ts +4 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +8 -5
- 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 +2 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.js +1 -1
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +49 -10
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +356 -51
- 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 +182 -74
- 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.d.ts.map +1 -1
- package/dist/agent-farm/commands/team-update.js +8 -5
- package/dist/agent-farm/commands/team-update.js.map +1 -1
- package/dist/agent-farm/commands/team.d.ts +1 -1
- package/dist/agent-farm/commands/team.d.ts.map +1 -1
- package/dist/agent-farm/commands/team.js +7 -3
- package/dist/agent-farm/commands/team.js.map +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 +10 -8
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +78 -38
- 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 +15 -13
- 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 +9 -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 +64 -36
- 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 +13 -7
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/types.d.ts +13 -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 +39 -17
- 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/roles.d.ts +11 -4
- package/dist/agent-farm/utils/roles.d.ts.map +1 -1
- package/dist/agent-farm/utils/roles.js +21 -6
- package/dist/agent-farm/utils/roles.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +40 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +16 -45
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +6 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +231 -183
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +121 -31
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -43
- 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 +14 -3
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +150 -4
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/config.d.ts +6 -9
- package/dist/commands/porch/config.d.ts.map +1 -1
- package/dist/commands/porch/config.js +12 -37
- package/dist/commands/porch/config.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 +273 -82
- 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 +142 -75
- 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 +34 -38
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +4 -3
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +21 -21
- package/dist/commands/porch/protocol.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 +100 -9
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +10 -2
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/commands/sync.d.ts +26 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +207 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/update.d.ts +15 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +159 -202
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/config.d.ts +96 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +165 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/forge-contracts.d.ts +100 -0
- package/dist/lib/forge-contracts.d.ts.map +1 -0
- package/dist/lib/forge-contracts.js +35 -0
- package/dist/lib/forge-contracts.js.map +1 -0
- package/dist/lib/forge.d.ts +102 -0
- package/dist/lib/forge.d.ts.map +1 -0
- package/dist/lib/forge.js +376 -0
- package/dist/lib/forge.js.map +1 -0
- package/dist/lib/github.d.ts +68 -60
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +160 -169
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/skeleton.d.ts +15 -5
- package/dist/lib/skeleton.d.ts.map +1 -1
- package/dist/lib/skeleton.js +82 -7
- package/dist/lib/skeleton.js.map +1 -1
- package/dist/lib/team-github.d.ts +7 -3
- package/dist/lib/team-github.d.ts.map +1 -1
- package/dist/lib/team-github.js +29 -19
- 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 +27 -24
- package/scripts/forge/gitea/auth-status.sh +3 -0
- package/scripts/forge/gitea/issue-comment.sh +3 -0
- package/scripts/forge/gitea/issue-list.sh +3 -0
- package/scripts/forge/gitea/issue-view.sh +3 -0
- package/scripts/forge/gitea/pr-exists.sh +6 -0
- package/scripts/forge/gitea/pr-list.sh +3 -0
- package/scripts/forge/gitea/pr-merge.sh +3 -0
- package/scripts/forge/gitea/pr-view.sh +3 -0
- package/scripts/forge/gitea/recently-closed.sh +3 -0
- package/scripts/forge/gitea/recently-merged.sh +3 -0
- package/scripts/forge/gitea/repo-archive.sh +11 -0
- package/scripts/forge/gitea/user-identity.sh +3 -0
- package/scripts/forge/github/auth-status.sh +4 -0
- package/scripts/forge/github/issue-comment.sh +5 -0
- package/scripts/forge/github/issue-list.sh +4 -0
- package/scripts/forge/github/issue-view.sh +5 -0
- package/scripts/forge/github/on-it-timestamps.sh +8 -0
- package/scripts/forge/github/pr-diff.sh +10 -0
- package/scripts/forge/github/pr-exists.sh +7 -0
- package/scripts/forge/github/pr-list.sh +4 -0
- package/scripts/forge/github/pr-merge.sh +5 -0
- package/scripts/forge/github/pr-search.sh +5 -0
- package/scripts/forge/github/pr-view.sh +11 -0
- package/scripts/forge/github/recently-closed.sh +15 -0
- package/scripts/forge/github/recently-merged.sh +15 -0
- package/scripts/forge/github/repo-archive.sh +10 -0
- package/scripts/forge/github/team-activity.sh +5 -0
- package/scripts/forge/github/user-identity.sh +4 -0
- package/scripts/forge/gitlab/auth-status.sh +3 -0
- package/scripts/forge/gitlab/issue-comment.sh +3 -0
- package/scripts/forge/gitlab/issue-list.sh +3 -0
- package/scripts/forge/gitlab/issue-view.sh +3 -0
- package/scripts/forge/gitlab/pr-diff.sh +3 -0
- package/scripts/forge/gitlab/pr-exists.sh +5 -0
- package/scripts/forge/gitlab/pr-list.sh +3 -0
- package/scripts/forge/gitlab/pr-merge.sh +3 -0
- package/scripts/forge/gitlab/pr-search.sh +3 -0
- package/scripts/forge/gitlab/pr-view.sh +3 -0
- package/scripts/forge/gitlab/recently-closed.sh +3 -0
- package/scripts/forge/gitlab/recently-merged.sh +3 -0
- package/scripts/forge/gitlab/repo-archive.sh +12 -0
- package/scripts/forge/gitlab/user-identity.sh +3 -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 +2 -2
- package/skeleton/protocols/air/protocol.md +1 -2
- package/skeleton/protocols/aspir/builder-prompt.md +28 -6
- 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 +1 -1
- 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 +72 -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/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/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 +55 -43
- package/skeleton/roles/builder.md +22 -14
- package/skeleton/templates/AGENTS.md +4 -3
- package/skeleton/templates/CLAUDE.md +4 -3
- package/skeleton/templates/cheatsheet.md +11 -12
- package/templates/3d-viewer.html +16 -18
- package/templates/tower.html +1 -1
- package/dashboard/dist/assets/index-CiIBsixF.js +0 -194
- package/dashboard/dist/assets/index-CiIBsixF.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,11 +8,13 @@ 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';
|
|
17
|
+
import { loadConfig } from '../../lib/config.js';
|
|
16
18
|
// ============================================================================
|
|
17
19
|
// Output Helpers
|
|
18
20
|
// ============================================================================
|
|
@@ -23,12 +25,32 @@ function header(text) {
|
|
|
23
25
|
function section(title, content) {
|
|
24
26
|
return `\n${chalk.bold(title)}:\n${content}`;
|
|
25
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
|
+
}
|
|
26
48
|
/**
|
|
27
49
|
* Log override/skip notices before running checks.
|
|
28
50
|
* Only emits output when overrides are actually in use.
|
|
29
51
|
* @param phaseCheckNames - original check names from the protocol phase
|
|
30
52
|
* @param resolvedChecks - checks after applying overrides (skipped ones absent)
|
|
31
|
-
* @param overrides - raw override map from
|
|
53
|
+
* @param overrides - raw override map from .codev/config.json (null if not configured)
|
|
32
54
|
*/
|
|
33
55
|
function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
|
|
34
56
|
if (!overrides)
|
|
@@ -38,7 +60,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
|
|
|
38
60
|
if (!override)
|
|
39
61
|
continue;
|
|
40
62
|
if (override.skip) {
|
|
41
|
-
console.log(chalk.yellow(` ⚠ Check "${name}" skipped (
|
|
63
|
+
console.log(chalk.yellow(` ⚠ Check "${name}" skipped (.codev/config.json)`));
|
|
42
64
|
}
|
|
43
65
|
else if (override.command || override.cwd) {
|
|
44
66
|
const parts = [];
|
|
@@ -57,7 +79,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
|
|
|
57
79
|
* porch status <id>
|
|
58
80
|
* Shows current state and prescriptive next steps.
|
|
59
81
|
*/
|
|
60
|
-
export async function status(workspaceRoot, projectId) {
|
|
82
|
+
export async function status(workspaceRoot, projectId, resolver) {
|
|
61
83
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
62
84
|
if (!statusPath) {
|
|
63
85
|
throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
|
|
@@ -78,7 +100,8 @@ export async function status(workspaceRoot, projectId) {
|
|
|
78
100
|
// Status icons
|
|
79
101
|
const icon = (status) => {
|
|
80
102
|
switch (status) {
|
|
81
|
-
case '
|
|
103
|
+
case 'verified': return chalk.green('✓');
|
|
104
|
+
case 'complete': return chalk.green('✓'); // backward compat
|
|
82
105
|
case 'in_progress': return chalk.yellow('►');
|
|
83
106
|
default: return chalk.gray('○');
|
|
84
107
|
}
|
|
@@ -94,11 +117,11 @@ export async function status(workspaceRoot, projectId) {
|
|
|
94
117
|
if (currentPlanPhase) {
|
|
95
118
|
console.log('');
|
|
96
119
|
console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
97
|
-
// Show phase content from plan
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
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);
|
|
102
125
|
if (phaseContent) {
|
|
103
126
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
|
|
104
127
|
}
|
|
@@ -145,11 +168,16 @@ export async function status(workspaceRoot, projectId) {
|
|
|
145
168
|
* porch check <id>
|
|
146
169
|
* Runs the phase checks and reports results.
|
|
147
170
|
*/
|
|
148
|
-
export async function check(workspaceRoot, projectId) {
|
|
171
|
+
export async function check(workspaceRoot, projectId, resolver) {
|
|
149
172
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
150
173
|
if (!statusPath) {
|
|
151
174
|
throw new Error(`Project ${projectId} not found.`);
|
|
152
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);
|
|
153
181
|
const state = readState(statusPath);
|
|
154
182
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
155
183
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
@@ -160,20 +188,20 @@ export async function check(workspaceRoot, projectId) {
|
|
|
160
188
|
console.log(chalk.dim('No checks defined for this phase.'));
|
|
161
189
|
return;
|
|
162
190
|
}
|
|
163
|
-
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) };
|
|
164
192
|
console.log('');
|
|
165
193
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
166
194
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
167
195
|
console.log('');
|
|
168
196
|
if (Object.keys(checks).length === 0) {
|
|
169
|
-
console.log(chalk.dim(' (all checks skipped via
|
|
197
|
+
console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
|
|
170
198
|
console.log('');
|
|
171
199
|
console.log(chalk.green('RESULT: ALL CHECKS PASSED'));
|
|
172
200
|
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
173
201
|
console.log('');
|
|
174
202
|
return;
|
|
175
203
|
}
|
|
176
|
-
const results = await runPhaseChecks(checks,
|
|
204
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
177
205
|
console.log(formatCheckResults(results));
|
|
178
206
|
console.log('');
|
|
179
207
|
if (allChecksPassed(results)) {
|
|
@@ -190,17 +218,50 @@ export async function check(workspaceRoot, projectId) {
|
|
|
190
218
|
* porch done <id>
|
|
191
219
|
* Advances to next phase if checks pass. Refuses if checks fail.
|
|
192
220
|
*/
|
|
193
|
-
export async function done(workspaceRoot, projectId) {
|
|
221
|
+
export async function done(workspaceRoot, projectId, resolver, options) {
|
|
194
222
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
195
223
|
if (!statusPath) {
|
|
196
224
|
throw new Error(`Project ${projectId} not found.`);
|
|
197
225
|
}
|
|
198
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
|
+
}
|
|
199
256
|
const protocol = loadProtocol(workspaceRoot, state.protocol);
|
|
200
257
|
const overrides = loadCheckOverrides(workspaceRoot);
|
|
201
258
|
const phaseConfig = getPhaseConfig(protocol, state.phase);
|
|
202
259
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
203
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);
|
|
204
265
|
// Run checks first — but skip if the gate was just approved (approve already ran them)
|
|
205
266
|
if (phaseCheckNames.length > 0) {
|
|
206
267
|
const gate = getPhaseGate(protocol, state.phase);
|
|
@@ -212,12 +273,12 @@ export async function done(workspaceRoot, projectId) {
|
|
|
212
273
|
console.log(chalk.dim('Checks skipped (gate approved <60s ago).'));
|
|
213
274
|
}
|
|
214
275
|
else {
|
|
215
|
-
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) };
|
|
216
277
|
console.log('');
|
|
217
278
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
218
279
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
219
280
|
if (Object.keys(checks).length > 0) {
|
|
220
|
-
const results = await runPhaseChecks(checks,
|
|
281
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
221
282
|
console.log(formatCheckResults(results));
|
|
222
283
|
if (!allChecksPassed(results)) {
|
|
223
284
|
console.log('');
|
|
@@ -227,44 +288,87 @@ export async function done(workspaceRoot, projectId) {
|
|
|
227
288
|
}
|
|
228
289
|
}
|
|
229
290
|
else {
|
|
230
|
-
console.log(chalk.dim(' (all checks skipped via
|
|
291
|
+
console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
|
|
231
292
|
}
|
|
232
293
|
}
|
|
233
294
|
}
|
|
234
295
|
// For build_verify phases: mark build as complete for verification
|
|
235
296
|
if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
|
|
236
297
|
state.build_complete = true;
|
|
237
|
-
|
|
298
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${state.phase} build-complete`);
|
|
238
299
|
console.log('');
|
|
239
300
|
console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
|
|
240
301
|
console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
|
|
241
302
|
return;
|
|
242
303
|
}
|
|
243
|
-
// Enforce
|
|
304
|
+
// Enforce verification for build_verify phases (config-aware)
|
|
244
305
|
const verifyConfig = getVerifyConfig(protocol, state.phase);
|
|
245
306
|
if (verifyConfig) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
307
|
+
// Resolve effective models from config (overrides protocol defaults)
|
|
308
|
+
let effectiveModels = verifyConfig.models;
|
|
309
|
+
let consultMode = 'normal';
|
|
310
|
+
try {
|
|
311
|
+
const config = loadConfig(workspaceRoot);
|
|
312
|
+
const configModels = config.porch?.consultation?.models;
|
|
313
|
+
if (configModels !== undefined) {
|
|
314
|
+
if (configModels === 'none') {
|
|
315
|
+
consultMode = 'none';
|
|
316
|
+
}
|
|
317
|
+
else if (configModels === 'parent') {
|
|
318
|
+
consultMode = 'parent';
|
|
319
|
+
}
|
|
320
|
+
else if (Array.isArray(configModels)) {
|
|
321
|
+
effectiveModels = configModels;
|
|
322
|
+
}
|
|
323
|
+
else if (typeof configModels === 'string') {
|
|
324
|
+
effectiveModels = [configModels];
|
|
325
|
+
}
|
|
255
326
|
}
|
|
256
327
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
328
|
+
catch {
|
|
329
|
+
// Config load failed — use protocol defaults
|
|
330
|
+
}
|
|
331
|
+
// "none" mode: skip verification
|
|
332
|
+
if (consultMode === 'none') {
|
|
333
|
+
console.log(chalk.dim(' (consultation skipped — configured: none)'));
|
|
334
|
+
}
|
|
335
|
+
else if (consultMode === 'parent') {
|
|
336
|
+
// "parent" mode: verification is handled by architect gate, not review files
|
|
337
|
+
console.log(chalk.dim(' (consultation delegated to architect — configured: parent)'));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Normal mode: check for review files from effective models
|
|
341
|
+
const projectDir = getProjectDir(workspaceRoot, state.id, state.title);
|
|
342
|
+
const phase = state.current_plan_phase || state.phase;
|
|
343
|
+
const missingModels = [];
|
|
344
|
+
for (const model of effectiveModels) {
|
|
345
|
+
// Look for any review file for this model+phase (any iteration)
|
|
346
|
+
const pattern = path.join(projectDir, `${state.id}-${phase}-iter*-${model}.txt`);
|
|
347
|
+
const matches = globSync(pattern);
|
|
348
|
+
if (matches.length === 0) {
|
|
349
|
+
missingModels.push(model);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (missingModels.length > 0) {
|
|
353
|
+
console.log('');
|
|
354
|
+
console.log(chalk.red('VERIFICATION REQUIRED'));
|
|
355
|
+
console.log(`\n ${effectiveModels.length}-way review not completed. Missing: ${missingModels.join(', ')}`);
|
|
356
|
+
console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
263
359
|
}
|
|
264
360
|
}
|
|
265
|
-
// Check for gate
|
|
361
|
+
// Check for gate — auto-request if not yet requested
|
|
266
362
|
const gate = getPhaseGate(protocol, state.phase);
|
|
267
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
|
+
}
|
|
268
372
|
console.log('');
|
|
269
373
|
console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
|
|
270
374
|
console.log(`\n Run: porch gate ${state.id}`);
|
|
@@ -284,13 +388,13 @@ export async function done(workspaceRoot, projectId) {
|
|
|
284
388
|
}
|
|
285
389
|
}
|
|
286
390
|
// Advance to next protocol phase
|
|
287
|
-
advanceProtocolPhase(workspaceRoot, state, protocol, statusPath);
|
|
391
|
+
await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, scopedResolver);
|
|
288
392
|
}
|
|
289
|
-
function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
393
|
+
async function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver) {
|
|
290
394
|
const nextPhase = getNextPhase(protocol, state.phase);
|
|
291
395
|
if (!nextPhase) {
|
|
292
|
-
state.phase = '
|
|
293
|
-
|
|
396
|
+
state.phase = 'verified';
|
|
397
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} protocol complete`);
|
|
294
398
|
console.log('');
|
|
295
399
|
console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
|
|
296
400
|
console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
|
|
@@ -301,16 +405,17 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
301
405
|
state.iteration = 1;
|
|
302
406
|
// If entering a phased phase (implement), extract plan phases
|
|
303
407
|
if (isPhased(protocol, nextPhase.id)) {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
308
413
|
if (state.plan_phases.length > 0) {
|
|
309
414
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
310
415
|
}
|
|
311
416
|
}
|
|
312
417
|
}
|
|
313
|
-
|
|
418
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${nextPhase.id} phase-transition`);
|
|
314
419
|
console.log('');
|
|
315
420
|
console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
|
|
316
421
|
// If we just entered implement phase, show phase 1 info and the critical warning
|
|
@@ -319,11 +424,11 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
319
424
|
const nextPlanPhase = state.plan_phases[1];
|
|
320
425
|
console.log('');
|
|
321
426
|
console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
|
|
322
|
-
// Show phase content from plan
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
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);
|
|
327
432
|
if (phaseContent) {
|
|
328
433
|
console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
|
|
329
434
|
}
|
|
@@ -347,7 +452,7 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
|
|
|
347
452
|
* porch gate <id>
|
|
348
453
|
* Requests human approval for current gate.
|
|
349
454
|
*/
|
|
350
|
-
export async function gate(workspaceRoot, projectId) {
|
|
455
|
+
export async function gate(workspaceRoot, projectId, resolver) {
|
|
351
456
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
352
457
|
if (!statusPath) {
|
|
353
458
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -366,22 +471,22 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
366
471
|
}
|
|
367
472
|
if (!state.gates[gateName].requested_at) {
|
|
368
473
|
state.gates[gateName].requested_at = new Date().toISOString();
|
|
369
|
-
|
|
474
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-requested`);
|
|
370
475
|
}
|
|
371
476
|
console.log('');
|
|
372
477
|
console.log(chalk.bold(`GATE: ${gateName}`));
|
|
373
478
|
console.log('');
|
|
374
479
|
// Show relevant artifact and open it for review
|
|
375
|
-
const artifact = getArtifactForPhase(workspaceRoot, state);
|
|
480
|
+
const artifact = getArtifactForPhase(workspaceRoot, state, resolver);
|
|
376
481
|
if (artifact) {
|
|
377
482
|
const fullPath = path.join(workspaceRoot, artifact);
|
|
378
483
|
if (fs.existsSync(fullPath)) {
|
|
379
484
|
console.log(` Artifact: ${artifact}`);
|
|
380
485
|
console.log('');
|
|
381
486
|
console.log(chalk.cyan(' Opening artifact for human review...'));
|
|
382
|
-
// Use
|
|
487
|
+
// Use afx open to display in annotation viewer
|
|
383
488
|
const { spawn } = await import('node:child_process');
|
|
384
|
-
spawn('
|
|
489
|
+
spawn('afx', ['open', fullPath], {
|
|
385
490
|
stdio: 'inherit',
|
|
386
491
|
detached: true
|
|
387
492
|
}).unref();
|
|
@@ -400,15 +505,34 @@ export async function gate(workspaceRoot, projectId) {
|
|
|
400
505
|
* porch approve <id> <gate> --a-human-explicitly-approved-this
|
|
401
506
|
* Human approves a gate. Requires explicit flag to prevent automated approvals.
|
|
402
507
|
*/
|
|
403
|
-
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag) {
|
|
508
|
+
export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag, resolver) {
|
|
404
509
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
405
510
|
if (!statusPath) {
|
|
406
511
|
throw new Error(`Project ${projectId} not found.`);
|
|
407
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);
|
|
408
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)
|
|
409
524
|
if (!state.gates[gateName]) {
|
|
410
|
-
const
|
|
411
|
-
|
|
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
|
+
}
|
|
412
536
|
}
|
|
413
537
|
if (state.gates[gateName].status === 'approved') {
|
|
414
538
|
console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
|
|
@@ -432,12 +556,12 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
432
556
|
const phaseCheckNames = phaseConfig?.checks ?? [];
|
|
433
557
|
const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
|
|
434
558
|
if (phaseCheckNames.length > 0) {
|
|
435
|
-
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) };
|
|
436
560
|
console.log('');
|
|
437
561
|
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
438
562
|
logCheckOverrides(phaseCheckNames, checks, overrides);
|
|
439
563
|
if (Object.keys(checks).length > 0) {
|
|
440
|
-
const results = await runPhaseChecks(checks,
|
|
564
|
+
const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
|
|
441
565
|
console.log(formatCheckResults(results));
|
|
442
566
|
if (!allChecksPassed(results)) {
|
|
443
567
|
console.log('');
|
|
@@ -447,22 +571,32 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
|
|
|
447
571
|
}
|
|
448
572
|
}
|
|
449
573
|
else {
|
|
450
|
-
console.log(chalk.dim(' (all checks skipped via
|
|
574
|
+
console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
|
|
451
575
|
}
|
|
452
576
|
}
|
|
453
577
|
state.gates[gateName].status = 'approved';
|
|
454
578
|
state.gates[gateName].approved_at = new Date().toISOString();
|
|
455
|
-
|
|
579
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-approved`);
|
|
456
580
|
console.log('');
|
|
457
581
|
console.log(chalk.green(`Gate ${gateName} approved.`));
|
|
458
|
-
|
|
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
|
+
}
|
|
459
593
|
console.log('');
|
|
460
594
|
}
|
|
461
595
|
/**
|
|
462
596
|
* porch rollback <id> <phase>
|
|
463
597
|
* Rewinds project to an earlier phase, clearing downstream gates and resetting build state.
|
|
464
598
|
*/
|
|
465
|
-
export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
599
|
+
export async function rollback(workspaceRoot, projectId, targetPhase, resolver) {
|
|
466
600
|
const statusPath = findStatusPath(workspaceRoot, projectId);
|
|
467
601
|
if (!statusPath) {
|
|
468
602
|
throw new Error(`Project ${projectId} not found.`);
|
|
@@ -479,7 +613,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
479
613
|
const currentIndex = protocol.phases.findIndex(p => p.id === state.phase);
|
|
480
614
|
const targetIndex = protocol.phases.findIndex(p => p.id === targetPhase);
|
|
481
615
|
// Handle completed projects (phase not in protocol phases array)
|
|
482
|
-
if (state.phase === 'complete') {
|
|
616
|
+
if (state.phase === 'verified' || state.phase === 'complete') {
|
|
483
617
|
// Allow rollback from complete state to any valid phase
|
|
484
618
|
}
|
|
485
619
|
else if (currentIndex === -1) {
|
|
@@ -502,11 +636,12 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
502
636
|
state.iteration = 1;
|
|
503
637
|
state.build_complete = false;
|
|
504
638
|
state.history = [];
|
|
505
|
-
// If rolling back to a phased phase, re-extract plan phases
|
|
639
|
+
// If rolling back to a phased phase, re-extract plan phases
|
|
506
640
|
if (isPhased(protocol, targetPhase)) {
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
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);
|
|
510
645
|
if (state.plan_phases.length > 0) {
|
|
511
646
|
state.current_plan_phase = state.plan_phases[0].id;
|
|
512
647
|
}
|
|
@@ -523,7 +658,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
523
658
|
state.plan_phases = [];
|
|
524
659
|
state.current_plan_phase = null;
|
|
525
660
|
}
|
|
526
|
-
|
|
661
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} rollback ${previousPhase} → ${targetPhase}`);
|
|
527
662
|
console.log('');
|
|
528
663
|
console.log(chalk.green(`ROLLED BACK: ${previousPhase} → ${targetPhase}`));
|
|
529
664
|
console.log(` Project: ${state.id}`);
|
|
@@ -536,7 +671,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
|
|
|
536
671
|
* Initialize a new project.
|
|
537
672
|
*
|
|
538
673
|
* Idempotent: if status.yaml already exists, preserves it and reports
|
|
539
|
-
* current state. This supports `
|
|
674
|
+
* current state. This supports `afx spawn --resume` where the builder
|
|
540
675
|
* may re-run `porch init` after a session restart.
|
|
541
676
|
*/
|
|
542
677
|
export async function init(workspaceRoot, protocolName, projectId, projectName) {
|
|
@@ -572,7 +707,7 @@ export async function init(workspaceRoot, protocolName, projectId, projectName)
|
|
|
572
707
|
return;
|
|
573
708
|
}
|
|
574
709
|
const state = createInitialState(protocol, projectId, projectName, workspaceRoot);
|
|
575
|
-
|
|
710
|
+
await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} init ${protocolName}`);
|
|
576
711
|
console.log('');
|
|
577
712
|
console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
|
|
578
713
|
console.log(` Protocol: ${protocolName}`);
|
|
@@ -611,8 +746,8 @@ function getNextAction(state, protocol) {
|
|
|
611
746
|
}
|
|
612
747
|
return `Complete the phase work, then run: porch done ${state.id}`;
|
|
613
748
|
}
|
|
614
|
-
function getArtifactForPhase(workspaceRoot, state) {
|
|
615
|
-
const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title);
|
|
749
|
+
function getArtifactForPhase(workspaceRoot, state, resolver) {
|
|
750
|
+
const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title, resolver);
|
|
616
751
|
switch (state.phase) {
|
|
617
752
|
case 'specify':
|
|
618
753
|
return `codev/specs/${baseName}.md`;
|
|
@@ -630,6 +765,7 @@ function getArtifactForPhase(workspaceRoot, state) {
|
|
|
630
765
|
export async function cli(args) {
|
|
631
766
|
const [command, ...rest] = args;
|
|
632
767
|
const workspaceRoot = process.cwd();
|
|
768
|
+
const resolver = getResolver(workspaceRoot);
|
|
633
769
|
// Auto-detect project ID for commands that need it
|
|
634
770
|
function getProjectId(provided) {
|
|
635
771
|
const { id, source } = resolveProjectId(provided, process.cwd(), workspaceRoot);
|
|
@@ -655,28 +791,80 @@ export async function cli(args) {
|
|
|
655
791
|
process.exit(1);
|
|
656
792
|
break;
|
|
657
793
|
case 'status':
|
|
658
|
-
await status(workspaceRoot, getProjectId(rest[0]));
|
|
794
|
+
await status(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
659
795
|
break;
|
|
660
796
|
case 'check':
|
|
661
|
-
await check(workspaceRoot, getProjectId(rest[0]));
|
|
797
|
+
await check(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
662
798
|
break;
|
|
663
|
-
case 'done':
|
|
664
|
-
|
|
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);
|
|
665
828
|
break;
|
|
829
|
+
}
|
|
666
830
|
case 'gate':
|
|
667
|
-
await gate(workspaceRoot, getProjectId(rest[0]));
|
|
831
|
+
await gate(workspaceRoot, getProjectId(rest[0]), resolver);
|
|
668
832
|
break;
|
|
669
833
|
case 'approve':
|
|
670
834
|
if (!rest[0] || !rest[1])
|
|
671
835
|
throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
|
|
672
836
|
const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
|
|
673
|
-
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag);
|
|
837
|
+
await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag, resolver);
|
|
674
838
|
break;
|
|
675
839
|
case 'rollback':
|
|
676
840
|
if (!rest[0] || !rest[1])
|
|
677
841
|
throw new Error('Usage: porch rollback <id> <phase>');
|
|
678
|
-
await rollback(workspaceRoot, rest[0], rest[1]);
|
|
842
|
+
await rollback(workspaceRoot, rest[0], rest[1], resolver);
|
|
679
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
|
+
}
|
|
680
868
|
case 'init':
|
|
681
869
|
if (!rest[0] || !rest[1] || !rest[2]) {
|
|
682
870
|
throw new Error('Usage: porch init <protocol> <id> <name>');
|
|
@@ -691,8 +879,11 @@ export async function cli(args) {
|
|
|
691
879
|
console.log(' status [id] Show current state and instructions');
|
|
692
880
|
console.log(' check [id] Run checks for current phase');
|
|
693
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)');
|
|
694
884
|
console.log(' gate [id] Request human approval');
|
|
695
885
|
console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
|
|
886
|
+
console.log(' verify <id> --skip "reason" Skip verification and mark as verified');
|
|
696
887
|
console.log(' rollback <id> <phase> Rewind project to an earlier phase');
|
|
697
888
|
console.log(' init <protocol> <id> <name> Initialize a new project');
|
|
698
889
|
console.log('');
|