@webpresso/agent-kit 3.3.3 → 3.3.4

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.
Files changed (136) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -10
  2. package/catalog/agent/rules/changeset-release.md +15 -4
  3. package/catalog/agent/rules/managed-tool-binaries.md +67 -0
  4. package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
  5. package/catalog/agent/rules/pre-implementation.md +36 -20
  6. package/catalog/agent/rules/rtk-routing.md +22 -16
  7. package/catalog/agent/skills/autopilot/SKILL.md +4 -3
  8. package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
  9. package/dist/esm/audit/hook-surface.js +5 -1
  10. package/dist/esm/audit/repo-guardrails.d.ts +8 -0
  11. package/dist/esm/audit/repo-guardrails.js +71 -1
  12. package/dist/esm/blueprint/core/parser.js +38 -12
  13. package/dist/esm/blueprint/execution/artifacts.js +8 -4
  14. package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
  15. package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
  16. package/dist/esm/blueprint/execution/metadata.js +8 -4
  17. package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
  18. package/dist/esm/blueprint/lifecycle/audit.js +89 -3
  19. package/dist/esm/blueprint/lifecycle/review-provenance.js +63 -10
  20. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
  21. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
  22. package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
  23. package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
  24. package/dist/esm/blueprint/markdown/helpers.js +10 -4
  25. package/dist/esm/blueprint/tracked-document/parser.js +3 -3
  26. package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
  27. package/dist/esm/blueprint/trust/gate-row.js +113 -0
  28. package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
  29. package/dist/esm/blueprint/trust/promotion.js +19 -19
  30. package/dist/esm/build/atomic-file-copy.d.ts +32 -0
  31. package/dist/esm/build/atomic-file-copy.js +44 -0
  32. package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
  33. package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
  34. package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
  35. package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
  36. package/dist/esm/cli/commands/compile.d.ts +13 -0
  37. package/dist/esm/cli/commands/compile.js +182 -62
  38. package/dist/esm/cli/commands/format.js +17 -1
  39. package/dist/esm/cli/commands/gain/index.js +10 -3
  40. package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
  41. package/dist/esm/cli/commands/init/index.js +29 -48
  42. package/dist/esm/cli/commands/init/mcp-spec.d.ts +48 -13
  43. package/dist/esm/cli/commands/init/mcp-spec.js +86 -30
  44. package/dist/esm/cli/commands/init/merge.js +30 -10
  45. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
  46. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
  47. package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
  48. package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
  49. package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
  50. package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
  51. package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
  52. package/dist/esm/cli/commands/pr-help.js +1 -1
  53. package/dist/esm/cli/commands/pr.js +18 -4
  54. package/dist/esm/cli/commands/review.js +60 -6
  55. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
  56. package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
  57. package/dist/esm/cli/commands/worktree/router.js +5 -2
  58. package/dist/esm/cli/direct-provider-launch.js +12 -4
  59. package/dist/esm/compiler/flatten.d.ts +10 -1
  60. package/dist/esm/compiler/flatten.js +14 -4
  61. package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
  62. package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
  63. package/dist/esm/compose/compose.d.ts +8 -0
  64. package/dist/esm/compose/compose.js +115 -8
  65. package/dist/esm/compose/ops-report-schema.js +17 -3
  66. package/dist/esm/compose/registry-resolve.d.ts +17 -0
  67. package/dist/esm/compose/registry-resolve.js +78 -11
  68. package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
  69. package/dist/esm/config/oxlint/import-hygiene.js +21 -8
  70. package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
  71. package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
  72. package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
  73. package/dist/esm/config/oxlint/oxlintrc.js +18 -21
  74. package/dist/esm/config/oxlint/path-roles.js +53 -0
  75. package/dist/esm/config/oxlint/query-patterns.js +2 -5
  76. package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
  77. package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
  78. package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
  79. package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
  80. package/dist/esm/daemon/domains/launch.d.ts +25 -0
  81. package/dist/esm/daemon/domains/launch.js +30 -1
  82. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
  83. package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
  84. package/dist/esm/daemon/handlers/product.d.ts +10 -0
  85. package/dist/esm/daemon/handlers/product.js +10 -1
  86. package/dist/esm/daemon/methods.d.ts +21 -1
  87. package/dist/esm/daemon/methods.js +20 -0
  88. package/dist/esm/daemon/protocol/contract.d.ts +58 -0
  89. package/dist/esm/daemon/protocol/contract.js +38 -0
  90. package/dist/esm/daemon/server.js +7 -0
  91. package/dist/esm/docs-linter/blueprint-plan.js +2 -2
  92. package/dist/esm/errors/wp-error.js +21 -2
  93. package/dist/esm/hooks/doctor.d.ts +11 -0
  94. package/dist/esm/hooks/doctor.js +114 -45
  95. package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
  96. package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
  97. package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
  98. package/dist/esm/hooks/pretool-guard/runner.js +6 -0
  99. package/dist/esm/hooks/status/index.js +1 -1
  100. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
  101. package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
  102. package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
  103. package/dist/esm/mcp/blueprint/registration.js +1 -1
  104. package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
  105. package/dist/esm/mcp/tools/_shared/redact.js +114 -8
  106. package/dist/esm/mcp/tools/gain.js +28 -2
  107. package/dist/esm/mcp/tools/worktree.js +65 -3
  108. package/dist/esm/mcp/tools/wp-ui-compose.js +38 -4
  109. package/dist/esm/mcp/tools/wp-ui-preview.js +14 -7
  110. package/dist/esm/review/lifecycle-writers.d.ts +76 -0
  111. package/dist/esm/review/lifecycle-writers.js +330 -0
  112. package/dist/esm/review/subject.js +66 -32
  113. package/dist/esm/status/snapshot.d.ts +5 -0
  114. package/dist/esm/status/snapshot.js +27 -2
  115. package/dist/esm/test/shard-durations.json +0 -1
  116. package/dist/esm/test-helpers/global-setup.js +38 -3
  117. package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
  118. package/dist/esm/tool-runtime/managed-rtk.js +275 -0
  119. package/dist/esm/tool-runtime/resolve-runner.js +18 -194
  120. package/dist/esm/utils/package-root.d.ts +32 -0
  121. package/dist/esm/utils/package-root.js +82 -0
  122. package/dist/esm/worktrees/git-metadata.d.ts +41 -0
  123. package/dist/esm/worktrees/git-metadata.js +48 -0
  124. package/package.json +16 -21
  125. package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
  126. package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
  127. package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
  128. package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
  129. package/dist/esm/config/oxlint/index.d.ts +0 -22
  130. package/dist/esm/config/oxlint/index.js +0 -28
  131. package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
  132. package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
  133. package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
  134. package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
  135. package/dist/esm/output-transforms/rulesync.d.ts +0 -2
  136. package/dist/esm/output-transforms/rulesync.js +0 -79
