@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
|
@@ -10,6 +10,7 @@ import { resolveCurrentManagedWorktreeContext } from "#worktrees/current.js";
|
|
|
10
10
|
import { canonicalizeWorktreePath, WORKTREE_PROBE_TIMEOUT_MS } from "#worktrees/identity.js";
|
|
11
11
|
import { resolveBlueprintRoot } from "#utils/blueprint-root";
|
|
12
12
|
import { getBlueprintDocumentPaths } from "#utils/document-paths.js";
|
|
13
|
+
import { canonicalizeBlueprint } from "#lifecycle/canonicalize.js";
|
|
13
14
|
import { applyBlueprintLifecycleToFile, BlueprintCreationService, BlueprintService, complexitySchema, relativeBlueprintSlug, parseBlueprint, planStatusSchema, runBlueprintAudit, resolveBlueprintFile, serializeBlueprint, validateAllTasksDone, } from "#local";
|
|
14
15
|
import { resolvePackageAssetPreferred } from "#utils/package-assets";
|
|
15
16
|
import { resolveDraftAuthoringRoot } from "#worktrees/draft-authoring-root.js";
|
|
@@ -512,9 +513,19 @@ export async function finalizeBlueprintBySlug(slug, options = {}) {
|
|
|
512
513
|
const projectRoot = resolveProjectRoot(options.projectRoot);
|
|
513
514
|
return finalizeBlueprintMutation(projectRoot, slug);
|
|
514
515
|
}
|
|
516
|
+
export async function canonicalizeBlueprintBySlug(slug, options = {}) {
|
|
517
|
+
const projectRoot = resolveProjectRoot(options.projectRoot);
|
|
518
|
+
const result = await canonicalizeBlueprint(projectRoot, slug);
|
|
519
|
+
return {
|
|
520
|
+
...result,
|
|
521
|
+
message: result.moved
|
|
522
|
+
? `Canonicalized blueprint ${slug}: ${result.from} -> ${result.to}`
|
|
523
|
+
: `Blueprint ${slug} is already canonical: ${result.to}`,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
515
526
|
export function registerBlueprintRouter(cli) {
|
|
516
527
|
cli
|
|
517
|
-
.command("blueprint [subcommand] [...args]", "Manage blueprints. Use: wp blueprint <action> --help for action-specific options (new, list, show, audit, exec, move, finalize, start, task)")
|
|
528
|
+
.command("blueprint [subcommand] [...args]", "Manage blueprints. Use: wp blueprint <action> --help for action-specific options (new, list, show, audit, exec, move, finalize, start, task, trust-dossier)")
|
|
518
529
|
.option("--json", "Print JSON output")
|
|
519
530
|
.option("--no-tui", "Use plain terminal output")
|
|
520
531
|
.option("--complexity <complexity>", "Blueprint complexity (XS|S|M|L|XL)")
|
|
@@ -535,6 +546,7 @@ export function registerBlueprintRouter(cli) {
|
|
|
535
546
|
await executeBlueprintSubcommand(subcommand, args, options, {
|
|
536
547
|
advanceBlueprintTask,
|
|
537
548
|
auditBlueprints,
|
|
549
|
+
canonicalizeBlueprintBySlug,
|
|
538
550
|
createBlueprint,
|
|
539
551
|
controlBlueprintExec,
|
|
540
552
|
executeBlueprint,
|
|
@@ -31,9 +31,20 @@ export interface CiPreflightDeps {
|
|
|
31
31
|
readonly cwd?: string;
|
|
32
32
|
readonly resolveRepoRoot?: (cwd: string) => string | null;
|
|
33
33
|
readonly hasNodeModules?: (repoRoot: string) => boolean;
|
|
34
|
-
readonly runCheck?: (check: CiPreflightCheck, repoRoot: string) => number
|
|
34
|
+
readonly runCheck?: (check: CiPreflightCheck, repoRoot: string) => number | Promise<number>;
|
|
35
35
|
readonly stdout?: Pick<typeof process.stdout, "write">;
|
|
36
36
|
readonly stderr?: Pick<typeof process.stderr, "write">;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Re-invoke the same `wp` runtime that is currently executing so each check
|
|
40
|
+
* runs the real verb (faithful CI parity) rather than a reimplemented copy of
|
|
41
|
+
* its affected-resolution logic. Prefers the repo-local `bin/wp` launcher when
|
|
42
|
+
* present so the exact source/compiled selection and NODE_PATH wiring apply;
|
|
43
|
+
* falls back to the running runtime for consumer repos with a global `wp`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveWpLauncher(repoRoot: string): {
|
|
46
|
+
readonly command: string;
|
|
47
|
+
readonly prefixArgs: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
export declare function runCiPreflight(deps?: CiPreflightDeps): Promise<number>;
|
|
39
50
|
export declare function registerCiPreflightCommand(cli: CAC): void;
|
|
@@ -57,7 +57,10 @@ function reproduceLine(check) {
|
|
|
57
57
|
* present so the exact source/compiled selection and NODE_PATH wiring apply;
|
|
58
58
|
* falls back to the running runtime for consumer repos with a global `wp`.
|
|
59
59
|
*/
|
|
60
|
-
|
|
60
|
+
// Exported so `src/mcp/tools/ci-preflight.ts` can re-invoke the exact same
|
|
61
|
+
// `wp` launcher when it substitutes an output-capturing `runCheck` for the
|
|
62
|
+
// default `stdio: "inherit"` spawn — do not duplicate this resolution logic.
|
|
63
|
+
export function resolveWpLauncher(repoRoot) {
|
|
61
64
|
const localLauncher = path.join(repoRoot, "bin", process.platform === "win32" ? "wp.cmd" : "wp");
|
|
62
65
|
if (existsSync(localLauncher))
|
|
63
66
|
return { command: localLauncher, prefixArgs: [] };
|
|
@@ -82,7 +85,7 @@ function defaultRunCheck(check, repoRoot) {
|
|
|
82
85
|
}
|
|
83
86
|
return typeof result.status === "number" ? result.status : 1;
|
|
84
87
|
}
|
|
85
|
-
export function runCiPreflight(deps = {}) {
|
|
88
|
+
export async function runCiPreflight(deps = {}) {
|
|
86
89
|
const cwd = deps.cwd ?? process.cwd();
|
|
87
90
|
const stdout = deps.stdout ?? process.stdout;
|
|
88
91
|
const stderr = deps.stderr ?? process.stderr;
|
|
@@ -100,7 +103,7 @@ export function runCiPreflight(deps = {}) {
|
|
|
100
103
|
}
|
|
101
104
|
for (const check of CI_PREFLIGHT_CHECKS) {
|
|
102
105
|
stdout.write(`\nwp ci-preflight: running ${check.label} (${reproduceLine(check)})\n`);
|
|
103
|
-
const exitCode = runCheck(check, repoRoot);
|
|
106
|
+
const exitCode = await runCheck(check, repoRoot);
|
|
104
107
|
if (exitCode !== 0) {
|
|
105
108
|
const normalizedExit = exitCode === 0 ? 1 : exitCode;
|
|
106
109
|
stderr.write(`\nwp ci-preflight: ${check.label} failed (exit ${normalizedExit}).\n` +
|
|
@@ -247,22 +247,10 @@ export async function runCompile(options) {
|
|
|
247
247
|
message: `wp compile: rulesync exited with code ${exitCode}`,
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
|
-
// Step 4:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const manifestOpts = {
|
|
255
|
-
agentDir,
|
|
256
|
-
outDir: cwd,
|
|
257
|
-
version: pkgVersion,
|
|
258
|
-
skills: skillNames,
|
|
259
|
-
commands: commandNames,
|
|
260
|
-
};
|
|
261
|
-
await Promise.all([
|
|
262
|
-
import("#compiler/manifests/claude").then((m) => m.emitManifest(manifestOpts)),
|
|
263
|
-
import("#compiler/manifests/codex").then((m) => m.emitManifest(manifestOpts)),
|
|
264
|
-
]);
|
|
265
|
-
// Step 5: Run mergeAgentsMd to produce AGENTS.md at repo root
|
|
250
|
+
// Step 4: Run mergeAgentsMd to produce AGENTS.md at repo root
|
|
251
|
+
// (Plugin manifests are hand-maintained SSOT under packages/*-plugin/;
|
|
252
|
+
// wp compile no longer emits them — golden tests + version stamping own
|
|
253
|
+
// that surface.)
|
|
266
254
|
const agentsLayers = collectAgentsLayers(agentDir);
|
|
267
255
|
if (agentsLayers.length > 0) {
|
|
268
256
|
const { mergeAgentsMd } = await import("#compiler/memory/merger");
|
|
@@ -278,11 +266,9 @@ export async function runCompile(options) {
|
|
|
278
266
|
writeFileSync(join(cwd, "AGENTS.md"), mergeResult.content);
|
|
279
267
|
}
|
|
280
268
|
}
|
|
281
|
-
// Step
|
|
269
|
+
// Step 5: Write .compile-manifest.json with content-hash sentinels
|
|
282
270
|
const outputHashes = {
|
|
283
271
|
"AGENTS.md": hashFile(join(cwd, "AGENTS.md")),
|
|
284
|
-
"packages/claude-plugin/.claude-plugin/plugin.json": hashFile(join(cwd, "packages", "claude-plugin", ".claude-plugin", "plugin.json")),
|
|
285
|
-
"packages/codex-plugin/.codex-plugin/plugin.json": hashFile(join(cwd, "packages", "codex-plugin", ".codex-plugin", "plugin.json")),
|
|
286
272
|
};
|
|
287
273
|
const compileManifest = {
|
|
288
274
|
version: COMPILE_MANIFEST_VERSION,
|
|
@@ -303,18 +289,6 @@ export async function runCompile(options) {
|
|
|
303
289
|
cleanup();
|
|
304
290
|
}
|
|
305
291
|
}
|
|
306
|
-
function readPackageVersion(cwd) {
|
|
307
|
-
const pkgPath = join(cwd, "package.json");
|
|
308
|
-
if (!existsSync(pkgPath))
|
|
309
|
-
return "0.0.0";
|
|
310
|
-
try {
|
|
311
|
-
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
312
|
-
return typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return "0.0.0";
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
292
|
function collectAgentsLayers(agentDir) {
|
|
319
293
|
const agentsDir = join(agentDir, "agents");
|
|
320
294
|
if (!existsSync(agentsDir))
|
|
@@ -56,3 +56,4 @@ export type DeliveryStatusOptions = {
|
|
|
56
56
|
* prior values and expose a retryable adapter warning.
|
|
57
57
|
*/
|
|
58
58
|
export declare function collectDeliveryStatus(options?: DeliveryStatusOptions): Promise<DeliveryStatus>;
|
|
59
|
+
export declare function blueprintDocumentPath(cwd: string, state: string, slug: string): string | null;
|
|
@@ -107,14 +107,21 @@ function checkState(value) {
|
|
|
107
107
|
return "pending";
|
|
108
108
|
return states.length > 0 ? "passing" : "unknown";
|
|
109
109
|
}
|
|
110
|
+
export function blueprintDocumentPath(cwd, state, slug) {
|
|
111
|
+
const folder = join(cwd, "blueprints", state, slug, "_overview.md");
|
|
112
|
+
if (existsSync(folder))
|
|
113
|
+
return folder;
|
|
114
|
+
const flat = join(cwd, "blueprints", state, `${slug}.md`);
|
|
115
|
+
return existsSync(flat) ? flat : null;
|
|
116
|
+
}
|
|
110
117
|
function readBlueprintDelivery(cwd) {
|
|
111
118
|
const branch = run("git", ["branch", "--show-current"], cwd);
|
|
112
119
|
if (!branch?.startsWith("bp/"))
|
|
113
120
|
return null;
|
|
114
121
|
const slug = branch.slice(3);
|
|
115
122
|
for (const state of ["in-progress", "planned", "draft", "completed", "parked", "archived"]) {
|
|
116
|
-
const path =
|
|
117
|
-
if (!
|
|
123
|
+
const path = blueprintDocumentPath(cwd, state, slug);
|
|
124
|
+
if (!path)
|
|
118
125
|
continue;
|
|
119
126
|
const raw = readFileSync(path, "utf8");
|
|
120
127
|
return {
|
|
@@ -129,8 +136,8 @@ function readVerificationDelivery(cwd) {
|
|
|
129
136
|
const blueprint = readBlueprintDelivery(cwd);
|
|
130
137
|
if (!blueprint)
|
|
131
138
|
return null;
|
|
132
|
-
const path =
|
|
133
|
-
if (!
|
|
139
|
+
const path = blueprintDocumentPath(cwd, blueprint.state, blueprint.slug);
|
|
140
|
+
if (!path)
|
|
134
141
|
return null;
|
|
135
142
|
const raw = readFileSync(path, "utf8");
|
|
136
143
|
const evidenceCount = (raw.match(/^\*\*Verification:\*\*/gmu) ?? []).length;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { auditBlueprintLifecycleSql } from "#audit/blueprint-lifecycle-sql";
|
|
2
|
+
import { auditCiGuardrailsWiring } from "#audit/ci-guardrails-wiring";
|
|
2
3
|
import { readConfig } from "#cli/commands/init/config";
|
|
3
4
|
import { auditCatalogDrift, auditDocsFrontmatter, formatRepoAuditReport, } from "#audit/repo-guardrails";
|
|
4
5
|
import { countWorktreeLifecycleScripts } from "#worktrees/lifecycle-scripts.js";
|
|
@@ -7,6 +8,9 @@ const REMEDIATIONS = {
|
|
|
7
8
|
"Catalog drift — single package (no workspace file)": "none needed",
|
|
8
9
|
"Docs frontmatter": "wp audit docs-frontmatter --fix",
|
|
9
10
|
"Blueprint lifecycle": "wp audit blueprint-lifecycle",
|
|
11
|
+
// Name the missing invocation explicitly so an operator sees which CI step to
|
|
12
|
+
// restore, not just a generic "drifted" signal.
|
|
13
|
+
"CI guardrails wiring": "restore the `wp audit guardrails` CI step (run `wp setup`)",
|
|
10
14
|
};
|
|
11
15
|
export async function runDoctor(options = {}) {
|
|
12
16
|
try {
|
|
@@ -17,6 +21,7 @@ export async function runDoctor(options = {}) {
|
|
|
17
21
|
docsRoot: options.docsRoot,
|
|
18
22
|
fix: options.fix,
|
|
19
23
|
}),
|
|
24
|
+
auditCiGuardrailsWiring(root),
|
|
20
25
|
await auditBlueprintLifecycleSql(root, { includeRuntimePlans: options.runtimePlans }),
|
|
21
26
|
];
|
|
22
27
|
let failed = false;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { CAC } from "cac";
|
|
2
|
+
/**
|
|
3
|
+
* Registered GUARD_* codes emitted by the hook-command fail-closed path.
|
|
4
|
+
* Consumed by the guard-outcome registry-completeness test.
|
|
5
|
+
*/
|
|
6
|
+
export declare const HOOK_FALLBACK_GUARD_CODES: readonly ["GUARD_PRETOOL_FAILURE"];
|
|
2
7
|
declare const HOOK_NAMES: readonly ["pretool-guard", "post-tool", "stop-qa", "stop-ultragoal", "guard-switch", "sessionstart-routing", "precompact-snapshot", "permission-request", "test-quality-check"];
|
|
3
8
|
export type HookName = (typeof HOOK_NAMES)[number];
|
|
4
9
|
export declare function isHookName(value: string): value is HookName;
|
|
@@ -2,8 +2,16 @@ import { performance } from "node:perf_hooks";
|
|
|
2
2
|
import { reportHookDegradation, takeHookDegradation, } from "#hooks/errors/index.js";
|
|
3
3
|
import { recordHookInvocation, summarizeHookText, takeRememberedHookStdin, } from "#hooks/logs/store.js";
|
|
4
4
|
import { createSyntheticProcessExit, isSyntheticProcessExit, } from "#hooks/shared/synthetic-process-exit.js";
|
|
5
|
-
import { writeHookOutput } from "#hooks/shared/hook-bootstrap";
|
|
5
|
+
import { writeGuardStderr, writeHookOutput } from "#hooks/shared/hook-bootstrap";
|
|
6
|
+
import { formatGuardReason } from "#hooks/shared/guard-outcome.js";
|
|
6
7
|
import { buildDenyEnvelope } from "#hooks/shared/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Registered GUARD_* codes emitted by the hook-command fail-closed path.
|
|
10
|
+
* Consumed by the guard-outcome registry-completeness test.
|
|
11
|
+
*/
|
|
12
|
+
export const HOOK_FALLBACK_GUARD_CODES = [
|
|
13
|
+
"GUARD_PRETOOL_FAILURE",
|
|
14
|
+
];
|
|
7
15
|
import { resolveHookProjectDirFromText } from "#hooks/shared/project-dir.js";
|
|
8
16
|
import { createManagedHookExecutionContext, } from "#hooks/shared/session-memory-deadline.js";
|
|
9
17
|
const HOOK_NAMES = [
|
|
@@ -101,13 +109,9 @@ function exitCodeFrom(value) {
|
|
|
101
109
|
}
|
|
102
110
|
function writePretoolDeny(reason) {
|
|
103
111
|
writeHookOutput(`${JSON.stringify(buildDenyEnvelope({ reason }))}\n`);
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// ignore
|
|
110
|
-
}
|
|
112
|
+
// Deliver to the REAL stderr too: Claude reads stderr on an exit-2 PreToolUse
|
|
113
|
+
// deny, and it is suppressed for decorative output.
|
|
114
|
+
writeGuardStderr(`${reason}\n`);
|
|
111
115
|
}
|
|
112
116
|
function emitFallback(name, error) {
|
|
113
117
|
const metadata = HOOK_METADATA[name];
|
|
@@ -121,7 +125,9 @@ function emitFallback(name, error) {
|
|
|
121
125
|
detail,
|
|
122
126
|
});
|
|
123
127
|
if (metadata.fallback === "fail-closed-deny") {
|
|
124
|
-
|
|
128
|
+
// Coded fail-closed denial so the host still sees a stable GUARD_ code even
|
|
129
|
+
// when the guard handler itself threw.
|
|
130
|
+
writePretoolDeny(formatGuardReason("GUARD_PRETOOL_FAILURE", `webpresso pretool guard failed: ${detail}`));
|
|
125
131
|
// Exit 2 so Grok (and exit-code runners) treat fail-closed as an explicit deny.
|
|
126
132
|
return 2;
|
|
127
133
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { isAbsolute, join, normalize } from "node:path";
|
|
7
|
-
import { REQUIRED_CORE_CAPABILITIES } from "./host-visibility.js";
|
|
7
|
+
import { AGENT_HOSTS, REQUIRED_CORE_CAPABILITIES } from "./host-visibility.js";
|
|
8
8
|
export const CONFIG_VERSION = "1";
|
|
9
9
|
export const CONFIG_FILENAME = ".webpressorc.json";
|
|
10
10
|
export const LEGACY_CONFIG_FILENAME = ".agent-kitrc.json";
|
|
@@ -136,7 +136,7 @@ function parseConfigFile(path) {
|
|
|
136
136
|
}
|
|
137
137
|
: undefined;
|
|
138
138
|
const selectedHosts = Array.isArray(hosts?.selected)
|
|
139
|
-
? hosts.selected.filter((s) =>
|
|
139
|
+
? hosts.selected.filter((s) => AGENT_HOSTS.includes(String(s)))
|
|
140
140
|
: [];
|
|
141
141
|
const requiredCapabilities = Array.isArray(hosts?.requiredCapabilities)
|
|
142
142
|
? hosts.requiredCapabilities.filter((s) => typeof s === "string")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ConvergeUnit } from "./convergence.js";
|
|
2
|
+
export { resolveUntrackedCollision } from "./untracked-collision.js";
|
|
3
|
+
export type AuthorCommitStatus = "committed" | "no-change" | "skipped-ci" | "failed" | "rolled-back";
|
|
4
|
+
export interface AuthorCommitResult {
|
|
5
|
+
readonly status: AuthorCommitStatus;
|
|
6
|
+
readonly ok: boolean;
|
|
7
|
+
readonly commit?: string;
|
|
8
|
+
readonly allowlist: readonly string[];
|
|
9
|
+
readonly unexpectedPaths?: readonly string[];
|
|
10
|
+
readonly missingPaths?: readonly string[];
|
|
11
|
+
readonly error?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthorCommitOptions {
|
|
14
|
+
readonly repoRoot: string;
|
|
15
|
+
readonly appliedUnits: readonly ConvergeUnit[];
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly isCi?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface AppliedConvergenceCandidate {
|
|
20
|
+
readonly targetPath: string;
|
|
21
|
+
readonly reason: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function collectAppliedConvergeUnits(repoRoot: string, candidates: readonly AppliedConvergenceCandidate[]): ConvergeUnit[];
|
|
24
|
+
export declare function authorConvergenceCommit(options: AuthorCommitOptions): AuthorCommitResult;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { convergeUnitFromTarget, deriveCommitAllowlist } from "./convergence.js";
|
|
4
|
+
export { resolveUntrackedCollision } from "./untracked-collision.js";
|
|
5
|
+
const GIT_TIMEOUT_MS = 15_000;
|
|
6
|
+
function git(repoRoot, args) {
|
|
7
|
+
const result = spawnSync("git", [...args], {
|
|
8
|
+
cwd: repoRoot,
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
timeout: GIT_TIMEOUT_MS,
|
|
11
|
+
maxBuffer: 1024 * 1024,
|
|
12
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
ok: result.status === 0 && !result.error,
|
|
16
|
+
stdout: result.stdout.trim(),
|
|
17
|
+
stderr: result.stderr.trim(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function lines(value) {
|
|
21
|
+
return value.length === 0 ? [] : value.split("\n").filter(Boolean).sort();
|
|
22
|
+
}
|
|
23
|
+
function readPorcelainStatus(repoRoot, gitPath) {
|
|
24
|
+
const result = spawnSync("git", [
|
|
25
|
+
"status",
|
|
26
|
+
"--porcelain=v1",
|
|
27
|
+
"-z",
|
|
28
|
+
"--untracked-files=all",
|
|
29
|
+
"--ignored=matching",
|
|
30
|
+
"--",
|
|
31
|
+
gitPath,
|
|
32
|
+
], {
|
|
33
|
+
cwd: repoRoot,
|
|
34
|
+
encoding: "utf8",
|
|
35
|
+
timeout: GIT_TIMEOUT_MS,
|
|
36
|
+
maxBuffer: 1024 * 1024,
|
|
37
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
+
});
|
|
39
|
+
if (result.status !== 0 || result.error)
|
|
40
|
+
return null;
|
|
41
|
+
const fields = result.stdout.split("\0");
|
|
42
|
+
const entries = [];
|
|
43
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
44
|
+
const record = fields[index];
|
|
45
|
+
if (!record || record.length < 4)
|
|
46
|
+
continue;
|
|
47
|
+
const code = record.slice(0, 2);
|
|
48
|
+
entries.push({ code, gitPath: record.slice(3) });
|
|
49
|
+
if (code.includes("R") || code.includes("C"))
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
54
|
+
function isIgnored(repoRoot, gitPath) {
|
|
55
|
+
return git(repoRoot, ["check-ignore", "--no-index", "-q", "--", gitPath]).ok;
|
|
56
|
+
}
|
|
57
|
+
export function collectAppliedConvergeUnits(repoRoot, candidates) {
|
|
58
|
+
const units = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const candidate of candidates) {
|
|
61
|
+
let candidateUnit;
|
|
62
|
+
try {
|
|
63
|
+
candidateUnit = convergeUnitFromTarget(repoRoot, candidate.targetPath, {
|
|
64
|
+
action: "modify",
|
|
65
|
+
scope: "repo-committed",
|
|
66
|
+
reason: candidate.reason,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const statuses = readPorcelainStatus(repoRoot, candidateUnit.gitPath);
|
|
73
|
+
if (!statuses)
|
|
74
|
+
continue;
|
|
75
|
+
for (const { code, gitPath } of statuses) {
|
|
76
|
+
if (seen.has(gitPath))
|
|
77
|
+
continue;
|
|
78
|
+
let unit;
|
|
79
|
+
try {
|
|
80
|
+
unit = convergeUnitFromTarget(repoRoot, join(repoRoot, gitPath), {
|
|
81
|
+
action: code === "??" || code === "!!" || code.includes("A")
|
|
82
|
+
? "create"
|
|
83
|
+
: code.includes("D")
|
|
84
|
+
? "delete"
|
|
85
|
+
: "modify",
|
|
86
|
+
scope: "repo-committed",
|
|
87
|
+
reason: candidate.reason,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
units.push({
|
|
94
|
+
...unit,
|
|
95
|
+
scope: code === "!!" || (code === "??" && isIgnored(repoRoot, gitPath))
|
|
96
|
+
? "repo-ignored"
|
|
97
|
+
: "repo-committed",
|
|
98
|
+
});
|
|
99
|
+
seen.add(gitPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return units;
|
|
103
|
+
}
|
|
104
|
+
export function authorConvergenceCommit(options) {
|
|
105
|
+
const allowlist = deriveCommitAllowlist(options.appliedUnits);
|
|
106
|
+
if (options.isCi)
|
|
107
|
+
return { status: "skipped-ci", ok: true, allowlist };
|
|
108
|
+
if (allowlist.length === 0)
|
|
109
|
+
return { status: "no-change", ok: true, allowlist };
|
|
110
|
+
const before = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
|
|
111
|
+
if (!before.ok) {
|
|
112
|
+
return { status: "failed", ok: false, allowlist, error: before.stderr || "Missing HEAD." };
|
|
113
|
+
}
|
|
114
|
+
const add = git(options.repoRoot, ["add", "--", ...allowlist]);
|
|
115
|
+
if (!add.ok) {
|
|
116
|
+
return { status: "failed", ok: false, allowlist, error: add.stderr || "git add failed." };
|
|
117
|
+
}
|
|
118
|
+
const staged = git(options.repoRoot, ["diff", "--cached", "--name-only"]);
|
|
119
|
+
if (!staged.ok) {
|
|
120
|
+
return { status: "failed", ok: false, allowlist, error: staged.stderr || "git diff failed." };
|
|
121
|
+
}
|
|
122
|
+
if (lines(staged.stdout).length === 0) {
|
|
123
|
+
return { status: "no-change", ok: true, allowlist };
|
|
124
|
+
}
|
|
125
|
+
const commit = git(options.repoRoot, ["commit", "-m", options.message]);
|
|
126
|
+
if (!commit.ok) {
|
|
127
|
+
return {
|
|
128
|
+
status: "failed",
|
|
129
|
+
ok: false,
|
|
130
|
+
allowlist,
|
|
131
|
+
error: commit.stderr || commit.stdout || "git commit failed; hook changes were preserved.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const committedHead = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
|
|
135
|
+
if (!committedHead.ok) {
|
|
136
|
+
return {
|
|
137
|
+
status: "failed",
|
|
138
|
+
ok: false,
|
|
139
|
+
allowlist,
|
|
140
|
+
error: committedHead.stderr || "Unable to validate the authored commit.",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const committedParent = git(options.repoRoot, [
|
|
144
|
+
"rev-parse",
|
|
145
|
+
"--verify",
|
|
146
|
+
`${committedHead.stdout}^`,
|
|
147
|
+
]);
|
|
148
|
+
if (!committedParent.ok || committedParent.stdout !== before.stdout) {
|
|
149
|
+
return {
|
|
150
|
+
status: "failed",
|
|
151
|
+
ok: false,
|
|
152
|
+
allowlist,
|
|
153
|
+
commit: committedHead.stdout,
|
|
154
|
+
error: "The observed commit does not directly follow the recorded HEAD; no rollback was attempted. Inspect Git history and recover manually.",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const committedPathsResult = git(options.repoRoot, [
|
|
158
|
+
"diff-tree",
|
|
159
|
+
"--root",
|
|
160
|
+
"--no-commit-id",
|
|
161
|
+
"--name-only",
|
|
162
|
+
"-r",
|
|
163
|
+
committedHead.stdout,
|
|
164
|
+
]);
|
|
165
|
+
if (!committedPathsResult.ok) {
|
|
166
|
+
return {
|
|
167
|
+
status: "failed",
|
|
168
|
+
ok: false,
|
|
169
|
+
allowlist,
|
|
170
|
+
commit: committedHead.stdout,
|
|
171
|
+
error: committedPathsResult.stderr || "Unable to inspect the authored commit.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const committedPaths = lines(committedPathsResult.stdout);
|
|
175
|
+
const allowed = new Set(allowlist);
|
|
176
|
+
const committed = new Set(committedPaths);
|
|
177
|
+
const unexpectedPaths = committedPaths.filter((path) => !allowed.has(path));
|
|
178
|
+
const missingPaths = allowlist.filter((path) => !committed.has(path));
|
|
179
|
+
if (unexpectedPaths.length === 0 && missingPaths.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
status: "committed",
|
|
182
|
+
ok: true,
|
|
183
|
+
allowlist,
|
|
184
|
+
commit: committedHead.stdout,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const currentHead = git(options.repoRoot, ["rev-parse", "--verify", "HEAD"]);
|
|
188
|
+
if (!currentHead.ok || currentHead.stdout !== committedHead.stdout) {
|
|
189
|
+
return {
|
|
190
|
+
status: "failed",
|
|
191
|
+
ok: false,
|
|
192
|
+
allowlist,
|
|
193
|
+
commit: committedHead.stdout,
|
|
194
|
+
unexpectedPaths,
|
|
195
|
+
missingPaths,
|
|
196
|
+
error: "The setup-authored commit changed before validation; no rollback was attempted. Inspect Git history and recover manually.",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const rollback = git(options.repoRoot, ["reset", "--mixed", before.stdout]);
|
|
200
|
+
if (!rollback.ok) {
|
|
201
|
+
return {
|
|
202
|
+
status: "failed",
|
|
203
|
+
ok: false,
|
|
204
|
+
allowlist,
|
|
205
|
+
commit: committedHead.stdout,
|
|
206
|
+
unexpectedPaths,
|
|
207
|
+
missingPaths,
|
|
208
|
+
error: rollback.stderr || "Failed to undo the path-invalid setup commit.",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
status: "rolled-back",
|
|
213
|
+
ok: false,
|
|
214
|
+
allowlist,
|
|
215
|
+
commit: committedHead.stdout,
|
|
216
|
+
unexpectedPaths,
|
|
217
|
+
missingPaths,
|
|
218
|
+
error: `A commit hook changed setup commit scope (unexpected: ${unexpectedPaths.join(", ") || "none"}; missing: ${missingPaths.join(", ") || "none"}). ` +
|
|
219
|
+
"The setup commit was undone and all worktree changes were retained. Update the hook so it does not stage unrelated paths, then stage or discard those paths and rerun wp setup.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const CONVERGENCE_STATE_SCHEMA_VERSION: 1;
|
|
2
|
+
export declare const CONVERGENCE_STATE_PATH = ".webpresso/converged.json";
|
|
3
|
+
export interface ConvergenceState {
|
|
4
|
+
readonly schemaVersion: typeof CONVERGENCE_STATE_SCHEMA_VERSION;
|
|
5
|
+
readonly agentKitVersion: string;
|
|
6
|
+
readonly host: string;
|
|
7
|
+
readonly sentinels: Readonly<Record<string, string>>;
|
|
8
|
+
readonly writtenAt: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseConvergenceState(value: unknown): ConvergenceState | null;
|
|
11
|
+
export declare function readConvergenceState(repoRoot: string): ConvergenceState | null;
|
|
12
|
+
export declare function isConvergenceStateCurrent(state: ConvergenceState | null, expected: Pick<ConvergenceState, "agentKitVersion" | "host" | "sentinels">): boolean;
|
|
13
|
+
export declare function writeConvergenceState(repoRoot: string, state: ConvergenceState): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { writeJsonFile } from "#shared-utils/write-json-file.js";
|
|
4
|
+
export const CONVERGENCE_STATE_SCHEMA_VERSION = 1;
|
|
5
|
+
export const CONVERGENCE_STATE_PATH = ".webpresso/converged.json";
|
|
6
|
+
function isStringRecord(value) {
|
|
7
|
+
return (value !== null &&
|
|
8
|
+
typeof value === "object" &&
|
|
9
|
+
!Array.isArray(value) &&
|
|
10
|
+
Object.values(value).every((entry) => typeof entry === "string"));
|
|
11
|
+
}
|
|
12
|
+
export function parseConvergenceState(value) {
|
|
13
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
14
|
+
return null;
|
|
15
|
+
const record = value;
|
|
16
|
+
if (record.schemaVersion !== CONVERGENCE_STATE_SCHEMA_VERSION ||
|
|
17
|
+
typeof record.agentKitVersion !== "string" ||
|
|
18
|
+
typeof record.host !== "string" ||
|
|
19
|
+
typeof record.writtenAt !== "string" ||
|
|
20
|
+
!isStringRecord(record.sentinels)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
schemaVersion: CONVERGENCE_STATE_SCHEMA_VERSION,
|
|
25
|
+
agentKitVersion: record.agentKitVersion,
|
|
26
|
+
host: record.host,
|
|
27
|
+
sentinels: record.sentinels,
|
|
28
|
+
writtenAt: record.writtenAt,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function readConvergenceState(repoRoot) {
|
|
32
|
+
try {
|
|
33
|
+
return parseConvergenceState(JSON.parse(readFileSync(join(resolve(repoRoot), CONVERGENCE_STATE_PATH), "utf8")));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function isConvergenceStateCurrent(state, expected) {
|
|
40
|
+
if (!state ||
|
|
41
|
+
state.agentKitVersion !== expected.agentKitVersion ||
|
|
42
|
+
state.host !== expected.host) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return Object.entries(expected.sentinels).every(([name, value]) => state.sentinels[name] === value);
|
|
46
|
+
}
|
|
47
|
+
export function writeConvergenceState(repoRoot, state) {
|
|
48
|
+
const statePath = join(resolve(repoRoot), CONVERGENCE_STATE_PATH);
|
|
49
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
50
|
+
writeJsonFile(statePath, state, {
|
|
51
|
+
atomic: true,
|
|
52
|
+
indent: 2,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WriteAction } from "./write-plan.js";
|
|
2
|
+
export declare const CONVERGE_SCOPES: readonly ["repo-committed", "repo-ignored"];
|
|
3
|
+
export type ConvergeScope = (typeof CONVERGE_SCOPES)[number];
|
|
4
|
+
export interface ConvergeUnit {
|
|
5
|
+
readonly repoRoot: string;
|
|
6
|
+
readonly targetPath: string;
|
|
7
|
+
readonly gitPath: string;
|
|
8
|
+
readonly action: WriteAction;
|
|
9
|
+
readonly scope: ConvergeScope;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CreateConvergeUnitInput {
|
|
13
|
+
readonly repoRoot: string;
|
|
14
|
+
readonly targetPath: string;
|
|
15
|
+
readonly gitPath: string;
|
|
16
|
+
readonly action: WriteAction;
|
|
17
|
+
readonly scope: ConvergeScope;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createConvergeUnit(input: CreateConvergeUnitInput): ConvergeUnit;
|
|
21
|
+
export declare function convergeUnitFromTarget(repoRoot: string, targetPath: string, input: Pick<CreateConvergeUnitInput, "action" | "scope" | "reason">): ConvergeUnit;
|
|
22
|
+
/** Applied units are the sole authority for authored commit scope. */
|
|
23
|
+
export declare function deriveCommitAllowlist(appliedUnits: readonly ConvergeUnit[]): string[];
|