@wrongstack/core 0.309.0 → 0.310.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.
Files changed (129) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/director.d.ts +7 -0
  13. package/dist/coordination/explore-companion.d.ts +9 -6
  14. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  15. package/dist/coordination/index.d.ts +5 -1
  16. package/dist/coordination/index.js +2392 -405
  17. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  18. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  19. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  20. package/dist/coordination/mailbox-project-server.js +14 -9
  21. package/dist/coordination/mutation-engine.d.ts +5 -3
  22. package/dist/core/context.d.ts +35 -44
  23. package/dist/core/conversation-state.d.ts +16 -52
  24. package/dist/core/index.js +92 -25
  25. package/dist/core/provider-runner.d.ts +2 -2
  26. package/dist/core/run-env.d.ts +7 -28
  27. package/dist/core/streaming-response-builder.d.ts +2 -2
  28. package/dist/execution/index.js +138 -162
  29. package/dist/goal/index.js +100 -22
  30. package/dist/hq/auth-store.d.ts +9 -0
  31. package/dist/hq/cost-bridge.d.ts +1 -1
  32. package/dist/hq/index.js +69 -7
  33. package/dist/index.d.ts +12 -5
  34. package/dist/index.js +21185 -26807
  35. package/dist/infrastructure/index.js +232 -135
  36. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  37. package/dist/infrastructure/token-counter.d.ts +5 -1
  38. package/dist/kernel/events/file-events.d.ts +6 -0
  39. package/dist/kernel/events/provider-events.d.ts +10 -7
  40. package/dist/kernel/events/session-events.d.ts +4 -4
  41. package/dist/kernel/events/tool-events.d.ts +12 -2
  42. package/dist/observability/index.js +1 -1
  43. package/dist/plugin/index.js +218 -47
  44. package/dist/prompts/index.js +360 -3
  45. package/dist/security/auto-approve-policy.d.ts +2 -2
  46. package/dist/security/index.d.ts +1 -0
  47. package/dist/security/index.js +224 -68
  48. package/dist/security/kanban-boundary.d.ts +1 -1
  49. package/dist/security/kanban-governance-port.d.ts +28 -0
  50. package/dist/security/permission-helpers.d.ts +11 -0
  51. package/dist/security/permission-policy.d.ts +10 -1
  52. package/dist/security/yolo-risk.d.ts +17 -0
  53. package/dist/session-catalog/index.js +34 -5
  54. package/dist/session-catalog/project-server.js +34 -5
  55. package/dist/session-catalog/protocol.d.ts +1 -1
  56. package/dist/session-catalog/registry.d.ts +1 -1
  57. package/dist/session-catalog/store-schema.d.ts +1 -1
  58. package/dist/session-catalog/store.d.ts +1 -1
  59. package/dist/skills/index.js +39 -6
  60. package/dist/storage/annotations-store.d.ts +1 -1
  61. package/dist/storage/board-store-port.d.ts +45 -0
  62. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  63. package/dist/storage/config-loader/types.d.ts +1 -1
  64. package/dist/storage/event-bus-port.d.ts +27 -0
  65. package/dist/storage/file-session-writer.d.ts +47 -5
  66. package/dist/storage/goal-coordination.d.ts +1 -1
  67. package/dist/storage/goal-store.d.ts +1 -1
  68. package/dist/storage/index.d.ts +3 -4
  69. package/dist/storage/index.js +1107 -1462
  70. package/dist/storage/plan-store.d.ts +1 -1
  71. package/dist/storage/queue-store.d.ts +1 -1
  72. package/dist/storage/replay-log-store.d.ts +1 -1
  73. package/dist/storage/session-recovery.d.ts +10 -0
  74. package/dist/storage/session-resume-validation.d.ts +13 -1
  75. package/dist/storage/session-store/events.d.ts +1 -1
  76. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  77. package/dist/storage/session-store/rename-session.d.ts +1 -1
  78. package/dist/storage/session-store/resume-session.d.ts +3 -1
  79. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  80. package/dist/storage/session-store/types.d.ts +1 -1
  81. package/dist/storage/session-store.d.ts +70 -0
  82. package/dist/storage/session-summary-tracker.d.ts +8 -0
  83. package/dist/storage/session-write-buffer.d.ts +31 -2
  84. package/dist/storage/task-store.d.ts +1 -1
  85. package/dist/storage/todos-checkpoint.d.ts +1 -1
  86. package/dist/storage/tool-audit-log.d.ts +1 -1
  87. package/dist/tasking/index.js +100 -22
  88. package/dist/tasking/task-tracker.d.ts +24 -1
  89. package/dist/types/compactor.d.ts +2 -2
  90. package/dist/types/context.d.ts +212 -0
  91. package/dist/types/conversation-state.d.ts +109 -0
  92. package/dist/types/error-handler.d.ts +2 -2
  93. package/dist/types/file-event-record.d.ts +6 -0
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/index.js +17 -1
  96. package/dist/types/permission.d.ts +3 -3
  97. package/dist/types/plugin.d.ts +3 -3
  98. package/dist/types/provider-runner.d.ts +2 -2
  99. package/dist/types/provider.d.ts +10 -0
  100. package/dist/types/run-env.d.ts +32 -0
  101. package/dist/types/session.d.ts +3 -0
  102. package/dist/types/slash-command.d.ts +2 -2
  103. package/dist/types/token-counter.d.ts +30 -1
  104. package/dist/types/tool-executor.d.ts +2 -2
  105. package/dist/types/tool.d.ts +20 -5
  106. package/dist/utils/context-breakdown.d.ts +2 -2
  107. package/dist/utils/context-evidence.d.ts +12 -12
  108. package/dist/utils/crash-shield.d.ts +9 -0
  109. package/dist/utils/heap-watchdog.js +11 -3
  110. package/dist/utils/index.d.ts +2 -1
  111. package/dist/utils/index.js +126 -160
  112. package/dist/utils/regex-guard.d.ts +7 -30
  113. package/dist/utils/terminal-sanitize.d.ts +41 -0
  114. package/dist/utils/todos-format.d.ts +1 -1
  115. package/dist/utils/tool-subject.d.ts +1 -1
  116. package/dist/utils/tree-kill.d.ts +2 -0
  117. package/dist/utils/tree-kill.js +1 -0
  118. package/instructions/agents/chaos-monkey.md +5 -1
  119. package/instructions/coordination/subagent-baseline.md +10 -0
  120. package/instructions/system-lite.md +2 -0
  121. package/instructions/system-pro.md +40 -0
  122. package/instructions/system.md +15 -0
  123. package/package.json +7 -10
  124. package/dist/defaults/index.d.ts +0 -69
  125. package/dist/defaults/index.js +0 -37755
  126. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  127. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  128. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  129. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -488,7 +488,9 @@ function hasToolUseBlock(message) {
488
488
  return message?.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "tool_use");