@@ -5,11 +5,21 @@
5
5
  * Domain implementations live in the sibling `*-handlers.ts` files; this
6
6
  * module only aggregates them into the map `startDaemonServer` consumes.
7
7
  */
8
+ import type { BlueprintExecLedger } from "#daemon/domains/blueprint-exec-ledger.js";
9
+ import type { LaunchLedger } from "#daemon/domains/launch.js";
8
10
  import type { MethodHandler } from "#daemon/rpc.js";
9
11
  /**
10
12
  * Build the default handler map. Callers may override individual methods via
11
13
  * `startDaemonServer({ handlers })` — overrides win.
14
+ *
15
+ * `launchLedger` / `execLedger` are daemon-scoped (one per server, not one per
16
+ * connection): blueprint exec registers a job on the webview connection and
17
+ * binds the PTY session on the rust connection, so both peers must see the
18
+ * same ledger instances.
12
19
  */
13
20
  export declare function createDefaultProductHandlers(options?: {
14
21
  readonly repoRoot?: string;
22
+ readonly allowlistedRoots?: readonly string[];
23
+ readonly launchLedger?: LaunchLedger;
24
+ readonly execLedger?: BlueprintExecLedger;
15
25
  }): Readonly<Partial<Record<string, MethodHandler>>>;
@@ -6,13 +6,22 @@ import { deployHandlers } from "./deploy-handlers.js";
6
6
  /**
7
7
  * Build the default handler map. Callers may override individual methods via
8
8
  * `startDaemonServer({ handlers })` — overrides win.
9
+ *
10
+ * `launchLedger` / `execLedger` are daemon-scoped (one per server, not one per
11
+ * connection): blueprint exec registers a job on the webview connection and
12
+ * binds the PTY session on the rust connection, so both peers must see the
13
+ * same ledger instances.
9
14
  */
