@webpresso/agent-kit 3.1.30 → 3.3.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.
- package/README.md +53 -15
- package/bin/_run.js +183 -20
- 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/blueprint-trust.js +14 -10
- package/dist/esm/audit/changeset-evidence.d.ts +23 -0
- package/dist/esm/audit/changeset-evidence.js +30 -0
- package/dist/esm/audit/changeset-required.d.ts +10 -0
- package/dist/esm/audit/changeset-required.js +73 -0
- package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
- package/dist/esm/audit/ci-guardrails-detection.js +68 -0
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +80 -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 +13 -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 +101 -2
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- 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 +87 -20
- 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.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
- 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/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- 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/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- 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 +74 -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 +15 -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 +43 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
- package/dist/esm/cli/commands/agent-launch.js +93 -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 +21 -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 +30 -6
- 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 +88 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +325 -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 +3 -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/package-root.d.ts +7 -0
- package/dist/esm/cli/commands/init/package-root.js +10 -2
- 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 +20 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
- 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 +54 -24
- 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 +22 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -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-probe.d.ts +107 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
- package/dist/esm/cli/commands/opencode-rotate.js +147 -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/quality-runner.js +3 -3
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +751 -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/rust-check.d.ts +24 -0
- package/dist/esm/cli/commands/rust-check.js +110 -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/typecheck.d.ts +1 -0
- package/dist/esm/cli/commands/typecheck.js +30 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +6 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- 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 +5 -3
- package/dist/esm/hooks/doctor.js +67 -16
- 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/pretool-guard/validators/worktree-discipline.js +6 -1
- 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 +50 -9
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
- 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 +5 -1
- package/dist/esm/mcp/tools/audit.js +42 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -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-fetch-and-index.d.ts +1 -1
- 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/package.json +2 -0
- package/dist/esm/platform/client.d.ts +7 -1
- package/dist/esm/platform/github-contract-source.d.ts +2 -1
- package/dist/esm/pr-description/contract.js +24 -4
- package/dist/esm/review/authority.js +27 -14
- package/dist/esm/review/availability.d.ts +33 -0
- package/dist/esm/review/availability.js +122 -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.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +36 -10
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- 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 +121 -21
- package/dist/esm/review/execution/supervisor.js +36 -4
- package/dist/esm/review/execution/types.d.ts +9 -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 +25 -0
- package/dist/esm/review/subject.js +116 -26
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/rust/affected-manifests.d.ts +38 -0
- package/dist/esm/rust/affected-manifests.js +69 -0
- package/dist/esm/session-memory/fetch-index.d.ts +1 -1
- package/dist/esm/status/snapshot.js +32 -25
- 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 -2
- package/dist/esm/test-helpers/global-setup.js +79 -32
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- 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/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type GuardOutcome } from "#hooks/shared/guard-outcome";
|
|
1
2
|
export type LogStatus = "PASS" | "BLOCK" | "WARN" | "ERROR";
|
|
2
3
|
export type ToolType = string;
|
|
3
4
|
export interface LogEntry {
|
|
@@ -6,6 +7,10 @@ export interface LogEntry {
|
|
|
6
7
|
tool: ToolType;
|
|
7
8
|
failures?: string[];
|
|
8
9
|
error?: string;
|
|
10
|
+
/** GUARD_* reason code for the decision (additive; present on BLOCK/WARN). */
|
|
11
|
+
code?: string;
|
|
12
|
+
/** Two-axis guard outcome for the decision (additive). */
|
|
13
|
+
outcome?: GuardOutcome;
|
|
9
14
|
}
|
|
10
15
|
export interface LogConfig {
|
|
11
16
|
logDir: string;
|
|
@@ -20,6 +25,8 @@ export interface ParsedLogLine {
|
|
|
20
25
|
target: string;
|
|
21
26
|
failures?: string[];
|
|
22
27
|
error?: string;
|
|
28
|
+
code?: string;
|
|
29
|
+
outcome?: GuardOutcome;
|
|
23
30
|
}
|
|
24
31
|
export declare function createLogConfig(): LogConfig;
|
|
25
32
|
export declare function parseLogLine(line: string): ParsedLogLine | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { GUARD_EFFECT, GUARD_INTENT } from "#hooks/shared/guard-outcome";
|
|
3
4
|
import { redactText } from "#mcp/tools/_shared/redact.js";
|
|
4
5
|
import { getSurfacePath } from "#paths/state-root.js";
|
|
5
6
|
const DEFAULT_MAX_LINES = 250;
|
|
@@ -42,6 +43,8 @@ export function parseLogLine(line) {
|
|
|
42
43
|
? parsed.failures.filter((value) => typeof value === "string")
|
|
43
44
|
: undefined,
|
|
44
45
|
error: typeof parsed.error === "string" ? parsed.error : undefined,
|
|
46
|
+
code: typeof parsed.code === "string" ? parsed.code : undefined,
|
|
47
|
+
outcome: isGuardOutcome(parsed.outcome) ? parsed.outcome : undefined,
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
export function rotateLines(lines, maxLines) {
|
|
@@ -100,3 +103,16 @@ function isLogStatus(value) {
|
|
|
100
103
|
function isToolType(value) {
|
|
101
104
|
return typeof value === "string" && value.length > 0;
|
|
102
105
|
}
|
|
106
|
+
const GUARD_EFFECT_VALUES = new Set(Object.values(GUARD_EFFECT));
|
|
107
|
+
const GUARD_INTENT_VALUES = new Set(Object.values(GUARD_INTENT));
|
|
108
|
+
function isGuardOutcome(value) {
|
|
109
|
+
if (typeof value !== "object" || value === null)
|
|
110
|
+
return false;
|
|
111
|
+
const record = value;
|
|
112
|
+
const effect = record["effect"];
|
|
113
|
+
const intent = record["intent"];
|
|
114
|
+
return (typeof effect === "string" &&
|
|
115
|
+
GUARD_EFFECT_VALUES.has(effect) &&
|
|
116
|
+
typeof intent === "string" &&
|
|
117
|
+
GUARD_INTENT_VALUES.has(intent));
|
|
118
|
+
}
|
|
@@ -10,6 +10,12 @@ export interface ProcessValidationOptions {
|
|
|
10
10
|
readonly optimizationRules?: readonly OptimizationRule[];
|
|
11
11
|
readonly recordOptimization?: (record: OptimizationTelemetryRecord) => void;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Registered GUARD_* codes the runner emits directly (beyond the dev-routing
|
|
15
|
+
* inventory and the per-validator invariant codes in GUARD_VALIDATOR_CODES).
|
|
16
|
+
* Consumed by the guard-outcome registry-completeness test.
|
|
17
|
+
*/
|
|
18
|
+
export declare const RUNNER_GUARD_CODES: readonly ["GUARD_PROMPT_INTENT"];
|
|
13
19
|
export declare function runAllValidators(input: ToolInput): AggregateResult;
|
|
14
20
|
export declare function formatOutput(aggregate: AggregateResult, input: ToolInput): void;
|
|
15
21
|
export declare function getToolType(input: ToolInput): string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getActivePromptRouting, isGuardEnabled } from "#hooks/guard-switch/state";
|
|
3
|
+
import { formatGuardReason, GUARD_EFFECT, GUARD_INTENT, GUARD_VALIDATOR_CODES, outcomeForCode, validatorUnexpectedCode, } from "#hooks/shared/guard-outcome";
|
|
3
4
|
import { routePromptIntent } from "#hooks/prompt-routing";
|
|
4
|
-
import { readStdinJson,
|
|
5
|
+
import { readStdinJson, suppressDecorativeStderr, writeGuardStderr, writeHookOutput, } from "#hooks/shared/hook-bootstrap";
|
|
5
6
|
import { isSyntheticProcessExit } from "#hooks/shared/synthetic-process-exit.js";
|
|
6
7
|
import { buildDenyEnvelope, buildUpdatedInputEnvelope, cloneToolInputWithCommand, getCommand, getFilePath, isBashInput, parseToolInput, } from "#hooks/shared/types";
|
|
7
8
|
import { logRun } from "./logger.js";
|
|
@@ -15,6 +16,22 @@ const YELLOW = "\x1b[33m";
|
|
|
15
16
|
const DIM = "\x1b[2m";
|
|
16
17
|
const BOLD = "\x1b[1m";
|
|
17
18
|
const NC = "\x1b[0m";
|
|
19
|
+
/** Code the pretool-guard emits when a routed prompt-intent forces a redirect. */
|
|
20
|
+
const PROMPT_INTENT_CODE = "GUARD_PROMPT_INTENT";
|
|
21
|
+
/**
|
|
22
|
+
* Registered GUARD_* codes the runner emits directly (beyond the dev-routing
|
|
23
|
+
* inventory and the per-validator invariant codes in GUARD_VALIDATOR_CODES).
|
|
24
|
+
* Consumed by the guard-outcome registry-completeness test.
|
|
25
|
+
*/
|
|
26
|
+
export const RUNNER_GUARD_CODES = [
|
|
27
|
+
PROMPT_INTENT_CODE,
|
|
28
|
+
];
|
|
29
|
+
/** Resolve the GUARD_* code for a failing validator, with a per-validator fallback. */
|
|
30
|
+
function codeForValidator(validatorName) {
|
|
31
|
+
if (!validatorName)
|
|
32
|
+
return "GUARD_VALIDATION_UNEXPECTED";
|
|
33
|
+
return GUARD_VALIDATOR_CODES[validatorName] ?? validatorUnexpectedCode(validatorName);
|
|
34
|
+
}
|
|
18
35
|
export function runAllValidators(input) {
|
|
19
36
|
const results = VALIDATORS.map((v) => v(input));
|
|
20
37
|
const failed = results.filter((r) => !r.passed);
|
|
@@ -62,20 +79,35 @@ export function getTarget(input) {
|
|
|
62
79
|
export function logValidationResult(result, target, tool) {
|
|
63
80
|
if (!result.passed) {
|
|
64
81
|
const failed = result.results.filter((r) => !r.passed);
|
|
82
|
+
const code = codeForValidator(failed[0]?.validator);
|
|
65
83
|
logRun({
|
|
66
84
|
status: "BLOCK",
|
|
67
85
|
target: target.slice(0, 100),
|
|
68
86
|
tool,
|
|
69
87
|
failures: failed.map((f) => f.validator),
|
|
88
|
+
code,
|
|
89
|
+
outcome: outcomeForCode(code),
|
|
70
90
|
});
|
|
71
91
|
return;
|
|
72
92
|
}
|
|
73
93
|
const warnings = result.results.filter((r) => r.passed && r.message);
|
|
94
|
+
if (warnings.length > 0) {
|
|
95
|
+
// A warn does not reach the wire; it is advisory guidance in logs only.
|
|
96
|
+
const code = codeForValidator(warnings[0]?.validator);
|
|
97
|
+
logRun({
|
|
98
|
+
status: "WARN",
|
|
99
|
+
target: target.slice(0, 100),
|
|
100
|
+
tool,
|
|
101
|
+
failures: warnings.map((w) => w.validator),
|
|
102
|
+
code,
|
|
103
|
+
outcome: { effect: GUARD_EFFECT.allow, intent: GUARD_INTENT.advise },
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
74
107
|
logRun({
|
|
75
|
-
status:
|
|
108
|
+
status: "PASS",
|
|
76
109
|
target: target.slice(0, 100),
|
|
77
110
|
tool,
|
|
78
|
-
failures: warnings.length > 0 ? warnings.map((w) => w.validator) : undefined,
|
|
79
111
|
});
|
|
80
112
|
}
|
|
81
113
|
export function handleParseError(error, inputJson) {
|
|
@@ -87,18 +119,20 @@ export function handleParseError(error, inputJson) {
|
|
|
87
119
|
});
|
|
88
120
|
console.error(`${RED}❌ Pretool Guard: Error parsing input${NC}`);
|
|
89
121
|
console.error(`${RED} ${error instanceof Error ? error.message : "Unknown error"}${NC}`);
|
|
122
|
+
// Fail-closed as a CODED deny envelope so malformed/unhandled input still
|
|
123
|
+
// carries the stable GUARD_PRETOOL_FAILURE code on the wire, not a bare exit 2.
|
|
124
|
+
const detail = error instanceof Error ? error.message : "unknown error";
|
|
125
|
+
writeDenyDecision(formatGuardReason("GUARD_PRETOOL_FAILURE", `pretool-guard could not evaluate tool input: ${detail}`));
|
|
90
126
|
process.exit(2);
|
|
91
127
|
}
|
|
92
128
|
function writeDenyDecision(permissionDecisionReason) {
|
|
129
|
+
// Deliver the coded reason on BOTH host channels: the stdout deny envelope
|
|
130
|
+
// (Codex reads hookSpecificOutput; Grok reads the dual top-level decision) AND
|
|
131
|
+
// the REAL stderr via writeGuardStderr (Claude reads stderr as the blocking
|
|
132
|
+
// feedback on an exit-2 PreToolUse deny — it ignores stdout there). The
|
|
133
|
+
// decorative diagnostics that go through console.error stay suppressed.
|
|
93
134
|
writeHookOutput(JSON.stringify(buildDenyEnvelope({ reason: permissionDecisionReason })));
|
|
94
|
-
|
|
95
|
-
// suppressStderr may null out fd 2; dual JSON still carries the reason for Grok.
|
|
96
|
-
try {
|
|
97
|
-
process.stderr.write(`${permissionDecisionReason}\n`);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// ignore
|
|
101
|
-
}
|
|
135
|
+
writeGuardStderr(`${permissionDecisionReason}\n`);
|
|
102
136
|
}
|
|
103
137
|
function writeUpdatedInput(input, command) {
|
|
104
138
|
writeHookOutput(JSON.stringify(buildUpdatedInputEnvelope(input, command)));
|
|
@@ -125,12 +159,16 @@ function evaluateRoutingPolicy(input) {
|
|
|
125
159
|
return {
|
|
126
160
|
snapshot: { promptIntentReason, toolInputAction, commandActions: [] },
|
|
127
161
|
denyReason: promptIntentReason,
|
|
162
|
+
code: PROMPT_INTENT_CODE,
|
|
163
|
+
outcome: outcomeForCode(PROMPT_INTENT_CODE),
|
|
128
164
|
};
|
|
129
165
|
}
|
|
130
166
|
if (toolInputAction?.action === "deny" || toolInputAction?.action === "sandbox") {
|
|
131
167
|
return {
|
|
132
168
|
snapshot: { promptIntentReason, toolInputAction, commandActions },
|
|
133
169
|
denyReason: toolInputAction.guidance,
|
|
170
|
+
code: toolInputAction.code,
|
|
171
|
+
outcome: outcomeForCode(toolInputAction.code),
|
|
134
172
|
};
|
|
135
173
|
}
|
|
136
174
|
for (const commandAction of commandActions) {
|
|
@@ -138,18 +176,24 @@ function evaluateRoutingPolicy(input) {
|
|
|
138
176
|
return {
|
|
139
177
|
snapshot: { promptIntentReason, toolInputAction, commandActions },
|
|
140
178
|
denyReason: commandAction.action.guidance,
|
|
179
|
+
code: commandAction.action.code,
|
|
180
|
+
outcome: outcomeForCode(commandAction.action.code),
|
|
141
181
|
};
|
|
142
182
|
}
|
|
143
183
|
if (commandAction.action?.action === "sandbox" && !commandAction.alreadySandboxed) {
|
|
144
184
|
return {
|
|
145
185
|
snapshot: { promptIntentReason, toolInputAction, commandActions },
|
|
146
186
|
denyReason: commandAction.action.guidance,
|
|
187
|
+
code: commandAction.action.code,
|
|
188
|
+
outcome: outcomeForCode(commandAction.action.code),
|
|
147
189
|
};
|
|
148
190
|
}
|
|
149
191
|
}
|
|
150
192
|
return {
|
|
151
193
|
snapshot: { promptIntentReason, toolInputAction, commandActions },
|
|
152
194
|
denyReason: null,
|
|
195
|
+
code: null,
|
|
196
|
+
outcome: null,
|
|
153
197
|
};
|
|
154
198
|
}
|
|
155
199
|
function policySnapshotsMatch(originalRouting, replacementRouting, originalValidation, replacementValidation) {
|
|
@@ -182,8 +226,11 @@ export function processValidation(inputJson, options = {}) {
|
|
|
182
226
|
const input = parseToolInput(inputJson);
|
|
183
227
|
const command = isBashInput(input) ? getCommand(input) : null;
|
|
184
228
|
const originalRouting = evaluateRoutingPolicy(input);
|
|
185
|
-
if (originalRouting.denyReason !== null) {
|
|
186
|
-
|
|
229
|
+
if (originalRouting.denyReason !== null && originalRouting.code !== null) {
|
|
230
|
+
// formatGuardReason owns the 80-char budget on BOTH deny paths (this routing
|
|
231
|
+
// path was previously untruncated) and rides the GUARD_* code inside the
|
|
232
|
+
// host-displayed reason so consumers key on the code, not on wording.
|
|
233
|
+
writeDenyDecision(formatGuardReason(originalRouting.code, originalRouting.denyReason));
|
|
187
234
|
// Exit 2 so Grok (and other exit-code runners) treat this as an explicit
|
|
188
235
|
// deny; dual JSON still carries Claude/Codex hookSpecificOutput.
|
|
189
236
|
process.exit(2);
|
|
@@ -201,8 +248,11 @@ export function processValidation(inputJson, options = {}) {
|
|
|
201
248
|
.map((r) => r.message)
|
|
202
249
|
.find((message) => typeof message === "string" && message.length > 0);
|
|
203
250
|
const validatorReason = failed.map((r) => r.validator).join(", ");
|
|
204
|
-
const
|
|
205
|
-
|
|
251
|
+
const rawReason = messageReason ?? (validatorReason || "pretool-guard validation failed");
|
|
252
|
+
// Code derives from the first failing validator's invariant, with a
|
|
253
|
+
// per-validator GUARD_<NAME>_UNEXPECTED fallback for unclassified failures.
|
|
254
|
+
const code = codeForValidator(failed[0]?.validator);
|
|
255
|
+
writeDenyDecision(formatGuardReason(code, rawReason));
|
|
206
256
|
process.exit(2);
|
|
207
257
|
}
|
|
208
258
|
if (command) {
|
|
@@ -235,7 +285,9 @@ export function processValidation(inputJson, options = {}) {
|
|
|
235
285
|
process.exit(result.exitCode);
|
|
236
286
|
}
|
|
237
287
|
export async function main() {
|
|
238
|
-
|
|
288
|
+
// Suppress decorative stderr but keep fd 2 open so a deny can deliver its
|
|
289
|
+
// coded reason to the real stderr (Claude's exit-2 blocking-feedback channel).
|
|
290
|
+
suppressDecorativeStderr();
|
|
239
291
|
const inputJson = await readStdinJson();
|
|
240
292
|
if (!inputJson.trim()) {
|
|
241
293
|
writeHookOutput("{}\n");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { getFilePath, isFileEditInput, isFileWriteInput } from "#hooks/shared/types";
|
|
3
3
|
import { validateBlueprint as validateBlueprintShared } from "#hooks/shared/validators/blueprint";
|
|
4
|
-
import {
|
|
4
|
+
import { getCanonicalBlueprintDocument, getCanonicalBlueprintReviewLedgerPath, } from "./path-contract.js";
|
|
5
5
|
import { buildRedirectMessage } from "./mcp-redirect.js";
|
|
6
6
|
function normalizeFilePath(filePath, cwd) {
|
|
7
7
|
if (!path.isAbsolute(filePath))
|
|
@@ -10,6 +10,23 @@ function normalizeFilePath(filePath, cwd) {
|
|
|
10
10
|
return filePath.replace(/^\/+/, "");
|
|
11
11
|
return path.relative(cwd, filePath).replace(/\\/g, "/");
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Build a short, deterministic `<state>/<slug>` display identity from the
|
|
15
|
+
* PARSED match, never from the raw input path. The raw path is a
|
|
16
|
+
* cwd-relative string that can legitimately climb several parent-directory
|
|
17
|
+
* segments out of the effective repo root (e.g. into
|
|
18
|
+
* `.webpresso/worktrees/repos/<hash>/.../_overview.md` when cwd is the
|
|
19
|
+
* primary checkout but the edited file lives inside a managed blueprint
|
|
20
|
+
* worktree) — confusing to read and, worse, long enough to consume
|
|
21
|
+
* the guard's entire wire-message budget (`GUARD_REASON_BUDGET`, 80 Unicode
|
|
22
|
+
* code points total, see `#hooks/shared/guard-outcome.ts`) before any
|
|
23
|
+
* actionable "use this MCP tool" guidance is shown. Dropping the root prefix
|
|
24
|
+
* and document filename (both implied by the "blueprint" category, not
|
|
25
|
+
* distinguishing information) buys back budget for that guidance.
|
|
26
|
+
*/
|
|
27
|
+
function canonicalDisplayId(state, slug) {
|
|
28
|
+
return `${state}/${slug}`;
|
|
29
|
+
}
|
|
13
30
|
export function validateBlueprint(input) {
|
|
14
31
|
const filePath = getFilePath(input);
|
|
15
32
|
if (filePath && (isFileWriteInput(input) || isFileEditInput(input))) {
|
|
@@ -17,28 +34,31 @@ export function validateBlueprint(input) {
|
|
|
17
34
|
// Detect a canonical blueprint document from EITHER the cwd-normalized form
|
|
18
35
|
// OR the raw path. Detection is prefix-agnostic (path-contract), so an
|
|
19
36
|
// absolute worktree path is caught even when input.cwd is undefined or does
|
|
20
|
-
// not reduce the path to a recognized blueprints root
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
// not reduce the path to a recognized blueprints root — including when cwd
|
|
38
|
+
// points at an unrelated checkout (e.g. the primary checkout) and the file
|
|
39
|
+
// lives inside a managed worktree.
|
|
40
|
+
const canonicalDoc = getCanonicalBlueprintDocument(normalized) ?? getCanonicalBlueprintDocument(filePath);
|
|
41
|
+
if (canonicalDoc) {
|
|
23
42
|
return {
|
|
24
43
|
validator: "blueprint",
|
|
25
44
|
passed: false,
|
|
26
45
|
message: buildRedirectMessage({
|
|
27
46
|
category: "blueprint",
|
|
28
|
-
command:
|
|
29
|
-
fallbackHint: "
|
|
47
|
+
command: canonicalDisplayId(canonicalDoc.state, canonicalDoc.slug),
|
|
48
|
+
fallbackHint: "wp_blueprint_put",
|
|
30
49
|
}),
|
|
31
50
|
};
|
|
32
51
|
}
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const ledgerDoc = getCanonicalBlueprintReviewLedgerPath(normalized) ??
|
|
53
|
+
getCanonicalBlueprintReviewLedgerPath(filePath);
|
|
54
|
+
if (ledgerDoc) {
|
|
35
55
|
return {
|
|
36
56
|
validator: "blueprint",
|
|
37
57
|
passed: false,
|
|
38
58
|
message: buildRedirectMessage({
|
|
39
59
|
category: "blueprint",
|
|
40
|
-
command:
|
|
41
|
-
fallbackHint: "wp_blueprint_review_log
|
|
60
|
+
command: canonicalDisplayId(ledgerDoc.state, ledgerDoc.slug),
|
|
61
|
+
fallbackHint: "wp_blueprint_review_log",
|
|
42
62
|
}),
|
|
43
63
|
};
|
|
44
64
|
}
|
|
@@ -348,6 +348,7 @@ export function createAuditResult(command, rule, options = {}) {
|
|
|
348
348
|
}
|
|
349
349
|
const AUDIT_KIND_SET = new Set(MCP_AUDIT_KINDS);
|
|
350
350
|
const WP_AUDIT_RE = /^wp\s+audit\s+([a-z0-9-]+)\b/u;
|
|
351
|
+
const WP_CI_PREFLIGHT_RE = /^wp\s+ci-preflight\b/u;
|
|
351
352
|
const SCRIPT_INVOCATION_RE = /^(?:pnpm run|vp run|npm run|pnpm|npm)\s+([A-Za-z0-9:_-]+)/u;
|
|
352
353
|
const RAW_PM_RE = /^(?:pnpm|npm)\b/u;
|
|
353
354
|
function loadGuardConfig(projectDir) {
|
|
@@ -389,6 +390,53 @@ function findWpAuditRedirect(command) {
|
|
|
389
390
|
}
|
|
390
391
|
return undefined;
|
|
391
392
|
}
|
|
393
|
+
/** `wp ci-preflight` (CLI) → `wp_ci_preflight` MCP tool. 1:1 mapping, not gated on config. */
|
|
394
|
+
function findWpCiPreflightRedirect(command) {
|
|
395
|
+
for (const variant of getCommandVariants(command)) {
|
|
396
|
+
if (WP_CI_PREFLIGHT_RE.test(variant)) {
|
|
397
|
+
return `"${variant}" denied — use the webpresso MCP tool wp_ci_preflight(). Runs the same fail-fast, branch-scoped pre-push gate (format → guardrails → typecheck → lint → test) without shelling out. Returns structured, summary-first results.`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const OPENCODE_RUN_RE = /^opencode\s+run\b/u;
|
|
403
|
+
const CODEX_EXEC_OR_REVIEW_RE = /^codex\s+(?:exec|review)\b/u;
|
|
404
|
+
/**
|
|
405
|
+
* Raw provider-CLI outside-voice review invocations (`opencode run ...`,
|
|
406
|
+
* `codex exec ...`, `codex review`) → `wp_review_run`. Generic; not gated on
|
|
407
|
+
* config, mirroring `findWpAuditRedirect`.
|
|
408
|
+
*
|
|
409
|
+
* Deliberately narrow: only the review-launching subcommands are blocked.
|
|
410
|
+
* `codex login status`, `codex --version`, `opencode models opencode-go`
|
|
411
|
+
* (listing available models — documented in the `codex` skill), and
|
|
412
|
+
* `opencode --version` are legitimate, non-review invocations and must stay
|
|
413
|
+
* unblocked.
|
|
414
|
+
*
|
|
415
|
+
* A single tool exists here (not a category with several natural
|
|
416
|
+
* `wp_<suffix>` matchers), so this deliberately does NOT go through the
|
|
417
|
+
* generic `CommandRule`/`buildRedirectMessage` category pipeline — that
|
|
418
|
+
* machinery always derives its "use the webpresso MCP tool" claim from a
|
|
419
|
+
* category → tool-suffix map, which is correct for lint/test/typecheck
|
|
420
|
+
* (real per-category MCP tools) but would misname the single review tool.
|
|
421
|
+
* Mirrors `findWpAuditRedirect`'s unconditional standalone-message shape:
|
|
422
|
+
* always names `wp_review_run` directly rather than gating on
|
|
423
|
+
* `resolveMcpReady` (the fallback tier that message implies — the `wp
|
|
424
|
+
* review run` CLI documented in the `codex` skill — is unconditionally
|
|
425
|
+
* still available as a manual fallback when MCP genuinely is not ready).
|
|
426
|
+
*/
|
|
427
|
+
function findReviewCliRedirect(command) {
|
|
428
|
+
for (const variant of getCommandVariants(command)) {
|
|
429
|
+
if (!OPENCODE_RUN_RE.test(variant) && !CODEX_EXEC_OR_REVIEW_RE.test(variant))
|
|
430
|
+
continue;
|
|
431
|
+
return [
|
|
432
|
+
`"${variant}" denied — use the webpresso MCP tool: wp_review_run(...).`,
|
|
433
|
+
"It runs the probe + review stages of an outside-voice provider review in one call and returns structured, summary-first results with artifact capture under .webpresso/reviews.",
|
|
434
|
+
"Provider fallback (e.g. Codex out of API credits) is a `provider` PARAMETER change on that SAME tool call (claude|codex|opencode|grok) — never shell into a different provider's raw CLI.",
|
|
435
|
+
"Fallback if MCP is genuinely unavailable: the `wp review run` CLI (see the `codex` skill) — never a different provider's raw CLI.",
|
|
436
|
+
].join("\n");
|
|
437
|
+
}
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
392
440
|
/** Repo-declared `guard.scriptRoutes`: a package script mapped to an audit kind. */
|
|
393
441
|
function findScriptRouteRedirect(command, routes) {
|
|
394
442
|
for (const variant of getCommandVariants(command)) {
|
|
@@ -446,6 +494,12 @@ export function validateForbiddenCommands(input) {
|
|
|
446
494
|
const wpAuditRedirect = findWpAuditRedirect(command);
|
|
447
495
|
if (wpAuditRedirect)
|
|
448
496
|
return guardRedirect(wpAuditRedirect);
|
|
497
|
+
const wpCiPreflightRedirect = findWpCiPreflightRedirect(command);
|
|
498
|
+
if (wpCiPreflightRedirect)
|
|
499
|
+
return guardRedirect(wpCiPreflightRedirect);
|
|
500
|
+
const reviewCliRedirect = findReviewCliRedirect(command);
|
|
501
|
+
if (reviewCliRedirect)
|
|
502
|
+
return guardRedirect(reviewCliRedirect);
|
|
449
503
|
const guard = loadGuardConfig(projectDir);
|
|
450
504
|
if (guard?.scriptRoutes) {
|
|
451
505
|
const redirect = findScriptRouteRedirect(command, guard.scriptRoutes);
|
|
@@ -12,4 +12,11 @@ import { validateTestQuality } from "./test-quality.js";
|
|
|
12
12
|
import { validateUxQuality } from "./ux-quality.js";
|
|
13
13
|
import { validateWorktreeDiscipline } from "./worktree-discipline.js";
|
|
14
14
|
export declare const VALIDATORS: (typeof validateBlueprintFirst | typeof validateDocsGovernance | typeof validatePackageImports)[];
|
|
15
|
+
/**
|
|
16
|
+
* Canonical `validator` name each entry in {@link VALIDATORS} reports on its
|
|
17
|
+
* `ValidationResult`. Kept in lockstep with the array above; the guard-outcome
|
|
18
|
+
* registry-completeness test asserts every name has a GUARD_* invariant code
|
|
19
|
+
* (and vice versa).
|
|
20
|
+
*/
|
|
21
|
+
export declare const VALIDATOR_NAMES: readonly ["forbidden-commands", "dangerous-commands", "blueprint", "blueprint-first", "docs-governance", "plan-frontmatter", "complexity", "package-imports", "file-conventions", "command-file", "test-quality", "ux-quality", "worktree-discipline"];
|
|
15
22
|
export { validateBlueprint, validateBlueprintFirst, validateCommandFile, validateComplexity, validateDangerousCommands, validateDocsGovernance, validateFileConventions, validateForbiddenCommands, validatePackageImports, validatePlanFrontmatter, validateTestQuality, validateUxQuality, validateWorktreeDiscipline, };
|
|
@@ -26,4 +26,25 @@ export const VALIDATORS = [
|
|
|
26
26
|
validateUxQuality,
|
|
27
27
|
validateWorktreeDiscipline,
|
|
28
28
|
];
|
|
29
|
+
/**
|
|
30
|
+
* Canonical `validator` name each entry in {@link VALIDATORS} reports on its
|
|
31
|
+
* `ValidationResult`. Kept in lockstep with the array above; the guard-outcome
|
|
32
|
+
* registry-completeness test asserts every name has a GUARD_* invariant code
|
|
33
|
+
* (and vice versa).
|
|
34
|
+
*/
|
|
35
|
+
export const VALIDATOR_NAMES = [
|
|
36
|
+
"forbidden-commands",
|
|
37
|
+
"dangerous-commands",
|
|
38
|
+
"blueprint",
|
|
39
|
+
"blueprint-first",
|
|
40
|
+
"docs-governance",
|
|
41
|
+
"plan-frontmatter",
|
|
42
|
+
"complexity",
|
|
43
|
+
"package-imports",
|
|
44
|
+
"file-conventions",
|
|
45
|
+
"command-file",
|
|
46
|
+
"test-quality",
|
|
47
|
+
"ux-quality",
|
|
48
|
+
"worktree-discipline",
|
|
49
|
+
];
|
|
29
50
|
export { validateBlueprint, validateBlueprintFirst, validateCommandFile, validateComplexity, validateDangerousCommands, validateDocsGovernance, validateFileConventions, validateForbiddenCommands, validatePackageImports, validatePlanFrontmatter, validateTestQuality, validateUxQuality, validateWorktreeDiscipline, };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BlueprintDocumentPath, BlueprintStatus } from "#utils/document-paths.js";
|
|
1
2
|
export declare const BLUEPRINTS_ROOT = "webpresso/blueprints";
|
|
2
3
|
export declare const TECH_DEBT_ROOT = "webpresso/tech-debt";
|
|
3
4
|
/**
|
|
@@ -12,5 +13,23 @@ export declare function getNonCanonicalPlanningPathViolation(filePath: string, b
|
|
|
12
13
|
*/
|
|
13
14
|
export declare function isCanonicalBlueprintOverviewPath(filePath: string, blueprintsRoot?: string): boolean;
|
|
14
15
|
export declare function isCanonicalBlueprintDocumentPath(filePath: string, blueprintsRoot?: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Same detection as {@link isCanonicalBlueprintDocumentPath}, but returns the
|
|
18
|
+
* parsed `<state>/<slug>/<doc>` identity instead of a boolean. Callers that
|
|
19
|
+
* need to DISPLAY the match (e.g. a guard denial message) should build their
|
|
20
|
+
* text from this clean, root-relative identity rather than from the raw
|
|
21
|
+
* input path — a raw path can be an arbitrarily long, cwd-relative string
|
|
22
|
+
* (e.g. several parent-directory segments climbing out of an unrelated
|
|
23
|
+
* worktree) that is both confusing and liable to consume an entire
|
|
24
|
+
* wire-message budget before any actionable guidance is shown.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCanonicalBlueprintDocument(filePath: string, blueprintsRoot?: string): BlueprintDocumentPath | null;
|
|
27
|
+
export interface ReviewLedgerDocumentPath {
|
|
28
|
+
state: BlueprintStatus;
|
|
29
|
+
slug: string;
|
|
30
|
+
doc: "reviews.md" | "review-events.jsonl";
|
|
31
|
+
}
|
|
32
|
+
/** Same detection as {@link isCanonicalBlueprintReviewLedgerPath}, returning the parsed identity. */
|
|
33
|
+
export declare function getCanonicalBlueprintReviewLedgerPath(filePath: string, blueprintsRoot?: string): ReviewLedgerDocumentPath | null;
|
|
15
34
|
export declare function isCanonicalBlueprintReviewLedgerPath(filePath: string, blueprintsRoot?: string): boolean;
|
|
16
35
|
export declare function getBlueprintPathViolation(filePath: string, blueprintsRoot?: string, cwd?: string): string | null;
|
|
@@ -107,7 +107,17 @@ function findCanonicalBlueprintDocument(normalized, roots) {
|
|
|
107
107
|
}
|
|
108
108
|
return null;
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Same detection as {@link isCanonicalBlueprintDocumentPath}, but returns the
|
|
112
|
+
* parsed `<state>/<slug>/<doc>` identity instead of a boolean. Callers that
|
|
113
|
+
* need to DISPLAY the match (e.g. a guard denial message) should build their
|
|
114
|
+
* text from this clean, root-relative identity rather than from the raw
|
|
115
|
+
* input path — a raw path can be an arbitrarily long, cwd-relative string
|
|
116
|
+
* (e.g. several parent-directory segments climbing out of an unrelated
|
|
117
|
+
* worktree) that is both confusing and liable to consume an entire
|
|
118
|
+
* wire-message budget before any actionable guidance is shown.
|
|
119
|
+
*/
|
|
120
|
+
export function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
|
|
111
121
|
const normalized = normalizePlanningPath(filePath);
|
|
112
122
|
const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
|
|
113
123
|
return findCanonicalBlueprintDocument(normalized, roots);
|
|
@@ -115,12 +125,14 @@ function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
|
|
|
115
125
|
function parseReviewLedgerRelativePath(tail) {
|
|
116
126
|
const parts = tail.split("/").filter((segment) => segment.length > 0);
|
|
117
127
|
if (parts.length < 3)
|
|
118
|
-
return
|
|
128
|
+
return null;
|
|
119
129
|
const [state, slug, ...rest] = parts;
|
|
120
130
|
if (!isBlueprintStatus(state) || !slug)
|
|
121
|
-
return
|
|
131
|
+
return null;
|
|
122
132
|
const doc = rest.join("/");
|
|
123
|
-
|
|
133
|
+
if (doc !== "reviews.md" && doc !== "review-events.jsonl")
|
|
134
|
+
return null;
|
|
135
|
+
return { state, slug, doc };
|
|
124
136
|
}
|
|
125
137
|
/**
|
|
126
138
|
* Prefix-agnostic detection of a blueprint's review-ledger AUTHORITY files:
|
|
@@ -140,17 +152,22 @@ function findCanonicalBlueprintReviewLedgerPath(normalized, roots) {
|
|
|
140
152
|
if (!matchesHere)
|
|
141
153
|
continue;
|
|
142
154
|
const tail = segments.slice(start + rootSegments.length).join("/");
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
const parsed = parseReviewLedgerRelativePath(tail);
|
|
156
|
+
if (parsed)
|
|
157
|
+
return parsed;
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
|
-
return
|
|
160
|
+
return null;
|
|
148
161
|
}
|
|
149
|
-
|
|
162
|
+
/** Same detection as {@link isCanonicalBlueprintReviewLedgerPath}, returning the parsed identity. */
|
|
163
|
+
export function getCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) {
|
|
150
164
|
const normalized = normalizePlanningPath(filePath);
|
|
151
165
|
const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
|
|
152
166
|
return findCanonicalBlueprintReviewLedgerPath(normalized, roots);
|
|
153
167
|
}
|
|
168
|
+
export function isCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) {
|
|
169
|
+
return getCanonicalBlueprintReviewLedgerPath(filePath, blueprintsRoot) !== null;
|
|
170
|
+
}
|
|
154
171
|
export function getBlueprintPathViolation(filePath, blueprintsRoot, cwd = process.cwd()) {
|
|
155
172
|
const normalized = normalizePlanningPath(filePath);
|
|
156
173
|
if (!isBlueprintPath(normalized, blueprintsRoot))
|
|
@@ -95,7 +95,12 @@ export function validateWorktreeDiscipline(input) {
|
|
|
95
95
|
return { validator: VALIDATOR_NAME, passed: true };
|
|
96
96
|
}
|
|
97
97
|
function normalizedToolName(input) {
|
|
98
|
-
|
|
98
|
+
// tool_name is typed string but malformed host envelopes can deliver non-strings;
|
|
99
|
+
// never call string methods without a typeof guard (same class as isBashInput).
|
|
100
|
+
const raw = input.tool_name;
|
|
101
|
+
if (typeof raw !== "string")
|
|
102
|
+
return "";
|
|
103
|
+
return raw.toLowerCase().replaceAll(/[^a-z]/gu, "");
|
|
99
104
|
}
|
|
100
105
|
function applyPatchTargets(input) {
|
|
101
106
|
if (!normalizedToolName(input).endsWith("applypatch"))
|