489
489
  }
490
490
  var ConversationState = class {
491
+ /** Owning context; public for structural typing (Roadmap 10A). */
491
492
  ctx;
493
+ /** Subscribers; public for structural typing (Roadmap 10A). */
492
494
  listeners = /* @__PURE__ */ new Set();
493
495
  _revision = 0;
494
496
  constructor(ctx) {
@@ -543,9 +545,8 @@ var ConversationState = class {
543
545
  }
544
546
  /**
545
547
  * How many of the oldest messages must be dropped to satisfy both retention
546
- * caps — count ({@link Context.MAX_MESSAGES}) and size
547
- * ({@link Context.MAX_MESSAGE_TOKENS}). Returns 0 when the history already
548
- * fits, which is the overwhelmingly common case.
548
+ * caps — count and size (see {@link AgentContext.messageLimits}). Returns 0
549
+ * when the history already fits, which is the overwhelmingly common case.
549
550
  *
550
551
  * The size pass reads the per-message `_estTokens` cache populated at
551
552
  * mutation time, so it is a sum over numbers rather than a re-walk of
@@ -553,9 +554,7 @@ var ConversationState = class {
553
554
  * cap determines the starting index for the sum but does not gate it.
554
555
  */
555
556
  overflowCount(arr) {
556
- const contextClass = this.ctx.constructor;
557
- const maxMessages = contextClass.MAX_MESSAGES;
558
- const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
557
+ const { maxMessages, maxMessageTokens } = this.ctx.messageLimits;
559
558
  let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
560
559
  if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
561
560
  let total = 0;
@@ -826,6 +825,10 @@ var Context = class _Context {
826
825
  * so storage operations can include it in `storage.*` events.
827
826
  */
828
827
  traceId;
828
+ /** Logical provider request whose response produced the current tool calls. */
829
+ activeLogicalRequestId = void 0;
830
+ /** Content-addressed prompt composition for {@link activeLogicalRequestId}. */
831
+ activePromptManifestId = void 0;
829
832
  /**
830
833
  * Session id pinned to the currently-executing run. Set by `Agent.run()`
831
834
  * at run start and cleared when the run ends. Event-emission sites must
@@ -851,6 +854,7 @@ var Context = class _Context {
851
854
  return resolveEventSessionId(this);
852
855
  }
853
856
  /** Callbacks fired when `setWorkingDir()` changes the working directory. */
857
+ /** WorkingDir-change callbacks; public for structural typing (Roadmap 10A). */
854
858
  _onWorkingDirChanged = [];
855
859
  /**
856
860
  * Serializes externally requested provider/model changes. Request creation
@@ -1106,7 +1110,16 @@ var Context = class _Context {
1106
1110
  * For hooks that must survive across run boundaries (mailbox heartbeat,
1107
1111
  * awareness polling, HQ publisher), prefer `registerAgentHook` instead.
1108
1112
  */
1113
+ /** Run-scoped abort hooks (drained by drainAbortHooks). Public for structural typing (Roadmap 10A). */
1109
1114
  abortHooks = /* @__PURE__ */ new Set();
1115
+ /** Retention limits honoring runtime subclass overrides of the statics. */
1116
+ get messageLimits() {
1117
+ const cls = this.constructor;
1118
+ return Object.freeze({
1119
+ maxMessages: cls.MAX_MESSAGES,
1120
+ maxMessageTokens: cls.MAX_MESSAGE_TOKENS
1121
+ });
1122
+ }
1110
1123
  registerAbortHook(fn) {
1111
1124
  this.abortHooks.add(fn);
1112
1125
  return () => this.abortHooks.delete(fn);
@@ -1131,6 +1144,7 @@ var Context = class _Context {
1131
1144
  * awareness polling interval, HQ publisher connection, and auto-compaction
1132
1145
  * timer — resources that must survive from the first run to the last.
1133
1146
  */
1147
+ /** Session-lifetime teardown hooks (drained by drainAgentHooks). Public for structural typing (Roadmap 10A). */
1134
1148
  agentHooks = /* @__PURE__ */ new Set();
1135
1149
  registerAgentHook(fn) {
1136
1150
  this.agentHooks.add(fn);
@@ -1311,6 +1325,9 @@ var Context = class _Context {
1311
1325
  agentName: this.agentName,
1312
1326
  provider: typeof this.provider === "object" ? this.provider.id : String(this.provider),
1313
1327
  model: this.model,
1328
+ ...this.activeLogicalRequestId ? { logicalRequestId: this.activeLogicalRequestId } : {},
1329
+ ...this.activePromptManifestId ? { promptManifestId: this.activePromptManifestId } : {},
1330
+ provenanceConfidence: this.activeLogicalRequestId && this.activePromptManifestId ? "explicit" : "unknown",
1314
1331
  toolName: input.toolName,
1315
1332
  toolUseId: input.toolUseId,
1316
1333
  scope,
@@ -1338,6 +1355,9 @@ var Context = class _Context {
1338
1355
  agentName: this.agentName,
1339
1356
  provider: record.provider,
1340
1357
  model: record.model,
1358
+ ...record.logicalRequestId ? { logicalRequestId: record.logicalRequestId } : {},
1359
+ ...record.promptManifestId ? { promptManifestId: record.promptManifestId } : {},
1360
+ provenanceConfidence: record.provenanceConfidence,
1341
1361
  toolName: input.toolName,
1342
1362
  toolUseId: input.toolUseId,
1343
1363
  scope,
@@ -3570,127 +3590,11 @@ function assertNever(x, message) {
3570
3590
  }
3571
3591
 
3572
3592
  // src/utils/regex-guard.ts
3573
- var MAX_PATTERN_LEN = 256;
3574
- var DANGEROUS_PATTERNS = [
3575
- // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
3576
- /(\([^)]*[+*][^)]*\))[+*]/,
3577
- /(\(\?:[^)]*[+*][^)]*\))[+*]/,
3578
- // Adjacent quantifiers: a++ a*+
3579
- /[+*]{2,}/,
3580
- // Quantifier on alternation with length 2+
3581
- /\([^|)]+\|[^)]+\)[+*][+*]/,
3582
- // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
3583
- /[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
3584
- ];
3585
- function hasAmbiguousQuantifiedAlternation(pattern) {
3586
- for (let i = 0; i < pattern.length; i++) {
3587
- if (pattern[i] !== "(") continue;
3588
- if (i > 0 && pattern[i - 1] === "\\") continue;
3589
- let depth = 0;
3590
- let inClass = false;
3591
- let j = i;
3592
- for (; j < pattern.length; j++) {
3593
- const ch = pattern[j];
3594
- if (ch === "\\") {
3595
- j++;
3596
- continue;
3597
- }
3598
- if (inClass) {
3599
- if (ch === "]") inClass = false;
3600
- continue;
3601
- }
3602
- if (ch === "[") {
3603
- inClass = true;
3604
- continue;
3605
- }
3606
- if (ch === "(") depth++;
3607
- else if (ch === ")") {
3608
- depth--;
3609
- if (depth === 0) break;
3610
- }
3611
- }
3612
- if (j >= pattern.length) return false;
3613
- const next = pattern[j + 1];
3614
- if (next !== "+" && next !== "*" && next !== "{") continue;
3615
- let inner = pattern.slice(i + 1, j);
3616
- inner = inner.replace(/^\?(?::|<?[=!])/u, "");
3617
- const branches = [];
3618
- let current = "";
3619
- let d = 0;
3620
- let cls = false;
3621
- for (let k = 0; k < inner.length; k++) {
3622
- const ch = inner[k];
3623
- if (ch === "\\") {
3624
- current += ch + (inner[k + 1] ?? "");
3625
- k++;
3626
- continue;
3627
- }
3628
- if (cls) {
3629
- if (ch === "]") cls = false;
3630
- current += ch;
3631
- continue;
3632
- }
3633
- if (ch === "[") {
3634
- cls = true;
3635
- current += ch;
3636
- continue;
3637
- }
3638
- if (ch === "(") d++;
3639
- if (ch === ")") d--;
3640
- if (ch === "|" && d === 0) {
3641
- branches.push(current);
3642
- current = "";
3643
- continue;
3644
- }
3645
- current += ch;
3646
- }
3647
- branches.push(current);
3648
- if (branches.length < 2) continue;
3649
- for (let a = 0; a < branches.length; a++) {
3650
- for (let b = a + 1; b < branches.length; b++) {
3651
- const x = branches[a];
3652
- const y = branches[b];
3653
- if (x === "" || y === "") return true;
3654
- if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
3655
- }
3656
- }
3657
- }
3658
- return false;
3659
- }
3660
- function compileUserRegex(pattern, flags) {
3661
- if (typeof pattern !== "string") {
3662
- return { ok: false, reason: "pattern must be a string" };
3663
- }
3664
- if (pattern.length === 0) {
3665
- return { ok: false, reason: "pattern is empty" };
3666
- }
3667
- if (pattern.length > MAX_PATTERN_LEN) {
3668
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
3669
- }
3670
- for (const rx of DANGEROUS_PATTERNS) {
3671
- if (rx.test(pattern)) {
3672
- return {
3673
- ok: false,
3674
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
3675
- };
3676
- }
3677
- }
3678
- if (hasAmbiguousQuantifiedAlternation(pattern)) {
3679
- return {
3680
- ok: false,
3681
- reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
3682
- };
3683
- }
3684
- try {
3685
- return { ok: true, regex: new RegExp(pattern, flags) };
3686
- } catch (err) {
3687
- return {
3688
- ok: false,
3689
- reason: err instanceof Error ? err.message : "invalid regex"
3690
- };
3691
- }
3692
- }
3693
- var MAX_SUBJECT_LEN = 64 * 1024;
3593
+ import {
3594
+ capSubject,
3595
+ compileUserRegex,
3596
+ MAX_SUBJECT_LEN
3597
+ } from "@wrongstack/primitives";
3694
3598
 
3695
3599
  // src/execution/autonomous-runner.ts
3696
3600
  var DoneConditionChecker = class {
@@ -5088,7 +4992,12 @@ function isContextOverflowShaped(err) {
5088
4992
  const providerErr = err;
5089
4993
  if (providerErr.kind === "context_overflow" || providerErr.status === 413) return true;
5090
4994
  if (providerErr.status < 400) return false;
5091
- const text = [providerErr.message, providerErr.body?.message, providerErr.body?.type, providerErr.body?.raw].filter(Boolean).join("\n");
4995
+ const text = [
4996
+ providerErr.message,
4997
+ providerErr.body?.message,
4998
+ providerErr.body?.type,
4999
+ providerErr.body?.raw
5000
+ ].filter(Boolean).join("\n");
5092
5001
  return CONTEXT_OVERFLOW_RE.test(text);
5093
5002
  }
5094
5003
  function describeStatus(status, type) {
@@ -10393,27 +10302,39 @@ var DefaultErrorHandler = class {
10393
10302
  import { execFile } from "node:child_process";
10394
10303
  import { promisify } from "node:util";
10395
10304
 
10396
- // src/storage/goal-coordination.ts
10397
- import { updateTask } from "@wrongstack/kanban";
10305
+ // src/storage/board-store-port.ts
10306
+ var notWired = () => {
10307
+ throw new Error(
10308
+ "Kanban BoardStorePort is not wired \u2014 register the implementation at the CLI composition root (see setBoardStorePort)."
10309
+ );
10310
+ };
10311
+ var port = void 0;
10312
+ function boardStore() {
10313
+ return port ?? notWired();
10314
+ }
10315
+ function tryBoardStore() {
10316
+ return port ?? null;
10317
+ }
10398
10318
 
10399
10319
  // src/storage/goal-kanban.ts
10400
- import { addTask, createBoard, getBoard, listBoards, removeBoard } from "@wrongstack/kanban";
10401
10320
  var GOAL_BOARD_TAG_PREFIX = "goal:";
10402
10321
  async function findGoalKanbanBoard(projectRoot, boardId) {
10403
10322
  if (!projectRoot || !boardId) return null;
10404
10323
  try {
10405
- return await getBoard(projectRoot, boardId);
10324
+ return await boardStore().getBoard(projectRoot, boardId);
10406
10325
  } catch {
10407
10326
  return null;
10408
10327
  }
10409
10328
  }
10410
10329
  async function findGoalBoardByTag(projectRoot, goalFile) {
10411
10330
  if (!projectRoot) return null;
10331
+ const store = tryBoardStore();
10332
+ if (!store) return null;
10412
10333
  const tag = goalTag(goalFile);
10413
- const boards = await listBoards(projectRoot);
10334
+ const boards = await store.listBoards(projectRoot);
10414
10335
  const matched = boards.find((b) => b.tags?.includes(tag));
10415
10336
  if (!matched) return null;
10416
- return getBoard(projectRoot, matched.id).catch(() => null);
10337
+ return store.getBoard(projectRoot, matched.id).catch(() => null);
10417
10338
  }
10418
10339
  function goalTag(goalFile) {
10419
10340
  const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\s+/g, "-").slice(0, 24).toLowerCase();
@@ -10577,7 +10498,7 @@ async function refreshGoalKanban(projectRoot, goal, completed, resolvedBoard) {
10577
10498
  const originMatch = task.origin?.system === "goal" && task.origin?.taskId ? originKeys.has(task.origin.taskId) : false;
10578
10499
  const titleMatch = !task.origin?.taskId && completedTitles.has(normalizeTitle(task.title));
10579
10500
  if (!originMatch && !titleMatch) continue;
10580
- const result = await updateTask(
10501
+ const result = await boardStore().updateTask(
10581
10502
  projectRoot,
10582
10503
  board.id,
10583
10504
  task.id,
@@ -17098,11 +17019,13 @@ var CHAOS_MONKEY_AGENT = {
17098
17019
  tools: [...TOOLS.build],
17099
17020
  skillNames: ["testing", "typescript-strict"],
17100
17021
  spawnBudgetExempt: true,
17101
- // Follow fleet worktree policy (NOT 'required'): mutation targets are
17102
- // often freshly written and uncommitted — a worktree spawned from HEAD
17103
- // would not contain them and every mutant would drift. Callers pass
17104
- // `worktree: 'off'` in the mutation_test input for uncommitted targets.
17105
- worktree: "auto",
17022
+ // Run in the live checkout: mutation targets are usually freshly
17023
+ // written and uncommitted — a worktree spawned from HEAD would not
17024
+ // contain them and every mutant would drift. The mutation_test tool
17025
+ // honors this value as its default; callers can still override per
17026
+ // call via its `chaosWorktree` input when targets are committed and
17027
+ // isolation is wanted.
17028
+ worktree: "off",
17106
17029
  // Report travels via submit_result + final text, not the leader's stream.
17107
17030
  textStream: "silent",
17108
17031
  toolStream: "silent"
@@ -20441,6 +20364,10 @@ var DefaultSkillLoader = class {
20441
20364
  );
20442
20365
  for (const e of entries) {
20443
20366
  if (!await entryIsDirectory(dir, e)) continue;
20367
+ if (!isValidSkillNameFormat(e.name)) {
20368
+ this.skipped.push({ dir, entry: e.name, reason: "invalid-name-format" });
20369
+ continue;
20370
+ }
20444
20371
  const skillFile = path15.join(dir, e.name, "SKILL.md");
20445
20372
  let raw;
20446
20373
  try {
@@ -21510,9 +21437,21 @@ function renderCommandLine(command, args) {
21510
21437
  });
21511
21438
  return [command, ...rendered].join(" ");
21512
21439
  }
21513
- function subjectForToolInput(toolName, input, subjectKey) {
21440
+ function renderSubjectFields(obj, fields) {
21441
+ const parts = [];
21442
+ for (const field of fields) {
21443
+ const value = obj[field];
21444
+ if (value === void 0 || value === null || value === "" || value === false) continue;
21445
+ const str = String(value);
21446
+ parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
21447
+ }
21448
+ return parts.join(" ");
21449
+ }
21450
+ function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
21514
21451
  if (!input || typeof input !== "object") return void 0;
21515
21452
  const obj = input;
21453
+ const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
21454
+ const withExtra = (base) => extra ? `${base} ${extra}` : base;
21516
21455
  if (subjectKey) {
21517
21456
  const value = obj[subjectKey];
21518
21457
  if (Array.isArray(value)) {
@@ -21528,9 +21467,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
21528
21467
  if (subjectKey === "command") {
21529
21468
  const rendered = renderCommandLine(value, obj["args"]);
21530
21469
  if (value === "commit" && obj["dry_run"] === true) {
21531
- return `${escapeGlobSubject(rendered)}:dry-run`;
21470
+ return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
21532
21471
  }
21533
- return escapeGlobSubject(rendered);
21472
+ return escapeGlobSubject(withExtra(rendered));
21534
21473
  }
21535
21474
  if (subjectKey === "directory" && obj["dry_run"] === true) {
21536
21475
  return `${escapeGlobSubject(value)}:dry-run`;
@@ -22025,6 +21964,8 @@ function logToolSuccess(opts, ctx, use, toolName, durationMs, outputChars) {
22025
21964
  id: use.id,
22026
21965
  sessionId: ctx.session.id,
22027
21966
  ...ctx.traceId ? { traceId: ctx.traceId } : {},
21967
+ ...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
21968
+ ...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
22028
21969
  agentId: ctx.agentId ?? "<unknown>",
22029
21970
  durationMs,
22030
21971
  outputChars
@@ -22049,6 +21990,8 @@ function logToolFailure(opts, ctx, use, toolName, durationMs, err) {
22049
21990
  id: use.id,
22050
21991
  sessionId: ctx.session.id,
22051
21992
  ...ctx.traceId ? { traceId: ctx.traceId } : {},
21993
+ ...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
21994
+ ...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
22052
21995
  agentId: ctx.agentId ?? "<unknown>",
22053
21996
  durationMs,
22054
21997
  category,
@@ -22118,14 +22061,20 @@ import { isDeepStrictEqual } from "node:util";
22118
22061
  // src/security/kanban-boundary.ts
22119
22062
  import { realpath } from "node:fs/promises";
22120
22063
  import * as path17 from "node:path";
22121
- import {
22122
- evaluateContractGraphReadiness,
22123
- evaluateKanbanBoundaryOpaque,
22124
- evaluateKanbanBoundaryPath,
22125
- readBoard,
22126
- resolveKanbanBoundaryLayers
22127
- } from "@wrongstack/kanban";
22128
- var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo"]);
22064
+
22065
+ // src/security/kanban-governance-port.ts
22066
+ var notWired2 = () => {
22067
+ throw new Error(
22068
+ "KanbanGovernancePort is not wired \u2014 register the implementation at the CLI composition root (see setKanbanGovernance)."
22069
+ );
22070
+ };
22071
+ var port2 = void 0;
22072
+ function kanbanGovernance() {
22073
+ return port2 ?? notWired2();
22074
+ }
22075
+
22076
+ // src/security/kanban-boundary.ts
22077
+ var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo", "nextsteps"]);
22129
22078
  var PATH_KEYS = /* @__PURE__ */ new Set([
22130
22079
  "path",
22131
22080
  "paths",
@@ -22160,7 +22109,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
22160
22109
  reason: "Kanban governance is mandatory before product mutation. Create a managed card, add its required details and executable acceptance criteria, then call kanban start_task to bind it to this run."
22161
22110
  } : { decision: "allow" };
22162
22111
  }
22163
- const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
22112
+ const board = await kanbanGovernance().readBoard(
22113
+ identity.policyRoot ?? ctx.projectRoot,
22114
+ identity.boardId
22115
+ );
22164
22116
  if (!board) {
22165
22117
  return governanceRequired ? {
22166
22118
  decision: "block",
@@ -22186,22 +22138,39 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
22186
22138
  taskId: identity.taskId
22187
22139
  };
22188
22140
  }
22189
- const readiness = evaluateContractGraphReadiness(board, task.id);
22141
+ const readiness = kanbanGovernance().evaluateContractGraphReadiness(board, task.id);
22190
22142
  if (!readiness.ready) {
22143
+ const issueLines = readiness.issues.map((issue, index) => ` ${index + 1}. ${issue.message}`).join("\n");
22191
22144
  return {
22192
22145
  decision: "block",
22193
- reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
22146
+ reason: `Active card failed contract-readiness check (${readiness.issues.length} issue(s)):
22147
+ ` + issueLines + `
22148
+ Resolve the readiness issues, then call kanban start_task to retry.`,
22194
22149
  boardId: board.id,
22195
22150
  taskId: task.id,
22196
22151
  readinessIssues: readiness.issues
22197
22152
  };
22198
22153
  }
22199
- if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
22200
- const lifecycleStage = task.lifecycle?.currentStage ?? "missing";
22201
- const assignmentStatus = task.assignment?.status ?? "missing";
22154
+ const lifecycleStage = task.lifecycle?.currentStage;
22155
+ const assignmentStatus = task.assignment?.status;
22156
+ const lifecycleOk = lifecycleStage === "running";
22157
+ const assignmentOk = assignmentStatus === "running";
22158
+ if (!lifecycleOk || !assignmentOk) {
22159
+ const failed = [];
22160
+ let reassessmentNote = "";
22161
+ if (!lifecycleOk) {
22162
+ failed.push(`lifecycle.currentStage (got: ${lifecycleStage ?? "missing"}; want: 'running')`);
22163
+ }
22164
+ if (!assignmentOk) {
22165
+ failed.push(`assignment.status (got: ${assignmentStatus ?? "missing"}; want: 'running')`);
22166
+ if (lifecycleOk && (assignmentStatus === "queued" || assignmentStatus === void 0 || assignmentStatus === null)) {
22167
+ reassessmentNote = '\nNote: lifecycle is still "running" but the assignment was cleared \u2014 the automated kanban reassessment agent likely reset the binding. Re-issue kanban start_task to re-bind the leader to this card.';
22168
+ }
22169
+ }
22202
22170
  return {
22203
22171
  decision: "block",
22204
- reason: `Active card must be in Running with a live assignment before product mutation (lifecycle: ${lifecycleStage}; assignment: ${assignmentStatus}). Call kanban start_task after completing the required card details.`,
22172
+ reason: `Active card failed ${failed.length} governance check(s) before product mutation:
22173
+ ` + failed.map((line, index) => ` ${index + 1}. ${line}`).join("\n") + reassessmentNote,
22205
22174
  boardId: board.id,
22206
22175
  taskId: task.id
22207
22176
  };
@@ -22219,7 +22188,7 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
22219
22188
  };
22220
22189
  }
22221
22190
  }
22222
- const layers = resolveKanbanBoundaryLayers(board, task);
22191
+ const layers = kanbanGovernance().resolveKanbanBoundaryLayers(board, task);
22223
22192
  if (layers.length === 0) return { decision: "allow" };
22224
22193
  const caps = tool.capabilities ?? [];
22225
22194
  const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
@@ -22227,10 +22196,10 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
22227
22196
  const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
22228
22197
  let evaluation;
22229
22198
  if (shellLike || access2 && candidates.length === 0) {
22230
- evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
22199
+ evaluation = kanbanGovernance().evaluateKanbanBoundaryOpaque(layers, tool.name);
22231
22200
  } else if (access2) {
22232
22201
  const results = candidates.map(
22233
- (candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
22202
+ (candidate) => kanbanGovernance().evaluateKanbanBoundaryPath(layers, candidate, access2)
22234
22203
  );
22235
22204
  evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
22236
22205
  } else {
@@ -22757,6 +22726,8 @@ var ToolExecutor = class _ToolExecutor {
22757
22726
  this.opts.events?.emit("permission.evaluated", {
22758
22727
  sessionId: resolveEventSessionId(ctx),
22759
22728
  ...ctx.traceId ? { traceId: ctx.traceId } : {},
22729
+ ...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
22730
+ ...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
22760
22731
  ...ctx.agentId ? { agentId: ctx.agentId } : {},
22761
22732
  name: tool.name,
22762
22733
  id: use.id,
@@ -22781,7 +22752,7 @@ var ToolExecutor = class _ToolExecutor {
22781
22752
  return { result, tool, durationMs: Date.now() - start };
22782
22753
  }
22783
22754
  if (effectivePermission === "confirm") {
22784
- const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
22755
+ const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey, tool.subjectFields) ?? tool.name;
22785
22756
  if (this.opts.confirmAwaiter) {
22786
22757
  const awaiter = this.opts.confirmAwaiter;
22787
22758
  const choice = await new Promise(
@@ -22908,6 +22879,9 @@ ${post.additionalContext}`;
22908
22879
  agentName: ctx.agentName,
22909
22880
  provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
22910
22881
  model: ctx.model,
22882
+ ...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
22883
+ ...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
22884
+ provenanceConfidence: ctx.activeLogicalRequestId && ctx.activePromptManifestId ? "explicit" : "unknown",
22911
22885
  toolName: tool.name,
22912
22886
  toolUseId: use.id,
22913
22887
  scope: ctx.currentKanbanTaskId ? "task" : "session",
@@ -23050,6 +23024,8 @@ ${preToolContext.text}` : preToolContext.text;
23050
23024
  this.opts.events?.emit("tool.started", {
23051
23025
  sessionId: resolveEventSessionId(ctx),
23052
23026
  ...ctx.traceId ? { traceId: ctx.traceId } : {},
23027
+ ...ctx.activeLogicalRequestId ? { logicalRequestId: ctx.activeLogicalRequestId } : {},
23028
+ ...ctx.activePromptManifestId ? { promptManifestId: ctx.activePromptManifestId } : {},
23053
23029
  agentId: ctx.agentId,
23054
23030
  agentName: ctx.agentName,
23055
23031
  name: tool.name,