10
15
  export function createDefaultProductHandlers(options) {
11
16
  const repoRoot = options?.repoRoot ?? process.cwd();
12
17
  return {
13
18
  ...gitHandlers,
14
19
  ...createFleetHandlers(repoRoot),
15
- ...createBlueprintsHandlers(repoRoot),
20
+ ...createBlueprintsHandlers(repoRoot, {
21
+ launchLedger: options?.launchLedger,
22
+ execLedger: options?.execLedger,
23
+ allowlistedRoots: options?.allowlistedRoots,
24
+ }),
16
25
  ...createSessionsHandlers(repoRoot),
17
26
  ...deployHandlers,
18
27
  };
@@ -148,6 +148,26 @@ export declare const DAEMON_METHOD_REGISTRY: {
148
148
  readonly requireHandshake: true;
149
149
  readonly uiFacing: true;
150
150
  };
151
+ /** Read-only exec attribution + executor-written progress. Never PTY-derived. */
152
+ readonly "blueprints.execStatus": {
153
+ readonly privileged: true;
154
+ readonly requiresConfirmation: false;
155
+ readonly mutates: false;
156
+ readonly requireHandshake: true;
157
+ readonly uiFacing: true;
158
+ };
159
+ /**
160
+ * Rust-only: binds the spawned PTY session id to an exec job. Not UI-facing —
161
+ * the webview must never be able to re-point a job at another session.
162
+ */
163
+ readonly "blueprints.execBind": {
164
+ readonly privileged: true;
165
+ readonly requiresConfirmation: false;
166
+ readonly mutates: true;
167
+ readonly requireHandshake: true;
168
+ readonly allowedRoles: readonly ["rust"];
169
+ readonly uiFacing: false;
170
+ };
151
171
  readonly "deploy.run": {
152
172
  readonly privileged: true;
153
173
  readonly requiresConfirmation: true;
@@ -171,7 +191,7 @@ export type UiRpcMethod = {
171
191
  [M in DaemonRpcMethod]: (typeof DAEMON_METHOD_REGISTRY)[M]["uiFacing"] extends true ? M : never;
172
192
  }[DaemonRpcMethod];
173
193
  export declare const UI_RPC_METHODS: UiRpcMethod[];
174
- export declare const DESTRUCTIVE_METHODS: ("auth.issueNonce" | "blueprints.exec" | "blueprints.list" | "daemon.envMap.push" | "deploy.run" | "fleet.status" | "git.commit" | "git.createPr" | "git.diff" | "handshake" | "launch.claim" | "launch.permissionState" | "launch.prepare" | "ping" | "sessions.list" | "sessions.search" | "worktrees.list")[];
194
+ export declare const DESTRUCTIVE_METHODS: ("auth.issueNonce" | "blueprints.exec" | "blueprints.execBind" | "blueprints.execStatus" | "blueprints.list" | "daemon.envMap.push" | "deploy.run" | "fleet.status" | "git.commit" | "git.createPr" | "git.diff" | "handshake" | "launch.claim" | "launch.permissionState" | "launch.prepare" | "ping" | "sessions.list" | "sessions.search" | "worktrees.list")[];
175
195
  export declare function getMethodCapability(method: string): MethodCapability | null;
176
196
  export declare function isDaemonRpcMethod(method: string): method is DaemonRpcMethod;
177
197
  export declare function isUiRpcMethod(method: string): method is UiRpcMethod;
@@ -116,6 +116,26 @@ export const DAEMON_METHOD_REGISTRY = {
116
116
  requireHandshake: true,
117
117
  uiFacing: true,
118
118
  },
119
+ /** Read-only exec attribution + executor-written progress. Never PTY-derived. */
120
+ "blueprints.execStatus": {
121
+ privileged: true,
122
+ requiresConfirmation: false,
123
+ mutates: false,
124
+ requireHandshake: true,
125
+ uiFacing: true,
126
+ },
127
+ /**
128
+ * Rust-only: binds the spawned PTY session id to an exec job. Not UI-facing —
129
+ * the webview must never be able to re-point a job at another session.
130
+ */
131
+ "blueprints.execBind": {
132
+ privileged: true,
133
+ requiresConfirmation: false,
134
+ mutates: true,
135
+ requireHandshake: true,
136
+ allowedRoles: ["rust"],
137
+ uiFacing: false,
138
+ },
119
139
  "deploy.run": {
120
140
  privileged: true,
121
141
  requiresConfirmation: true,
@@ -79,6 +79,8 @@ export type HandshakeResult = z.infer<typeof handshakeResultSchema>;
79
79
  export declare const daemonRpcMethodSchema: z.ZodEnum<{
80
80
  "auth.issueNonce": "auth.issueNonce";
81
81
  "blueprints.exec": "blueprints.exec";
82
+ "blueprints.execBind": "blueprints.execBind";
83
+ "blueprints.execStatus": "blueprints.execStatus";
82
84
  "blueprints.list": "blueprints.list";
83
85
  "daemon.envMap.push": "daemon.envMap.push";
84
86
  "deploy.run": "deploy.run";
@@ -98,6 +100,7 @@ export declare const daemonRpcMethodSchema: z.ZodEnum<{
98
100
  export declare const uiRpcMethodSchema: z.ZodEnum<{
99
101
  "auth.issueNonce": "auth.issueNonce";
100
102
  "blueprints.exec": "blueprints.exec";
103
+ "blueprints.execStatus": "blueprints.execStatus";
101
104
  "blueprints.list": "blueprints.list";
102
105
  "deploy.run": "deploy.run";
103
106
  "fleet.status": "fleet.status";
@@ -303,11 +306,42 @@ export declare const blueprintsListResultSchema: z.ZodObject<{
303
306
  }, z.core.$strict>;
304
307
  export declare const blueprintsExecParamsSchema: z.ZodObject<{
305
308
  slug: z.ZodString;
309
+ taskId: z.ZodOptional<z.ZodString>;
306
310
  confirmationToken: z.ZodString;
307
311
  }, z.core.$strict>;
312
+ /**
313
+ * Opaque only. `jobId` is the durable exec-attribution key; `handle` is the
314
+ * single-use launch handle the Rust peer claims. Deliberately carries no
315
+ * command, args, cwd, or env — those never cross to the webview.
316
+ */
308
317
  export declare const blueprintsExecResultSchema: z.ZodObject<{
318
+ jobId: z.ZodString;
309
319
  handle: z.ZodString;
310
320
  }, z.core.$strict>;
321
+ export declare const blueprintsExecStatusParamsSchema: z.ZodObject<{
322
+ slug: z.ZodOptional<z.ZodString>;
323
+ }, z.core.$strict>;
324
+ /**
325
+ * Execution status is sourced from executor-written blueprint metadata, never
326
+ * from PTY output. The terminal stream is human-viewing only.
327
+ */
328
+ export declare const blueprintsExecStatusResultSchema: z.ZodObject<{
329
+ jobs: z.ZodArray<z.ZodObject<{
330
+ jobId: z.ZodString;
331
+ slug: z.ZodString;
332
+ taskId: z.ZodNullable<z.ZodString>;
333
+ sessionId: z.ZodNullable<z.ZodNumber>;
334
+ state: z.ZodEnum<{
335
+ preparing: "preparing";
336
+ running: "running";
337
+ terminal: "terminal";
338
+ }>;
339
+ startedAt: z.ZodNumber;
340
+ executionId: z.ZodNullable<z.ZodString>;
341
+ executionStatus: z.ZodNullable<z.ZodString>;
342
+ executionUpdatedAt: z.ZodNullable<z.ZodString>;
343
+ }, z.core.$strict>>;
344
+ }, z.core.$strict>;
311
345
  export declare const deployRunParamsSchema: z.ZodObject<{
312
346
  cwd: z.ZodString;
313
347
  lane: z.ZodString;
@@ -499,12 +533,36 @@ export declare const UI_METHOD_SCHEMAS: {
499
533
  readonly "blueprints.exec": {
500
534
  readonly params: z.ZodObject<{
501
535
  slug: z.ZodString;
536
+ taskId: z.ZodOptional<z.ZodString>;
502
537
  confirmationToken: z.ZodString;
503
538
  }, z.core.$strict>;
504
539
  readonly result: z.ZodObject<{
540
+ jobId: z.ZodString;
505
541
  handle: z.ZodString;
506
542
  }, z.core.$strict>;
507
543
  };
544
+ readonly "blueprints.execStatus": {
545
+ readonly params: z.ZodObject<{
546
+ slug: z.ZodOptional<z.ZodString>;
547
+ }, z.core.$strict>;
548
+ readonly result: z.ZodObject<{
549
+ jobs: z.ZodArray<z.ZodObject<{
550
+ jobId: z.ZodString;
551
+ slug: z.ZodString;
552
+ taskId: z.ZodNullable<z.ZodString>;
553
+ sessionId: z.ZodNullable<z.ZodNumber>;
554
+ state: z.ZodEnum<{
555
+ preparing: "preparing";
556
+ running: "running";
557
+ terminal: "terminal";
558
+ }>;
559
+ startedAt: z.ZodNumber;
560
+ executionId: z.ZodNullable<z.ZodString>;
561
+ executionStatus: z.ZodNullable<z.ZodString>;
562
+ executionUpdatedAt: z.ZodNullable<z.ZodString>;
563
+ }, z.core.$strict>>;
564
+ }, z.core.$strict>;
565
+ };
508
566
  readonly "deploy.run": {
509
567
  readonly params: z.ZodObject<{
510
568
  cwd: z.ZodString;
@@ -301,14 +301,48 @@ export const blueprintsListResultSchema = z
301
301
  export const blueprintsExecParamsSchema = z
302
302
  .object({
303
303
  slug: z.string().min(1),
304
+ /** Optional attribution only; never becomes part of the composed argv. */
305
+ taskId: z.string().min(1).optional(),
304
306
  confirmationToken: z.string().min(1),
305
307
  })
306
308
  .strict();
309
+ /**
310
+ * Opaque only. `jobId` is the durable exec-attribution key; `handle` is the
311
+ * single-use launch handle the Rust peer claims. Deliberately carries no
312
+ * command, args, cwd, or env — those never cross to the webview.
313
+ */
307
314
  export const blueprintsExecResultSchema = z
308
315
  .object({
316
+ jobId: z.string().min(1),
309
317
  handle: opaqueHandleSchema,
310
318
  })
311
319
  .strict();
320
+ export const blueprintsExecStatusParamsSchema = z
321
+ .object({
322
+ slug: z.string().min(1).optional(),
323
+ })
324
+ .strict();
325
+ /**
326
+ * Execution status is sourced from executor-written blueprint metadata, never
327
+ * from PTY output. The terminal stream is human-viewing only.
328
+ */
329
+ export const blueprintsExecStatusResultSchema = z
330
+ .object({
331
+ jobs: z.array(z
332
+ .object({
333
+ jobId: z.string().min(1),
334
+ slug: z.string().min(1),
335
+ taskId: z.string().nullable(),
336
+ sessionId: z.number().int().nonnegative().nullable(),
337
+ state: z.enum(["preparing", "running", "terminal"]),
338
+ startedAt: z.number().int().nonnegative(),
339
+ executionId: z.string().nullable(),
340
+ executionStatus: z.string().nullable(),
341
+ executionUpdatedAt: z.string().nullable(),
342
+ })
343
+ .strict()),
344
+ })
345
+ .strict();
312
346
  export const deployRunParamsSchema = z
313
347
  .object({
314
348
  cwd: z.string().min(1),
@@ -344,6 +378,10 @@ export const UI_METHOD_SCHEMAS = {
344
378
  "worktrees.list": { params: worktreesListParamsSchema, result: worktreesListResultSchema },
345
379
  "blueprints.list": { params: blueprintsListParamsSchema, result: blueprintsListResultSchema },
346
380
  "blueprints.exec": { params: blueprintsExecParamsSchema, result: blueprintsExecResultSchema },
381
+ "blueprints.execStatus": {
382
+ params: blueprintsExecStatusParamsSchema,
383
+ result: blueprintsExecStatusResultSchema,
384
+ },
347
385
  "deploy.run": { params: deployRunParamsSchema, result: deployRunResultSchema },
348
386
  };
349
387
  // ---------------------------------------------------------------------------
@@ -10,6 +10,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, sta
10
10
  import { createConnection, createServer } from "node:net";
11
11
  import { dirname, join } from "node:path";
12
12
  import { createDaemonAuthz } from "./authz.js";
13
+ import { createBlueprintExecLedger } from "./domains/blueprint-exec-ledger.js";
13
14
  import { createLaunchLedger } from "./domains/launch.js";
14
15
  import { createDefaultProductHandlers } from "./handlers/product.js";
15
16
  import { getDefaultDaemonSocketPath } from "./paths.js";
@@ -104,6 +105,9 @@ export async function startDaemonServer(options = {}) {
104
105
  const server = createServer();
105
106
  const connections = new Set();
106
107
  const launchLedger = createLaunchLedger();
108
+ // Daemon-scoped, not per-connection: the webview registers an exec job and
109
+ // the rust peer binds the PTY session from a different connection.
110
+ const execLedger = createBlueprintExecLedger();
107
111
  server.on("connection", (socket) => {
108
112
  connections.add(socket);
109
113
  socket.on("close", () => connections.delete(socket));
@@ -137,6 +141,9 @@ export async function startDaemonServer(options = {}) {
137
141
  handlers: {
138
142
  ...createDefaultProductHandlers({
139
143
  repoRoot: options.allowlistedRoots?.[0] ?? process.cwd(),
144
+ allowlistedRoots: options.allowlistedRoots,
145
+ launchLedger,
146
+ execLedger,
140
147
  }),
141
148
  ...options.handlers,
142
149
  },
@@ -1,3 +1,4 @@
1
+ import { extractBlockedReason } from "#markdown/blocked-line";
1
2
  /**
2
3
  * Blueprint Plan Format Validator
3
4
  *
@@ -394,7 +395,6 @@ function validateLifecycleContract(filePath, content) {
394
395
  const taskBlocks = extractTaskBlocks(content);
395
396
  for (const task of taskBlocks) {
396
397
  const statusMatch = task.section.match(/\*\*Status:\*\*\s*(.+)/i);
397
- const blockedMatch = task.section.match(/\*\*Blocked:\*\*\s*(.+)/i);
398
398
  const checkboxMatches = Array.from(task.section.matchAll(/^- \[([ x])\]/gm));
399
399
  const total = checkboxMatches.length;
400
400
  const checked = checkboxMatches.filter((match) => match[1] === "x").length;
@@ -407,7 +407,7 @@ function validateLifecycleContract(filePath, content) {
407
407
  errors.push(createTaskStatusInvalidError(filePath, task.id, taskStatus));
408
408
  continue;
409
409
  }
410
- const blockedReason = blockedMatch?.[1]?.trim() ?? "";
410
+ const blockedReason = extractBlockedReason(task.section) ?? "";
411
411
  if (taskStatus === "blocked" && !blockedReason) {
412
412
  errors.push(createBlockedReasonRequiredError(filePath, task.id));
413
413
  }
@@ -11,8 +11,23 @@ export function validateWpErrorDocsUrl(value) {
11
11
  return value;
12
12
  }
13
13
  function redactStringValue(value, explicitSecrets) {
14
- let next = redactText(value) ?? value;
15
- let redacted = next !== value;
14
+ // Caller-declared secrets are masked FIRST, against the untouched original
15
+ // string, and always resolve to the full `[REDACTED]` form. The generic
16
+ // shared-redactor (`redactText`) runs SECOND, on whatever the explicit pass
17
+ // left behind.
18
+ //
19
+ // The order is load-bearing: `redactText`'s pattern set is broad (see
20
+ // `_shared/redact.ts`) and grows over time (h-010). A caller-supplied
21
+ // secret can incidentally match one of those generic shapes (e.g. a value
22
+ // adjacent to a `TOKEN=`/`SECRET:` key). Running the generic pass first
23
+ // would replace the literal with a *partial* mask (`CA***23`) before the
24
+ // explicit pass ever sees it, so `next.includes(secret)` fails silently and
25
+ // the caller's declared secret ends up under-redacted relative to what the
26
+ // caller explicitly asked to fully hide. A value the caller names as a
27
+ // secret is a stronger, more specific signal than a generic pattern match
28
+ // and must win.
29
+ let next = value;
30
+ let redacted = false;
16
31
  for (const secret of [...explicitSecrets].sort((left, right) => right.length - left.length)) {
17
32
  if (!secret)
18
33
  continue;
@@ -21,6 +36,10 @@ function redactStringValue(value, explicitSecrets) {
21
36
  redacted = true;
22
37
  }
23
38
  }
39
+ const generic = redactText(next) ?? next;
40
+ if (generic !== next)
41
+ redacted = true;
42
+ next = generic;
24
43
  return { value: next, redacted };
25
44
  }
26
45
  function redactUnknownEvidence(value, explicitSecrets) {
@@ -57,6 +57,9 @@ export interface ResolvePackageRootForRuntimeOptions {
57
57
  }
58
58
  export declare function resolvePackageRootForRuntime(options?: ResolvePackageRootForRuntimeOptions): string | null;
59
59
  export declare function findOwningPackageRoot(startDir: string): string | null;
60
+ export declare function checkFreestandingRtkSurface(cwd?: string): DoctorCheck | null;
61
+ /** Non-mutating warn for user-global freestanding RTK rewriters (rtk init -g). */
62
+ export declare function checkGlobalFreestandingRtkSurface(): DoctorCheck | null;
60
63
  export declare function checkRtkOnPath(cwd?: string): Promise<DoctorCheck | null>;
61
64
  export declare function checkRootLauncherContract(): DoctorCheck;
62
65
  export declare function checkRuntimePluginCacheStaleSurfaceRepair(options?: {
@@ -66,6 +69,14 @@ export declare function checkRuntimePluginCacheStaleSurfaceRepair(options?: {
66
69
  }): DoctorCheck;
67
70
  export declare function checkNativePluginRuntime(): DoctorCheck;
68
71
  export declare function checkPhase2RuntimeTypecheckParity(): DoctorCheck;
72
+ /**
73
+ * Codex must receive `webpresso` over exactly one channel — the bundled Codex
74
+ * plugin. Report the retired user-global `[mcp_servers.webpresso]` block that
75
+ * `wp setup` removes, and call out a double registration distinctly. A
76
+ * user-authored block (a live `wp` outside every agent-kit root) is never
77
+ * removed by setup, so it gets its own manual-action finding.
78
+ */
79
+ export declare function classifyLegacyCodexWebpressoChannel(configPath?: string, homeDir?: string): string | null;
69
80
  export declare function checkPackagedHostArtifacts(cwd?: string): DoctorCheck;
70
81
  export declare function checkHostArtifactOwnership(cwd?: string): DoctorCheck;
71
82
  export declare function checkHostLifecycleDepth(): DoctorCheck;
@@ -27,6 +27,8 @@ import { isAgentKitSourceRepo } from "#cli/commands/init/source-repo-hook-policy
27
27
  import { findAgentKitPackageRoot, resolveAgentKitPackageRoot, } from "#cli/commands/init/package-root";
28
28
  import { readInstalledHooksMap } from "#hooks/shared/installed-hooks.js";
29
29
  import { auditCodexConfigPaths, checkCodexConfigPathIntegrity } from "#hooks/codex-config-paths.js";
30
+ import { findInstalledCodexPluginMcpManifests, pruneOwnedMcpServerBlock, } from "#cli/commands/init/scaffolders/codex-mcp/index.js";
31
+ import { isAgentKitOwnedWpCommand, WEBPRESSO_MCP_SERVER_NAME, } from "#cli/commands/init/mcp-spec.js";
30
32
  import { parseProjectMcpServers, scanCodexConfigDrift } from "#hooks/host-config-contract.js";
31
33
  import { defaultCodexConfigFilePath } from "#cli/commands/init/scaffolders/agent-hooks/codex-trust-sync.js";
32
34
  import { expectedRootWpBinRelativePath, formatRootLauncherContractFailure, rootContractMode, validateRootLauncherContract, } from "#launcher/root-contract.js";
@@ -55,7 +57,10 @@ function createOwnedProbeSettler(child, timeoutMs, timeoutResult, resolve) {
55
57
  return settle;
56
58
  }
57
59
  const RTK_REQUESTED_MARKER = join(".agent", ".rtk-requested");
58
- const RTK_INSTALL_HINT = "rtk requested via --with rtk but not on PATH; brew install rtk";
60
+ const RTK_INSTALL_HINT = "rtk requested but managed binary missing; re-run wp setup (or set WP_RTK_BIN)";
61
+ const RTK_FREESTANDING_HOOK_REL = join(".claude", "hooks", "rtk-rewrite.sh");
62
+ const RTK_FREESTANDING_SETTINGS_REL = join(".claude", "settings.json");
63
+ const RTK_FREESTANDING_REPAIR = "stale freestanding rtk-rewrite surface; run wp setup (ensureRtk strips rtk-rewrite.sh — pretool-guard owns rewrite)";
59
64
  const HOST_SMOKE_ENV = "WP_RUN_HOST_SMOKE";
60
65
  const HOOK_PROBE_TIMEOUT_ENV = "WP_DOCTOR_HOOK_TIMEOUT_MS";
61
66
  const WRAPPER_PROBE_ENV = "WP_DOCTOR_WRAPPER_PROBE";
@@ -347,6 +352,62 @@ function checkConsumerCodexHookPaths(cwd = process.cwd()) {
347
352
  function wasRtkRequested(cwd = process.cwd()) {
348
353
  return tryAccess(join(cwd, RTK_REQUESTED_MARKER));
349
354
  }
355
+ export function checkFreestandingRtkSurface(cwd = process.cwd()) {
356
+ const hookPath = join(cwd, RTK_FREESTANDING_HOOK_REL);
357
+ const settingsPath = join(cwd, RTK_FREESTANDING_SETTINGS_REL);
358
+ const hookPresent = tryAccess(hookPath);
359
+ const markers = [];
360
+ if (hookPresent)
361
+ markers.push(RTK_FREESTANDING_HOOK_REL);
362
+ if (tryAccess(settingsPath)) {
363
+ try {
364
+ const settings = readFileSync(settingsPath, "utf8");
365
+ if (settings.includes("rtk-rewrite.sh")) {
366
+ markers.push(`${RTK_FREESTANDING_SETTINGS_REL} rtk-rewrite.sh`);
367
+ }
368
+ if (settings.includes("rtk hook claude")) {
369
+ markers.push(`${RTK_FREESTANDING_SETTINGS_REL} rtk hook claude`);
370
+ }
371
+ }
372
+ catch {
373
+ // ignore unreadable settings
374
+ }
375
+ }
376
+ if (markers.length === 0)
377
+ return null;
378
+ return {
379
+ name: "freestanding rtk PreToolUse retired",
380
+ ok: false,
381
+ detail: `${RTK_FREESTANDING_REPAIR} (found: ${markers.join(", ")})`,
382
+ };
383
+ }
384
+ /** Non-mutating warn for user-global freestanding RTK rewriters (rtk init -g). */
385
+ export function checkGlobalFreestandingRtkSurface() {
386
+ const globalSettings = join(homedir(), ".claude", "settings.json");
387
+ const globalHook = join(homedir(), ".claude", "hooks", "rtk-rewrite.sh");
388
+ const markers = [];
389
+ if (tryAccess(globalHook))
390
+ markers.push("~/.claude/hooks/rtk-rewrite.sh");
391
+ if (tryAccess(globalSettings)) {
392
+ try {
393
+ const settings = readFileSync(globalSettings, "utf8");
394
+ if (settings.includes("rtk-rewrite.sh"))
395
+ markers.push("~/.claude/settings.json rtk-rewrite.sh");
396
+ if (settings.includes("rtk hook claude"))
397
+ markers.push("~/.claude/settings.json rtk hook claude");
398
+ }
399
+ catch {
400
+ // ignore
401
+ }
402
+ }
403
+ if (markers.length === 0)
404
+ return null;
405
+ return {
406
+ name: "global freestanding rtk (warn)",
407
+ ok: true,
408
+ detail: `WARNING: global freestanding RTK PreToolUse still present (${markers.join(", ")}); remove manually or re-run rtk uninstall — project setup does not mutate ~/.claude. Dual rewriters may race updatedInput with wp-pretool-guard.`,
409
+ };
410
+ }
350
411
  function shouldRunHostChecks(mode) {
351
412
  if (mode === "skip")
352
413
  return false;
@@ -393,36 +454,13 @@ function resolveRequestedHosts(mode, hostNames) {
393
454
  export function checkRtkOnPath(cwd) {
394
455
  if (!wasRtkRequested(cwd))
395
456
  return Promise.resolve(null);
396
- return new Promise((resolve) => {
397
- const timeoutMs = hookProbeTimeoutMs();
398
- const child = spawn("rtk", ["--version"], {
399
- stdio: ["ignore", "pipe", "pipe"],
400
- detached: process.platform !== "win32",
401
- });
402
- let stdout = "";
403
- let stderr = "";
404
- const settle = createOwnedProbeSettler(child, timeoutMs, () => ({
405
- name: "rtk on PATH",
406
- ok: false,
407
- detail: `rtk probe timed out after ${timeoutMs}ms`,
408
- }), resolve);
409
- child.stdout.on("data", (chunk) => {
410
- stdout += String(chunk);
411
- });
412
- child.stderr?.on("data", (chunk) => {
413
- stderr += String(chunk);
414
- });
415
- child.on("error", () => {
416
- settle({ name: "rtk on PATH", ok: false, detail: RTK_INSTALL_HINT });
417
- });
418
- child.on("close", (code) => {
419
- if (code === 0) {
420
- settle({ name: "rtk on PATH", ok: true, detail: stdout.trim() || "rtk present" });
421
- return;
422
- }
423
- const suffix = stderr.trim().length > 0 ? ` (${stderr.trim()})` : "";
424
- settle({ name: "rtk on PATH", ok: false, detail: `${RTK_INSTALL_HINT}${suffix}` });
425
- });
457
+ // Managed binary (Webpresso cache / WP_RTK_BIN), not bare PATH brew install.
458
+ return import("#tool-runtime/managed-rtk.js").then(({ isManagedRtkAvailable, resolveManagedRtkExecutable }) => {
459
+ const path = resolveManagedRtkExecutable();
460
+ if (path && isManagedRtkAvailable()) {
461
+ return { name: "rtk managed", ok: true, detail: path };
462
+ }
463
+ return { name: "rtk managed", ok: false, detail: RTK_INSTALL_HINT };
426
464
  });
427
465
  }
428
466
  async function probeHookBin(wpCli, hookName, checkStdin) {
@@ -1184,19 +1222,44 @@ async function checkCodexHost() {
1184
1222
  detail: `missing MCP entry (webpresso=${hasAgentKit})`,
1185
1223
  };
1186
1224
  }
1187
- const mcpPathFindings = auditCodexConfigPaths().filter((finding) => finding.location.startsWith("mcp_servers.webpresso."));
1188
- if (mcpPathFindings.length > 0) {
1189
- const detail = mcpPathFindings
1190
- .map((finding) => `${finding.location}: ${finding.path} (${finding.reason === "missing" ? "path does not exist" : "volatile versioned path"})`)
1191
- .join("; ");
1192
- return {
1193
- name: "Codex host integration",
1194
- ok: false,
1195
- detail: `webpresso MCP listed but launch paths are broken or rot-prone — ${detail}`,
1196
- };
1197
- }
1225
+ const legacy = classifyLegacyCodexWebpressoChannel();
1226
+ if (legacy)
1227
+ return { name: "Codex host integration", ok: false, detail: legacy };
1198
1228
  return { name: "Codex host integration", ok: true, detail: "webpresso MCP visible" };
1199
1229
  }
1230
+ /**
1231
+ * Codex must receive `webpresso` over exactly one channel — the bundled Codex
1232
+ * plugin. Report the retired user-global `[mcp_servers.webpresso]` block that
1233
+ * `wp setup` removes, and call out a double registration distinctly. A
1234
+ * user-authored block (a live `wp` outside every agent-kit root) is never
1235
+ * removed by setup, so it gets its own manual-action finding.
1236
+ */
1237
+ export function classifyLegacyCodexWebpressoChannel(configPath = defaultCodexConfigFilePath(), homeDir = process.env.HOME || homedir()) {
1238
+ const outcome = classifyCodexConfigWebpressoBlock(configPath);
1239
+ if (outcome === null || outcome.kind === "absent")
1240
+ return null;
1241
+ if (outcome.kind === "ambiguous") {
1242
+ return `${configPath} declares webpresso in a TOML spelling wp setup cannot rewrite; remove the [mcp_servers.webpresso] definition by hand — the bundled Codex plugin is the single delivery channel`;
1243
+ }
1244
+ if (outcome.kind === "preserved") {
1245
+ return `${configPath} declares a user-authored webpresso MCP server (${outcome.command ?? "no command"}); wp setup preserves it. Remove it by hand to use the bundled Codex plugin as the single delivery channel`;
1246
+ }
1247
+ const dualChannel = findInstalledCodexPluginMcpManifests(homeDir).length > 0;
1248
+ const prefix = dualChannel
1249
+ ? "webpresso is registered over BOTH delivery channels — the bundled Codex plugin AND a legacy"
1250
+ : "webpresso is still registered over the retired legacy";
1251
+ return `${prefix} [mcp_servers.webpresso] block in ${configPath} (command = ${outcome.command}); run \`wp setup\` — it removes the legacy block`;
1252
+ }
1253
+ function classifyCodexConfigWebpressoBlock(configPath) {
1254
+ try {
1255
+ if (!existsSync(configPath))
1256
+ return null;
1257
+ return pruneOwnedMcpServerBlock(readFileSync(configPath, "utf8"), WEBPRESSO_MCP_SERVER_NAME, isAgentKitOwnedWpCommand);
1258
+ }
1259
+ catch {
1260
+ return null;
1261
+ }
1262
+ }
1200
1263
  async function checkOpenCodeHost(cwd = process.cwd()) {
1201
1264
  const available = await runCommand("opencode", ["--version"]);
1202
1265
  if (!available.ok) {
@@ -1430,9 +1493,8 @@ export function checkPackagedHostArtifacts(cwd = process.cwd()) {
1430
1493
  const serverRecord = server && typeof server === "object" && !Array.isArray(server)
1431
1494
  ? server
1432
1495
  : null;
1433
- if (serverRecord?.command !== "${PLUGIN_ROOT}/bin/wp" ||
1434
- !stringArrayEquals(serverRecord.args, ["mcp"])) {
1435
- failures.push("Codex adapter MCP surface must expose direct webpresso server map launching ${PLUGIN_ROOT}/bin/wp mcp");
1496
+ if (serverRecord?.command !== "wp" || !stringArrayEquals(serverRecord.args, ["mcp"])) {
1497
+ failures.push("Codex adapter MCP surface must expose direct webpresso server map launching wp mcp");
1436
1498
  }
1437
1499
  }
1438
1500
  if (codexHooks) {
@@ -2138,6 +2200,13 @@ export async function runHooksDoctor(opts = {}) {
2138
2200
  const rtkCheck = await checkRtkOnPath(opts.cwd);
2139
2201
  if (rtkCheck)
2140
2202
  checks.push(rtkCheck);
2203
+ const freestandingRtk = checkFreestandingRtkSurface(opts.cwd);
2204
+ if (freestandingRtk)
2205
+ checks.push(freestandingRtk);
2206
+ // Non-mutating WARN only — never mutates ~/.claude; surfaces dual-rewriter risk.
2207
+ const globalFreestandingRtk = checkGlobalFreestandingRtkSurface();
2208
+ if (globalFreestandingRtk)
2209
+ checks.push(globalFreestandingRtk);
2141
2210
  if (shouldRunHostChecks(hostMode)) {
2142
2211
  for (const host of requestedHosts) {
2143
2212
  if (host === "codex") {
@@ -23,7 +23,7 @@ function routeRawMutatingGitWorktree(command) {
23
23
  return {
24
24
  action: "deny",
25
25
  tool: "wp_worktree",
26
- guidance: "Use the `wp_worktree` MCP tool (or `wp worktree` CLI when MCP is unavailable) instead — managed repositories may mutate linked worktrees only through agent-kit so owner bindings and the global registry stay consistent.",
26
+ guidance: "Use the `wp_worktree` MCP tool (or `wp worktree prune --all` / `wp worktree remove <target>` when MCP is unavailable) instead — managed repositories may mutate linked worktrees only through agent-kit so owner bindings and the global registry stay consistent. Both prune paths also run `git worktree prune`, so a hand-deleted worktree directory still releases the branch it claimed.",
27
27
  code: "GUARD_RAW_GIT_MUTATION",
28
28
  };
29
29
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * RTK rewrite inside path-stable pretool-guard (no freestanding consumer hook).
3
+ *
4
+ * v1: exact `git status` → `<managed-rtk> git status` when managed rtk is available.
5
+ */
6
+ export type RtkRewriteDecision = {
7
+ readonly action: "rewrite";
8
+ readonly command: string;
9
+ } | {
10
+ readonly action: "noop";
11
+ readonly reason: string;
12
+ };
13
+ export type RtkPresenceProbe = () => boolean;
14
+ export type RtkExecutableResolver = () => string | null;
15
+ /**
16
+ * Decide whether to rewrite a Bash command through managed rtk.
17
+ */
18
+ export declare function decideRtkRewrite(command: string, isRtkPresent?: RtkPresenceProbe, resolveExecutable?: RtkExecutableResolver): RtkRewriteDecision;
19
+ /**
20
+ * POSIX single-quote wrap for absolute managed paths.
21
+ * Refuse NUL/newline/single-quote — those cannot be safely embedded in `'…'`.
22
+ * Double-quote wrapping is intentionally avoided ($ / ` / " injection).
23
+ */
24
+ export declare function shellQuoteExecutable(path: string): string | null;