@webpresso/agent-kit 3.1.30 → 3.2.0
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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +179 -19
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +8 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +43 -1
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +65 -10
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +36 -11
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +62 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +10 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +34 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
- package/dist/esm/cli/commands/agent-launch.js +82 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +17 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +6 -3
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +1 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
- package/dist/esm/cli/commands/opencode-rotate.js +135 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +729 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +3 -3
- package/dist/esm/hooks/doctor.js +30 -15
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +23 -8
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +4 -1
- package/dist/esm/mcp/tools/audit.js +30 -0
- package/dist/esm/mcp/tools/audits.d.ts +3 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/review/authority.js +13 -5
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +107 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.js +26 -9
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +87 -6
- package/dist/esm/review/execution/supervisor.js +5 -0
- package/dist/esm/review/execution/types.d.ts +8 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +12 -0
- package/dist/esm/review/subject.js +74 -22
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/status/snapshot.js +30 -19
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -1
- package/dist/esm/test-helpers/global-setup.js +68 -24
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +16 -13
|
@@ -2,6 +2,7 @@ import type { CAC } from "cac";
|
|
|
2
2
|
import { type DashPermissionMode, type DashProvider } from "#cli/commands/dash/provider-capabilities.js";
|
|
3
3
|
import { type RuntimeCommandOptions } from "#runtime/executor.js";
|
|
4
4
|
import { type SecretsConfig } from "#runtime/secrets-config.js";
|
|
5
|
+
import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
|
|
5
6
|
export declare const AGENT_LAUNCH_COMMANDS: readonly ["claude", "codex", "grok", "opencode"];
|
|
6
7
|
export type AgentLaunchCommand = DashProvider;
|
|
7
8
|
export declare const DEFAULT_AGENT_SECRET_PROFILE = "preview";
|
|
@@ -18,6 +19,10 @@ export interface AgentLaunchDeps {
|
|
|
18
19
|
error?: Error;
|
|
19
20
|
};
|
|
20
21
|
readonly ensureCodexSkillSurface?: (cwd: string, env?: NodeJS.ProcessEnv) => string | null;
|
|
22
|
+
readonly resolveOpencodeAccounts?: typeof resolveOpencodeAccountsGracefully;
|
|
23
|
+
readonly materializeGlobalOpencodeAuth?: (key: string) => void;
|
|
24
|
+
readonly readActiveKey?: () => string | undefined;
|
|
25
|
+
readonly recordCooldown?: (canonicalId: string) => Promise<unknown>;
|
|
21
26
|
}
|
|
22
27
|
export declare function buildDefaultAgentArgs(command: AgentLaunchCommand, args?: readonly string[], permissionMode?: DashPermissionMode): readonly string[];
|
|
23
28
|
export declare function isAgentLaunchCommand(command: string | undefined): command is AgentLaunchCommand;
|
|
@@ -29,5 +34,5 @@ export declare function buildAgentRuntimeCommandOptions(input: {
|
|
|
29
34
|
readonly env?: NodeJS.ProcessEnv;
|
|
30
35
|
readonly secretsConfig: SecretsConfig | null;
|
|
31
36
|
}): RuntimeCommandOptions;
|
|
32
|
-
export declare function runAgentLaunchCommand(command: AgentLaunchCommand, args?: readonly string[], options?: AgentLaunchOptions, deps?: AgentLaunchDeps): number
|
|
37
|
+
export declare function runAgentLaunchCommand(command: AgentLaunchCommand, args?: readonly string[], options?: AgentLaunchOptions, deps?: AgentLaunchDeps): Promise<number>;
|
|
33
38
|
export declare function registerAgentLaunchCommand(cli: CAC, command: AgentLaunchCommand): void;
|
|
@@ -7,6 +7,11 @@ import { WP_AGENT_LAUNCHED_VIA_WP_ENV, WP_AGENT_LAUNCHED_VIA_WP_VALUE, } from "#
|
|
|
7
7
|
import { readSecretsConfig } from "#runtime/secrets-config.js";
|
|
8
8
|
import { resolveFreshAgentKitPackageRoot } from "#cli/commands/init/package-root";
|
|
9
9
|
import { ensureCodexUserPlugin } from "#cli/commands/init/scaffolders/codex-plugin/index.js";
|
|
10
|
+
import { resolveOpencodeAccountsGracefully } from "#runtime/opencode-account-materializer.js";
|
|
11
|
+
import { loadOpencodeAccountAvailability, pruneExpiredOpencodeAccountSuppressions, } from "#review/availability.js";
|
|
12
|
+
import { selectActiveOpencodeAccount } from "#review/opencode-account.js";
|
|
13
|
+
import { materializeOpencodeGoAuth } from "#review/opencode-auth.js";
|
|
14
|
+
import { describeOpencodeRotate, OPENCODE_ROTATE_FLAG, rotateActiveOpencodeAccount, } from "#cli/commands/opencode-rotate.js";
|
|
10
15
|
export const AGENT_LAUNCH_COMMANDS = DASH_PROVIDERS;
|
|
11
16
|
export const DEFAULT_AGENT_SECRET_PROFILE = "preview";
|
|
12
17
|
export const AGENT_SECRET_PROFILE_ENV = "WP_AGENT_SECRET_PROFILE";
|
|
@@ -83,7 +88,67 @@ function ensureCodexSkillSurface(cwd, env = process.env) {
|
|
|
83
88
|
return `Codex plugin refresh failed (${result.kind}). Run \`wp install codex\` and retry.`;
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Choose the active opencode-go account and materialize its key into the
|
|
93
|
+
* global opencode `auth.json` before the TUI launches.
|
|
94
|
+
*
|
|
95
|
+
* Honest limitation: interactive opencode is a long-lived TUI — the account is
|
|
96
|
+
* chosen once at launch and a mid-session dollar-cap is NOT auto-swapped. A
|
|
97
|
+
* plain relaunch does NOT rotate either: nothing on this path records a
|
|
98
|
+
* cooldown, so an exhausted account stays "first non-suppressed" and gets
|
|
99
|
+
* re-picked every time. Run `wp opencode --wp-rotate` to cool the current
|
|
100
|
+
* account and launch on the next one. Only the review gate does automatic
|
|
101
|
+
* in-flight failover, because only it can observe the usage-limit response.
|
|
102
|
+
*
|
|
103
|
+
* Never throws: a Doppler outage or materialize failure must not block the
|
|
104
|
+
* TUI from launching, so every failure is logged and swallowed.
|
|
105
|
+
*/
|
|
106
|
+
function selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile) {
|
|
107
|
+
try {
|
|
108
|
+
const resolution = (deps.resolveOpencodeAccounts ?? resolveOpencodeAccountsGracefully)({
|
|
109
|
+
cwd,
|
|
110
|
+
secretProfile,
|
|
111
|
+
});
|
|
112
|
+
if (!resolution.ok)
|
|
113
|
+
return;
|
|
114
|
+
const pruned = pruneExpiredOpencodeAccountSuppressions(loadOpencodeAccountAvailability());
|
|
115
|
+
const selection = selectActiveOpencodeAccount(resolution.accounts, pruned.state);
|
|
116
|
+
if (!selection)
|
|
117
|
+
return;
|
|
118
|
+
(deps.materializeGlobalOpencodeAuth ?? ((key) => materializeOpencodeGoAuth(key)))(selection.account.key);
|
|
119
|
+
if (selection.cooled) {
|
|
120
|
+
console.error(`wp opencode: all OpenCode Go accounts are within their usage cooldown; launching on the soonest-expiring account (${selection.account.id}).`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
console.error(`wp opencode: account selection failed, launching without it (${message}).`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run the opencode-only pre-launch credential step and return the args to
|
|
130
|
+
* forward to the binary. `--wp-rotate` is consumed here and must never reach
|
|
131
|
+
* opencode, which would treat it as a prompt.
|
|
132
|
+
*/
|
|
133
|
+
async function prepareOpencodeLaunch(cwd, args, secretsConfig, deps, secretProfile) {
|
|
134
|
+
if (!args.includes(OPENCODE_ROTATE_FLAG)) {
|
|
135
|
+
if (secretsConfig !== null)
|
|
136
|
+
selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile);
|
|
137
|
+
return args;
|
|
138
|
+
}
|
|
139
|
+
const outcome = await rotateActiveOpencodeAccount(cwd, secretProfile, deps);
|
|
140
|
+
for (const line of describeOpencodeRotate(outcome))
|
|
141
|
+
console.error(line);
|
|
142
|
+
// A successful rotation already materialized the next account, so the normal
|
|
143
|
+
// launch-time selection is skipped to avoid a second, redundant write. When
|
|
144
|
+
// rotation could not run, fall through to that selection so the launch still
|
|
145
|
+
// gets the best account it can rather than whatever the store happens to hold.
|
|
146
|
+
if (outcome.kind === "unavailable" && secretsConfig !== null) {
|
|
147
|
+
selectAndMaterializeOpencodeAccount(cwd, deps, secretProfile);
|
|
148
|
+
}
|
|
149
|
+
return args.filter((arg) => arg !== OPENCODE_ROTATE_FLAG);
|
|
150
|
+
}
|
|
151
|
+
export async function runAgentLaunchCommand(command, args = [], options = {}, deps = {}) {
|
|
87
152
|
const cwd = options.cwd ?? process.cwd();
|
|
88
153
|
if (command === "codex") {
|
|
89
154
|
const skillSurfaceError = (deps.ensureCodexSkillSurface ?? ensureCodexSkillSurface)(cwd, options.env ?? process.env);
|
|
@@ -93,9 +158,19 @@ export function runAgentLaunchCommand(command, args = [], options = {}, deps = {
|
|
|
93
158
|
}
|
|
94
159
|
}
|
|
95
160
|
const secretsConfig = (deps.readSecretsConfig ?? readSecretsConfig)(cwd);
|
|
161
|
+
// Account discovery must resolve secrets through the same profile the launch
|
|
162
|
+
// itself uses (below, via buildAgentRuntimeCommandOptions). Without it no
|
|
163
|
+
// environment selector reaches the secret manager, which then falls back to
|
|
164
|
+
// its own ambient, path-scoped config — that resolves only in a checkout that
|
|
165
|
+
// happens to sit under the scoped directory and fails in every managed
|
|
166
|
+
// worktree, CI clone, and fresh checkout.
|
|
167
|
+
const secretProfile = resolveAgentSecretProfile(options.env ?? process.env);
|
|
168
|
+
const launchArgs = command === "opencode"
|
|
169
|
+
? await prepareOpencodeLaunch(cwd, args, secretsConfig, deps, secretProfile)
|
|
170
|
+
: args;
|
|
96
171
|
const runtimeOptions = buildAgentRuntimeCommandOptions({
|
|
97
172
|
command,
|
|
98
|
-
args,
|
|
173
|
+
args: launchArgs,
|
|
99
174
|
cwd,
|
|
100
175
|
env: options.env ?? process.env,
|
|
101
176
|
secretsConfig,
|
|
@@ -108,8 +183,11 @@ export function runAgentLaunchCommand(command, args = [], options = {}, deps = {
|
|
|
108
183
|
return result.status ?? 1;
|
|
109
184
|
}
|
|
110
185
|
export function registerAgentLaunchCommand(cli, command) {
|
|
186
|
+
const rotateHint = command === "opencode"
|
|
187
|
+
? ` (pass \`${OPENCODE_ROTATE_FLAG}\` to cool the current OpenCode Go account and launch on the next one)`
|
|
188
|
+
: "";
|
|
111
189
|
cli
|
|
112
|
-
.command(`${command} [...args]`, `Launch ${command} with provider-backed agent secrets and safe-by-default permissions (run \`wp yolo on\` for persistent full-auto)`)
|
|
190
|
+
.command(`${command} [...args]`, `Launch ${command} with provider-backed agent secrets and safe-by-default permissions (run \`wp yolo on\` for persistent full-auto)${rotateHint}`)
|
|
113
191
|
.allowUnknownOptions()
|
|
114
|
-
.action((args = []) => runAgentLaunchCommand(command, args, { cwd: process.cwd() }));
|
|
192
|
+
.action(async (args = []) => await runAgentLaunchCommand(command, args, { cwd: process.cwd() }));
|
|
115
193
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAuditKindDescriptor } from "#audit/registry";
|
|
1
2
|
function optionsForCompositeRepoAudit(name, options) {
|
|
2
3
|
if (name !== "reference-parity-matrix" || options.strict !== true)
|
|
3
4
|
return options;
|
|
@@ -94,7 +95,13 @@ export async function runAuditDispatch(auditKind, targets, options, deps) {
|
|
|
94
95
|
let allOk = true;
|
|
95
96
|
for (const name of deps.knownRepoKinds) {
|
|
96
97
|
const result = await deps.runRepoAudit(name, root, optionsForCompositeRepoAudit(name, options));
|
|
97
|
-
|
|
98
|
+
// Signal-class kinds (registry.ts gateClass) still run and still
|
|
99
|
+
// report their findings via `results` below, but a failing signal
|
|
100
|
+
// kind must not flip the umbrella — only blocking-class failures do.
|
|
101
|
+
// Unclassified kinds default to blocking, matching the registry's
|
|
102
|
+
// own descriptor() default.
|
|
103
|
+
const gateClass = getAuditKindDescriptor(name)?.gateClass ?? "blocking";
|
|
104
|
+
if (!result.ok && gateClass !== "signal")
|
|
98
105
|
allOk = false;
|
|
99
106
|
results.push({ name, result });
|
|
100
107
|
}
|
|
@@ -15,6 +15,7 @@ import { createCliLogSink } from "#cli/commands/quality-log-store.js";
|
|
|
15
15
|
import { emitCliCommandOutput, runLoggedChildCommand } from "#cli/commands/quality-runner.js";
|
|
16
16
|
export const REPO_AUDIT_REGISTRY = {
|
|
17
17
|
"catalog-drift": async (root) => (await import("#audit/repo-guardrails")).auditCatalogDrift(root),
|
|
18
|
+
"typescript-version": async (root) => (await import("#audit/typescript-version")).auditTypescriptVersion(root),
|
|
18
19
|
"package-surface": async (root) => (await import("#audit/package-surface")).auditPackageSurface(root),
|
|
19
20
|
"command-surface": async (root) => (await import("#audit/command-surface")).auditCommandSurface(root),
|
|
20
21
|
"reference-parity-matrix": async (root, options) => (await import("#audit/reference-parity-matrix")).auditReferenceParityMatrix(root, undefined, {
|
|
@@ -123,6 +124,7 @@ export const REPO_AUDIT_REGISTRY = {
|
|
|
123
124
|
"secrets-config": async (root) => (await import("#audit/secrets-config")).auditSecretsConfig(root),
|
|
124
125
|
"consumer-agent-kit-dependency": async (root) => (await import("#audit/consumer-agent-kit-dependency")).auditConsumerAgentKitDependency(root),
|
|
125
126
|
"ci-test-perf": async (root) => (await import("#audit/ci-test-perf")).auditCiTestPerf(root),
|
|
127
|
+
"ci-guardrails-wiring": async (root) => (await import("#audit/ci-guardrails-wiring")).auditCiGuardrailsWiring(root),
|
|
126
128
|
"test-scan-perf": async (root, options) => (await import("#audit/test-scan-perf")).auditTestScanPerf(root, {
|
|
127
129
|
affectedFiles: options.affectedFiles,
|
|
128
130
|
}),
|
|
@@ -140,6 +142,7 @@ export const REPO_AUDIT_REGISTRY = {
|
|
|
140
142
|
draftTechDebt: options.draftTechDebt,
|
|
141
143
|
}),
|
|
142
144
|
"harness-overlay-evidence": async (root) => (await import("#audit/harness-overlay-evidence")).auditHarnessOverlayEvidence(root),
|
|
145
|
+
"host-substitution-risk": async (root) => (await import("#audit/host-substitution-risk")).auditHostSubstitutionRisk(root),
|
|
143
146
|
"legacy-workflow-identity": async (root) => (await import("#audit/legacy-workflow-identity")).auditLegacyWorkflowIdentity(root),
|
|
144
147
|
rules: async (root) => runContentAudit(root, "rule"),
|
|
145
148
|
skills: async (root) => runContentAudit(root, "skill"),
|
|
@@ -467,22 +470,31 @@ export function registerAuditCommand(cli) {
|
|
|
467
470
|
}
|
|
468
471
|
case "aggregate-result": {
|
|
469
472
|
const { formatRepoAuditReport } = await import("#audit/repo-guardrails");
|
|
473
|
+
const isSignal = (name) => getAuditKindDescriptor(name)?.gateClass === "signal";
|
|
470
474
|
for (const { name, result } of outcome.results) {
|
|
471
475
|
if (result.ok)
|
|
472
476
|
continue;
|
|
473
|
-
|
|
477
|
+
const label = isSignal(name) ? `${name} (signal, non-blocking)` : name;
|
|
478
|
+
sink.write(`\n[${label}]\n${formatRepoAuditReport(result)}\n`);
|
|
474
479
|
}
|
|
475
480
|
const failed = outcome.results.filter(({ result }) => !result.ok);
|
|
481
|
+
const blockingFailed = failed.filter(({ name }) => !isSignal(name));
|
|
482
|
+
const signalFailed = failed.filter(({ name }) => isSignal(name));
|
|
476
483
|
if (failed.length > 0) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
.join(", ")}
|
|
484
|
+
const parts = [];
|
|
485
|
+
if (blockingFailed.length > 0) {
|
|
486
|
+
parts.push(`${blockingFailed.length} blocking: ${blockingFailed.map(({ name }) => name).join(", ")}`);
|
|
487
|
+
}
|
|
488
|
+
if (signalFailed.length > 0) {
|
|
489
|
+
parts.push(`${signalFailed.length} signal/non-blocking: ${signalFailed.map(({ name }) => name).join(", ")}`);
|
|
490
|
+
}
|
|
491
|
+
sink.write(`\nguardrails: ${failed.length}/${outcome.results.length} audits reported findings (${parts.join("; ")})\n`);
|
|
480
492
|
}
|
|
481
493
|
exitCode = outcome.code;
|
|
482
494
|
summary =
|
|
483
495
|
outcome.code === 0
|
|
484
496
|
? "audit guardrails passed"
|
|
485
|
-
: `audit guardrails failed (${
|
|
497
|
+
: `audit guardrails failed (${blockingFailed.length}/${outcome.results.length})`;
|
|
486
498
|
break;
|
|
487
499
|
}
|
|
488
500
|
case "quality-exit": {
|
|
@@ -9,6 +9,7 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
9
9
|
import { applyRuntimeProgressSnapshot, buildBlueprintProgressBridgeState, normalizeRuntimeTaskSnapshot, projectBlueprintLifecycleFromRuntime, readBlueprintExecutionArtifacts, runtimeStateSnapshotSchema, writeBlueprintExecutionArtifacts, writeBlueprintExecutionMetadata, } from "#index";
|
|
10
10
|
import { applyBlueprintLifecycleToFile, parseBlueprint } from "#local";
|
|
11
11
|
import { resolveBlueprintRoot } from "#utils/blueprint-root";
|
|
12
|
+
import { resolveBlueprintDocumentIdentity } from "#utils/document-paths.js";
|
|
12
13
|
import { clearBlueprintExecutionState, persistBlueprintExecutionArtifacts, persistBlueprintExecutionMetadata, persistBlueprintProgressBridgeState, readBlueprintExecutionArtifactsState, readBlueprintExecutionState, readBlueprintProgressBridgeState, readBlueprintRuntimeSnapshot, writeBlueprintRuntimeSnapshot, } from "./execution-io.js";
|
|
13
14
|
import { assertCompletionEvidence, mergeExecutionArtifacts, } from "./execution-state.js";
|
|
14
15
|
import { buildBlueprintExecutionControlCommand, buildBlueprintExecutionLaunchCommand, buildBlueprintExecutionRuntimePaths, buildBlueprintLaunchSpec, buildListTasksVerificationCommand, isMissingFileError, nowIsoTimestamp, parseRuntimeApiResponse, parseTeamExecutionId, resolveControlStatus, toProjectRelativePath, uniqueStrings, } from "./execution-spec.js";
|
|
@@ -114,22 +115,33 @@ export async function reconcileBlueprintRuntimeSnapshot(projectRoot, blueprintPa
|
|
|
114
115
|
const currentStatus = currentDir.split(blueprintsRoot + path.sep)[1]?.split(path.sep)[0];
|
|
115
116
|
const relativeSlug = slug.replace(/^[^/]+\//u, "");
|
|
116
117
|
const targetDir = path.join(blueprintsRoot, nextStatus, relativeSlug);
|
|
117
|
-
|
|
118
|
+
// A flat document owns only its own file; renaming its parent directory
|
|
119
|
+
// would move the entire lifecycle state directory. Lifecycle moves are
|
|
120
|
+
// shape-preserving: flat stays flat, folder moves as a folder.
|
|
121
|
+
const isFlatDocument = resolveBlueprintDocumentIdentity(blueprintsRoot, blueprintPath)?.shape === "flat";
|
|
122
|
+
const targetPath = isFlatDocument ? `${targetDir}.md` : path.join(targetDir, "_overview.md");
|
|
123
|
+
const documentMoved = blueprintPath !== targetPath;
|
|
118
124
|
const nextMarkdown = completionEvidence
|
|
119
125
|
? writeBlueprintExecutionArtifacts(result.markdown, completionEvidence)
|
|
120
126
|
: result.markdown;
|
|
121
|
-
if (
|
|
127
|
+
if (documentMoved && currentStatus && currentStatus !== nextStatus) {
|
|
122
128
|
const { mkdir, rename } = await import("node:fs/promises");
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
if (isFlatDocument) {
|
|
130
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
131
|
+
await rename(blueprintPath, targetPath);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await mkdir(path.dirname(targetDir), { recursive: true });
|
|
135
|
+
await rename(currentDir, targetDir);
|
|
136
|
+
}
|
|
125
137
|
await writeFile(targetPath, nextMarkdown, "utf-8");
|
|
126
138
|
}
|
|
127
139
|
else {
|
|
128
140
|
await writeFile(blueprintPath, nextMarkdown, "utf-8");
|
|
129
141
|
}
|
|
130
142
|
return {
|
|
131
|
-
moved:
|
|
132
|
-
path:
|
|
143
|
+
moved: documentMoved,
|
|
144
|
+
path: documentMoved ? targetPath : blueprintPath,
|
|
133
145
|
status: result.execution.status,
|
|
134
146
|
};
|
|
135
147
|
}
|
|
@@ -24,9 +24,9 @@ import { getBlueprintDocumentPaths } from "#utils/document-paths.js";
|
|
|
24
24
|
import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
|
|
25
25
|
import { reIngestProjection } from "#projection-ready.js";
|
|
26
26
|
import { assertAllTasksHaveCanonicalPassingEvidence } from "#verification.js";
|
|
27
|
-
import {
|
|
27
|
+
import { assertPromotionTrustProofCurrent, preparePromotionTrustGate, } from "#trust/promotion.js";
|
|
28
28
|
import matter from "gray-matter";
|
|
29
|
-
import { evaluateApprovalGate, formatApprovalGateRequirement } from "#lifecycle/audit";
|
|
29
|
+
import { assertCompletedApprovalGate, evaluateApprovalGate, formatApprovalGateRequirement, } from "#lifecycle/audit";
|
|
30
30
|
/**
|
|
31
31
|
* Module-level factory. `null` = use the production default (loadSyncCredentials
|
|
32
32
|
* from auth.ts + BlueprintSyncClient + ReplicaManager — lazy-imported so that
|
|
@@ -321,24 +321,38 @@ async function advanceTaskLocked(cwd, blueprintSlug, taskId, toStatus) {
|
|
|
321
321
|
* - Re-ingests into DB
|
|
322
322
|
*/
|
|
323
323
|
export async function promoteBlueprint(cwd, slug, toState) {
|
|
324
|
+
const adapter = await resolveSyncAdapterForCli(cwd);
|
|
325
|
+
// Pull platform state under the mutation lock, then release it before any
|
|
326
|
+
// Trust Dossier command spawns a child `wp`. Child startup/ingestion can
|
|
327
|
+
// legitimately need the same repo-scoped lock.
|
|
328
|
+
await withMarkdownWriteLock(cwd, () => runCliPlatformMutationSync(adapter, {
|
|
329
|
+
label: "wp_blueprint_promote",
|
|
330
|
+
ensureFreshSlug: slug,
|
|
331
|
+
}));
|
|
332
|
+
const found = findBlueprintDocument(resolveBlueprintRoot(cwd), slug);
|
|
333
|
+
if (!found) {
|
|
334
|
+
throw new Error(`Blueprint "${slug}" not found in any state directory under ${resolveBlueprintRoot(cwd)}`);
|
|
335
|
+
}
|
|
336
|
+
const crossesDraftApprovalBoundary = found.state === "draft" && (toState === "planned" || toState === "in-progress");
|
|
337
|
+
const trustProof = crossesDraftApprovalBoundary
|
|
338
|
+
? preparePromotionTrustGate({
|
|
339
|
+
repoRoot: cwd,
|
|
340
|
+
file: found.documentPath,
|
|
341
|
+
markdown: readFileSync(found.documentPath, "utf8"),
|
|
342
|
+
})
|
|
343
|
+
: null;
|
|
324
344
|
// F9/R7: cross-worktree markdown writes serialize via the repo-scoped lock.
|
|
325
|
-
|
|
345
|
+
// The proof is revalidated inside this lock before any lifecycle write.
|
|
346
|
+
return withMarkdownWriteLock(cwd, () => promoteBlueprintLocked(cwd, slug, toState, adapter, trustProof));
|
|
326
347
|
}
|
|
327
|
-
async function promoteBlueprintLocked(cwd, slug, toState) {
|
|
348
|
+
async function promoteBlueprintLocked(cwd, slug, toState, adapter, trustProof) {
|
|
328
349
|
const blueprintRoot = resolveBlueprintRoot(cwd);
|
|
329
350
|
const found = findBlueprintDocument(blueprintRoot, slug);
|
|
330
351
|
if (!found) {
|
|
331
352
|
throw new Error(`Blueprint "${slug}" not found in any state directory under ${blueprintRoot}`);
|
|
332
353
|
}
|
|
333
354
|
const { dir: currentDir, documentPath: currentDocumentPath, shape, state: currentState } = found;
|
|
334
|
-
|
|
335
|
-
// Markdown `_overview.md` is SSOT for task status + verification evidence.
|
|
336
|
-
// The projection DB is derived and can lag when agents edit task Status
|
|
337
|
-
// blocks without going through `task complete` (hash-skipping reingest then
|
|
338
|
-
// leaves stale todo rows). Do not fail completion on projection lag —
|
|
339
|
-
// reingest after the markdown gate so consumers see fresh rows.
|
|
340
|
-
if (toState === "completed") {
|
|
341
|
-
const markdown = readFileSync(currentDocumentPath, "utf8");
|
|
355
|
+
const assertCompletionReady = (markdown) => {
|
|
342
356
|
const blueprint = parseBlueprint(markdown, slug);
|
|
343
357
|
if (blueprint.tasks.length === 0) {
|
|
344
358
|
throw new Error(`Cannot promote "${slug}" to completed: zero-task blueprints cannot complete through the public lifecycle surface`);
|
|
@@ -349,30 +363,30 @@ async function promoteBlueprintLocked(cwd, slug, toState) {
|
|
|
349
363
|
throw new Error(`Cannot promote "${slug}" to completed: the following tasks are not done/dropped: ${list}`);
|
|
350
364
|
}
|
|
351
365
|
assertAllTasksHaveCanonicalPassingEvidence(markdown, blueprint.tasks.filter((task) => task.status === "done").map((task) => task.id));
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
label: "wp_blueprint_promote",
|
|
363
|
-
ensureFreshSlug: slug,
|
|
364
|
-
});
|
|
366
|
+
// Completion parity with CI: the CLI completion owner uses the same
|
|
367
|
+
// audit-owned gate as the MCP funnel and execution engine, so all normal
|
|
368
|
+
// paths into `completed` enforce the same plan-purpose approval.
|
|
369
|
+
try {
|
|
370
|
+
assertCompletedApprovalGate(currentDocumentPath, markdown);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
throw new Error(`Cannot complete "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
365
376
|
// Trust must be proven before any platform status_changed event is published.
|
|
366
377
|
let content = readFileSync(currentDocumentPath, "utf8");
|
|
367
378
|
let trustedSource = content;
|
|
379
|
+
if (toState === "completed") {
|
|
380
|
+
assertCompletionReady(content);
|
|
381
|
+
}
|
|
368
382
|
const crossesDraftApprovalBoundary = currentState === "draft" && (toState === "planned" || toState === "in-progress");
|
|
369
383
|
if (crossesDraftApprovalBoundary) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
trustedSource =
|
|
384
|
+
if (trustProof === null) {
|
|
385
|
+
throw new Error("Blueprint trust gate invalidated: no external gate proof is available");
|
|
386
|
+
}
|
|
387
|
+
assertPromotionTrustProofCurrent({ repoRoot: cwd, file: currentDocumentPath, markdown: content }, trustProof);
|
|
388
|
+
content = trustProof.trustedMarkdown;
|
|
389
|
+
trustedSource = trustProof.sourceMarkdown;
|
|
376
390
|
// Governance Piece 1 — HARD gate at the promotion boundary. One
|
|
377
391
|
// provenance-backed Claude/Codex/Grok or OpenCode-Go model approval can
|
|
378
392
|
// promote. Check AFTER the trust gate so trust failures surface first.
|
|
@@ -402,18 +416,23 @@ async function promoteBlueprintLocked(cwd, slug, toState) {
|
|
|
402
416
|
if (crossesDraftApprovalBoundary) {
|
|
403
417
|
const refreshedSource = readFileSync(currentDocumentPath, "utf8");
|
|
404
418
|
if (refreshedSource !== trustedSource) {
|
|
405
|
-
|
|
406
|
-
repoRoot: cwd,
|
|
407
|
-
file: currentDocumentPath,
|
|
408
|
-
markdown: refreshedSource,
|
|
409
|
-
});
|
|
410
|
-
const approvalEvaluation = evaluateApprovalGate(currentDocumentPath, matter(content).data.approvals);
|
|
411
|
-
if (!approvalEvaluation.satisfied) {
|
|
412
|
-
throw new Error(`Cannot promote "${slug}" to ${toState}: frontmatter \`approvals:\` does not satisfy the promotion reviewer mix (${formatApprovalGateRequirement(approvalEvaluation)}). Record approvals, commit the matching review ledger (e.g. reviews.md), and include tracked review transcript/artifact files referenced by each structured review entry — see catalog/agent/rules/pre-implementation.md.`);
|
|
413
|
-
}
|
|
419
|
+
throw new Error(`Cannot promote "${slug}" to ${toState}: blueprint changed after external trust gates ran; retry promotion`);
|
|
414
420
|
}
|
|
421
|
+
assertPromotionTrustProofCurrent({ repoRoot: cwd, file: currentDocumentPath, markdown: refreshedSource }, trustProof);
|
|
422
|
+
}
|
|
423
|
+
if (toState === "completed") {
|
|
424
|
+
// The platform pull can replace the document after the first completion
|
|
425
|
+
// check. Re-read and re-assert against the exact content written below.
|
|
426
|
+
content = readFileSync(currentDocumentPath, "utf8");
|
|
427
|
+
assertCompletionReady(content);
|
|
415
428
|
}
|
|
416
429
|
}
|
|
430
|
+
if (toState === "completed") {
|
|
431
|
+
// Best-effort projection refresh. Clear content hash first so a lagging
|
|
432
|
+
// tasks table is rewritten even when a prior corrupt UPDATE left the
|
|
433
|
+
// stored hash equal to current markdown.
|
|
434
|
+
await forceReIngestBlueprint(cwd, slug, currentDocumentPath);
|
|
435
|
+
}
|
|
417
436
|
// Always update local markdown + SQLite.
|
|
418
437
|
// Platform-first: these become derived artifacts; disabled: these are canonical.
|
|
419
438
|
// Update frontmatter in the current location first, then move
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BlueprintAuditResult, BlueprintSummary } from "#local";
|
|
2
2
|
import type { BlueprintTemplateEntry } from "#sync/types.js";
|
|
3
|
-
import type { AdvanceTaskResult, BlueprintCommandOptions, BlueprintLifecycleMutationResult, CreateBlueprintResult, ExportBlueprintResult, ExecuteBlueprintResult, MoveBlueprintResult, PromoteBlueprintResult, ShowBlueprintResult } from "./router.js";
|
|
3
|
+
import type { AdvanceTaskResult, BlueprintCommandOptions, BlueprintCanonicalizeResult, BlueprintLifecycleMutationResult, CreateBlueprintResult, ExportBlueprintResult, ExecuteBlueprintResult, MoveBlueprintResult, PromoteBlueprintResult, ShowBlueprintResult } from "./router.js";
|
|
4
4
|
/**
|
|
5
5
|
* Override the platform template fetcher — for tests only.
|
|
6
6
|
* Pass `null` to restore the production default.
|
|
@@ -21,6 +21,7 @@ export declare class BlueprintAuditFailedError extends Error {
|
|
|
21
21
|
interface BlueprintCommandDependencies {
|
|
22
22
|
advanceBlueprintTask: (slug: string, taskId: string, toStatus: string, options: BlueprintCommandOptions) => Promise<AdvanceTaskResult>;
|
|
23
23
|
auditBlueprints: (options: BlueprintCommandOptions) => Promise<BlueprintAuditResult>;
|
|
24
|
+
canonicalizeBlueprintBySlug: (slug: string, options: BlueprintCommandOptions) => Promise<BlueprintCanonicalizeResult>;
|
|
24
25
|
controlBlueprintExec: (action: "status" | "resume" | "stop", slug: string, options: BlueprintCommandOptions) => Promise<ExecuteBlueprintResult>;
|
|
25
26
|
readBlueprintExecutionLogs: (slug: string, options: BlueprintCommandOptions) => Promise<ExecuteBlueprintResult>;
|
|
26
27
|
createBlueprint: (goal: string, options: BlueprintCommandOptions) => Promise<CreateBlueprintResult>;
|
|
@@ -3,7 +3,9 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { executeBlueprintDbSubcommand } from "./db-commands.js";
|
|
6
|
+
import { getBlueprintSubcommandNames } from "./router-output.js";
|
|
6
7
|
import { listTemplates, resolveTemplate } from "./template-resolver.js";
|
|
8
|
+
import { TRUST_DOSSIER_SCAFFOLD } from "#trust/scaffold.js";
|
|
7
9
|
// ---------------------------------------------------------------------------
|
|
8
10
|
// Platform template fetcher (injectable for tests)
|
|
9
11
|
// ---------------------------------------------------------------------------
|
|
@@ -232,6 +234,15 @@ export async function executeBlueprintSubcommand(subcommand, args, options, deps
|
|
|
232
234
|
deps.printBlueprintOutput(options.json ? result : result.message, options.json);
|
|
233
235
|
return;
|
|
234
236
|
}
|
|
237
|
+
case "canonicalize": {
|
|
238
|
+
const slug = args[0];
|
|
239
|
+
if (!slug) {
|
|
240
|
+
throw new Error("Usage: wp blueprint canonicalize <slug>");
|
|
241
|
+
}
|
|
242
|
+
const result = await deps.canonicalizeBlueprintBySlug(slug, options);
|
|
243
|
+
deps.printBlueprintOutput(options.json ? { from: result.from, to: result.to, moved: result.moved } : result.message, options.json);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
235
246
|
case "start": {
|
|
236
247
|
const slug = args[0];
|
|
237
248
|
if (!slug) {
|
|
@@ -350,8 +361,16 @@ export async function executeBlueprintSubcommand(subcommand, args, options, deps
|
|
|
350
361
|
deps.printBlueprintOutput(options.json ? result : result.message, options.json);
|
|
351
362
|
return;
|
|
352
363
|
}
|
|
364
|
+
case "trust-dossier": {
|
|
365
|
+
// Read-only: prints an already-valid "## Trust Dossier" starter block
|
|
366
|
+
// (src/blueprint/trust/scaffold.ts). No file writes, no worktree/lock
|
|
367
|
+
// interaction — an author pastes the output into their overview and
|
|
368
|
+
// edits the example values.
|
|
369
|
+
deps.printBlueprintOutput(options.json ? { markdown: TRUST_DOSSIER_SCAFFOLD } : TRUST_DOSSIER_SCAFFOLD, options.json);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
353
372
|
default: {
|
|
354
|
-
throw new Error(`Unknown blueprint subcommand: ${subcommand}\n\nUse one of:
|
|
373
|
+
throw new Error(`Unknown blueprint subcommand: ${subcommand}\n\nUse one of: ${getBlueprintSubcommandNames().join(", ")}`);
|
|
355
374
|
}
|
|
356
375
|
}
|
|
357
376
|
}
|
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import type { Blueprint, BlueprintAuditResult, BlueprintSummary } from "#local";
|
|
2
2
|
import type { CreateBlueprintResult, ExecuteBlueprintResult, ShowBlueprintResult } from "./router.js";
|
|
3
|
+
/**
|
|
4
|
+
* Canonical registry of every subcommand `router-dispatch.ts` actually
|
|
5
|
+
* dispatches. This is the single source of truth for both the `--help`
|
|
6
|
+
* `Commands:` block (see `BLUEPRINT_HELP` below) and the unknown-subcommand
|
|
7
|
+
* error list — so a subcommand can't ship dispatched-but-undocumented
|
|
8
|
+
* again. `router-dispatch.test.ts` asserts this list stays equal to the
|
|
9
|
+
* live `case "…":` labels in the dispatch switch.
|
|
10
|
+
*/
|
|
11
|
+
export interface BlueprintSubcommandSpec {
|
|
12
|
+
/** Matches a `case "<name>":` label in router-dispatch.ts exactly. */
|
|
13
|
+
readonly name: string;
|
|
14
|
+
/** One or more `Commands:` lines (without the leading indent). */
|
|
15
|
+
readonly usage: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const BLUEPRINT_SUBCOMMANDS: readonly BlueprintSubcommandSpec[];
|
|
3
18
|
export declare function formatTaskLine(task: Blueprint["tasks"][number]): string;
|
|
4
19
|
export declare function getBlueprintHelpText(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Subcommand names in registry order, for the unknown-subcommand error
|
|
22
|
+
* message. Kept as a function (rather than exporting a derived constant)
|
|
23
|
+
* so the source of truth stays singular: `BLUEPRINT_SUBCOMMANDS`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getBlueprintSubcommandNames(): readonly string[];
|
|
5
26
|
export interface BlueprintInventorySummary {
|
|
6
27
|
total: number;
|
|
7
28
|
byStatus: Record<string, number>;
|
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
import { buildRoadmapModel, planStatusSchema } from "#local";
|
|
2
|
+
export const BLUEPRINT_SUBCOMMANDS = [
|
|
3
|
+
{ name: "list", usage: ["list [status]"] },
|
|
4
|
+
{
|
|
5
|
+
name: "new",
|
|
6
|
+
usage: ["new <goal> --complexity <XS|S|M|L|XL> [--type <blueprint|parent-roadmap>]"],
|
|
7
|
+
},
|
|
8
|
+
{ name: "show", usage: ["show <slug>"] },
|
|
9
|
+
{
|
|
10
|
+
name: "exec",
|
|
11
|
+
usage: [
|
|
12
|
+
"exec <slug>",
|
|
13
|
+
"exec status <slug>",
|
|
14
|
+
"exec resume <slug>",
|
|
15
|
+
"exec stop <slug>",
|
|
16
|
+
"exec logs <slug>",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{ name: "start", usage: ["start <slug>"] },
|
|
20
|
+
{ name: "park", usage: ["park <slug>"] },
|
|
21
|
+
{
|
|
22
|
+
name: "task",
|
|
23
|
+
usage: [
|
|
24
|
+
"task start <slug> <taskId>",
|
|
25
|
+
"task block <slug> <taskId> --reason <text>",
|
|
26
|
+
"task unblock <slug> <taskId>",
|
|
27
|
+
"task complete <slug> <taskId>",
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{ name: "finalize", usage: ["finalize <slug>"] },
|
|
31
|
+
{ name: "promote", usage: ["promote <slug> <planned|in-progress|completed|parked>"] },
|
|
32
|
+
{ name: "audit", usage: ["audit [--staged|--all] [--strict]"] },
|
|
33
|
+
{ name: "move", usage: ["move <slug> <status> --force-recovery"] },
|
|
34
|
+
{ name: "canonicalize", usage: ["canonicalize <slug>"] },
|
|
35
|
+
{ name: "control", usage: ["control <status|resume|stop> <slug>"] },
|
|
36
|
+
{ name: "logs", usage: ["logs <slug>"] },
|
|
37
|
+
{ name: "db", usage: ["db <verb> [...args]"] },
|
|
38
|
+
{ name: "export", usage: ["export --format spec-kit <slug>"] },
|
|
39
|
+
{ name: "trust-dossier", usage: ["trust-dossier"] },
|
|
40
|
+
];
|
|
2
41
|
const BLUEPRINT_HELP = [
|
|
3
42
|
"Blueprint management",
|
|
4
43
|
"",
|
|
5
44
|
"Commands:",
|
|
6
|
-
|
|
7
|
-
" new <goal> --complexity <XS|S|M|L|XL> [--type <blueprint|parent-roadmap>]",
|
|
8
|
-
" show <slug>",
|
|
9
|
-
" exec <slug>",
|
|
10
|
-
" exec status <slug>",
|
|
11
|
-
" exec resume <slug>",
|
|
12
|
-
" exec stop <slug>",
|
|
13
|
-
" exec logs <slug>",
|
|
14
|
-
" start <slug>",
|
|
15
|
-
" park <slug>",
|
|
16
|
-
" task start <slug> <taskId>",
|
|
17
|
-
" task block <slug> <taskId> --reason <text>",
|
|
18
|
-
" task unblock <slug> <taskId>",
|
|
19
|
-
" task complete <slug> <taskId>",
|
|
20
|
-
" finalize <slug>",
|
|
21
|
-
" audit [--staged|--all] [--strict]",
|
|
22
|
-
" move <slug> <status> --force-recovery",
|
|
45
|
+
...BLUEPRINT_SUBCOMMANDS.flatMap((spec) => spec.usage.map((line) => ` ${line}`)),
|
|
23
46
|
].join("\n");
|
|
24
47
|
export function formatTaskLine(task) {
|
|
25
48
|
const checkbox = task.status === "done" || task.status === "dropped" ? "x" : " ";
|
|
@@ -28,6 +51,14 @@ export function formatTaskLine(task) {
|
|
|
28
51
|
export function getBlueprintHelpText() {
|
|
29
52
|
return BLUEPRINT_HELP;
|
|
30
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Subcommand names in registry order, for the unknown-subcommand error
|
|
56
|
+
* message. Kept as a function (rather than exporting a derived constant)
|
|
57
|
+
* so the source of truth stays singular: `BLUEPRINT_SUBCOMMANDS`.
|
|
58
|
+
*/
|
|
59
|
+
export function getBlueprintSubcommandNames() {
|
|
60
|
+
return BLUEPRINT_SUBCOMMANDS.map((spec) => spec.name);
|
|
61
|
+
}
|
|
31
62
|
export function summarizeBlueprintInventory(summaries) {
|
|
32
63
|
const byStatus = Object.fromEntries(planStatusSchema.options.toSorted().map((status) => [status, 0]));
|
|
33
64
|
const byType = {
|
|
@@ -73,6 +73,12 @@ export interface BlueprintLifecycleMutationResult {
|
|
|
73
73
|
status: string;
|
|
74
74
|
taskId?: string;
|
|
75
75
|
}
|
|
76
|
+
export interface BlueprintCanonicalizeResult {
|
|
77
|
+
from: string;
|
|
78
|
+
to: string;
|
|
79
|
+
moved: boolean;
|
|
80
|
+
message: string;
|
|
81
|
+
}
|
|
76
82
|
export interface CreateBlueprintResult extends CreatedBlueprint {
|
|
77
83
|
message: string;
|
|
78
84
|
/** True when draft was auto-routed off a primary-like checkout (H-009 DX). */
|
|
@@ -123,6 +129,7 @@ export declare function exportBlueprint(slug: string, format: string, options?:
|
|
|
123
129
|
export declare function advanceBlueprintTask(slug: string, taskId: string, toStatus: string, options?: BlueprintMoveOptions): Promise<AdvanceTaskResult>;
|
|
124
130
|
export declare function promoteBlueprintToState(slug: string, toState: string, options?: BlueprintMoveOptions): Promise<PromoteBlueprintResult>;
|
|
125
131
|
export declare function finalizeBlueprintBySlug(slug: string, options?: BlueprintMoveOptions): Promise<PromoteBlueprintResult>;
|
|
132
|
+
export declare function canonicalizeBlueprintBySlug(slug: string, options?: BlueprintMoveOptions): Promise<BlueprintCanonicalizeResult>;
|
|
126
133
|
export declare function registerBlueprintRouter(cli: CAC): void;
|
|
127
134
|
export declare function _setBlueprintExecCommandExistsForTests(probe: ((command: string) => boolean) | null): void;
|
|
128
135
|
export declare function _setBlueprintExecStarterForTests(starter: ((slug: string, options: BlueprintMoveOptions) => Promise<BlueprintLifecycleMutationResult>) | null): void;
|