@wrongstack/cli 0.303.0 → 0.305.1

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 (32) hide show
  1. package/dist/{acp-AQMGKW6O.js → acp-5ZLGFHWP.js} +4 -2
  2. package/dist/{auth-K7CYVVKH.js → auth-EPERLKYI.js} +3 -3
  3. package/dist/boot/system-prompt-builder.d.ts +24 -1
  4. package/dist/boot/tui-project-switch.d.ts +1 -1
  5. package/dist/boot/tui-runtime-state.d.ts +14 -0
  6. package/dist/{chunk-C5NXM2SI.js → chunk-2H47YMCV.js} +5 -1
  7. package/dist/{chunk-FJVCVZIV.js → chunk-5KFLLTSQ.js} +2 -2
  8. package/dist/{chunk-JW75HY4F.js → chunk-FKWHSFX4.js} +33 -4
  9. package/dist/{chunk-LUAFQIXT.js → chunk-RHLQT3EV.js} +11 -1
  10. package/dist/{cli-main-37XO26GK.js → cli-main-6YE423OH.js} +479 -326
  11. package/dist/diag-doctor-WTBHPOMB.js +329 -0
  12. package/dist/execute-deps.d.ts +16 -0
  13. package/dist/{execution-MVOH6PAQ.js → execution-QYFWDD3Y.js} +70 -35
  14. package/dist/fleet/host-context.d.ts +2 -0
  15. package/dist/fleet/host-session-writer.d.ts +33 -0
  16. package/dist/fleet/host.d.ts +10 -0
  17. package/dist/{hq-CD77GVSS.js → hq-5G6D5YIU.js} +2 -2
  18. package/dist/{hq-server-DME6EWIO.js → hq-server-IVZLVCFV.js} +2 -2
  19. package/dist/index.js +17 -60
  20. package/dist/{mailbox-serve-DBOOOOWZ.js → mailbox-serve-V3QIJBDH.js} +2 -2
  21. package/dist/{mcp-AX6ZAFJR.js → mcp-MSARYOPN.js} +4 -2
  22. package/dist/replay-XU6ZD7GM.js +34 -0
  23. package/dist/slash-commands/memory-triage.d.ts +4 -17
  24. package/dist/subcommands/handlers/daemon-inventory.d.ts +41 -0
  25. package/dist/{webui-server-E3OPEKU5.js → webui-server-FTG7JAZU.js} +5 -5
  26. package/dist/wiring/domain-glossary.d.ts +58 -0
  27. package/dist/wiring/domain-terms-mirror.d.ts +52 -0
  28. package/dist/wiring/pipeline.d.ts +2 -0
  29. package/dist/wiring/replay.d.ts +11 -1
  30. package/dist/wiring/sage.d.ts +4 -16
  31. package/package.json +24 -23
  32. package/dist/diag-doctor-ZF5BF2MK.js +0 -169
@@ -29,7 +29,7 @@ import {
29
29
  } from "./chunk-V3XH6XBJ.js";
30
30
  import {
31
31
  startCliHqConnection
32
- } from "./chunk-JW75HY4F.js";
32
+ } from "./chunk-FKWHSFX4.js";
33
33
  import {
34
34
  advanceToNextTask,
35
35
  findSpec,
@@ -59,7 +59,7 @@ import {
59
59
  runClaudeOAuthLogin,
60
60
  runCopilotOAuthLogin,
61
61
  validateFamily
62
- } from "./chunk-FJVCVZIV.js";
62
+ } from "./chunk-5KFLLTSQ.js";
63
63
  import {
64
64
  runCodexOAuthLogin
65
65
  } from "./chunk-SSSJQVUY.js";
@@ -78,7 +78,7 @@ import {
78
78
  import {
79
79
  LOCAL_LLM_PRESETS,
80
80
  parseSpawnFlags
81
- } from "./chunk-C5NXM2SI.js";
81
+ } from "./chunk-2H47YMCV.js";
82
82
  import {
83
83
  buildPickableProviders,
84
84
  visibleModelIds
@@ -117,7 +117,7 @@ import { gatedEnhancerReasoning } from "@wrongstack/core/execution";
117
117
  import { TOKENS as TOKENS9 } from "@wrongstack/core/kernel";
118
118
  import { ToolRegistry as ToolRegistry2 } from "@wrongstack/core/registry";
119
119
  import { getSessionRegistry } from "@wrongstack/core/storage";
120
- import { startSharedHeapWatchdog, writeErr as writeErr2 } from "@wrongstack/core/utils";
120
+ import { startSharedHeapWatchdog, writeErr as writeErr3 } from "@wrongstack/core/utils";
121
121
 
122
122
  // src/auth-menu/panel-service.ts
123
123
  var ANSI_RE = /\u001b\[[0-9;]*m/g;
@@ -900,6 +900,12 @@ function bindSystemPromptBuilder(deps) {
900
900
  // full-shape interfaces; the helper just needs the
901
901
  // passthrough.
902
902
  memoryStore: deps.memoryStore,
903
+ // Thread the narrow domain-term adapter so the builder emits a
904
+ // compact `[Project Jargon Dictionary]` block. `deps.domainGlossary`
905
+ // is an optional closure over the resolved SAGE `memoryStore`
906
+ // that returns only entries tagged `domain-term`; when omitted
907
+ // (e.g. in tests or subagent prompts) the builder emits no block.
908
+ domainGlossary: deps.domainGlossary,
903
909
  // SAGE's turn middleware is the single memory-injection channel.
904
910
  // Disable the builder's static "# Relevant Memory" section so memories
905
911
  // are injected once, per-turn, relevance-scored — not duplicated here.
@@ -2904,6 +2910,38 @@ import {
2904
2910
  } from "@wrongstack/core/types";
2905
2911
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
2906
2912
 
2913
+ // src/services/dispatch-classifier.ts
2914
+ import { makeLLMClassifier } from "@wrongstack/core/coordination";
2915
+ function makeProviderClassifier(provider, model) {
2916
+ return makeLLMClassifier(async (prompt) => {
2917
+ const ctrl = new AbortController();
2918
+ const timeout = setTimeout(() => ctrl.abort(), 15e3);
2919
+ try {
2920
+ const resp = await provider.complete(
2921
+ {
2922
+ model,
2923
+ system: [
2924
+ {
2925
+ type: "text",
2926
+ text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
2927
+ }
2928
+ ],
2929
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
2930
+ maxTokens: 120,
2931
+ temperature: 0
2932
+ },
2933
+ { signal: ctrl.signal }
2934
+ );
2935
+ const content = resp.content;
2936
+ return Array.isArray(content) ? content[0]?.text ?? "" : "";
2937
+ } catch {
2938
+ return "";
2939
+ } finally {
2940
+ clearTimeout(timeout);
2941
+ }
2942
+ });
2943
+ }
2944
+
2907
2945
  // src/fleet/host-acp.ts
2908
2946
  import {
2909
2947
  ACP_AGENT_COMMANDS,
@@ -3536,6 +3574,7 @@ function createHostStatusBroadcaster(input) {
3536
3574
  // src/fleet/host-learning.ts
3537
3575
  import {
3538
3576
  captureLearnedFromAgentOutputDetailed,
3577
+ recordDirectiveOutcomes,
3539
3578
  recordSkillOutcome
3540
3579
  } from "@wrongstack/core/agent-catalog";
3541
3580
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
@@ -3543,20 +3582,38 @@ function captureCompletedTaskLearningForHost(result, deps, subjects, onCaptured)
3543
3582
  const subject = subjects.get(result.subagentId);
3544
3583
  if (!subject) return;
3545
3584
  const logger = deps.container.safeResolve(TOKENS2.Logger);
3546
- if (subject.skills.length > 0) {
3585
+ const graded = result.status !== "stopped";
3586
+ const succeeded = result.status === "success";
3587
+ if (graded && subject.skills.length > 0) {
3547
3588
  try {
3548
- recordSkillOutcome(
3549
- subject.role,
3550
- subject.skills,
3551
- result.status === "success",
3552
- deps.projectRoot
3553
- );
3589
+ recordSkillOutcome(subject.role, subject.skills, succeeded, deps.projectRoot);
3554
3590
  } catch (error) {
3555
- logger?.debug?.(`skill affinity update failed for role "${subject.role}": ${describe(error)}`);
3591
+ logger?.debug?.(
3592
+ `skill affinity update failed for role "${subject.role}": ${describe(error)}`
3593
+ );
3556
3594
  }
3557
3595
  }
3558
3596
  const finalText = typeof result.result === "string" ? result.result : result.partial?.text;
3559
3597
  if (!finalText) return;
3598
+ if (graded) {
3599
+ try {
3600
+ const outcome = recordDirectiveOutcomes(subject.role, finalText, succeeded, deps.projectRoot);
3601
+ if (outcome.attributed > 0) {
3602
+ logger?.debug?.(
3603
+ `directive outcomes: ${outcome.attributed} applied for role "${subject.role}" (${succeeded ? "success" : "failure"})`
3604
+ );
3605
+ }
3606
+ for (const retired of outcome.quarantined) {
3607
+ logger?.info?.(
3608
+ `retired a directive for role "${subject.role}" after repeated failures: ${retired.slice(0, 120)}`
3609
+ );
3610
+ }
3611
+ } catch (error) {
3612
+ logger?.debug?.(
3613
+ `directive outcome update failed for role "${subject.role}": ${describe(error)}`
3614
+ );
3615
+ }
3616
+ }
3560
3617
  try {
3561
3618
  const capture = captureLearnedFromAgentOutputDetailed(
3562
3619
  finalText,
@@ -3636,6 +3693,84 @@ function applyFleetRootDefaults(opts) {
3636
3693
  }
3637
3694
  }
3638
3695
 
3696
+ // src/fleet/host-provider.ts
3697
+ import { makeProviderFromConfig, withCatalogCapabilities } from "@wrongstack/providers";
3698
+ async function buildHostSubagentProvider(deps, config, overrideId, model) {
3699
+ const requestedProviderId = overrideId ?? config.provider;
3700
+ const providerId = requestedProviderId === config.provider || config.providers?.[requestedProviderId] !== void 0 || deps.providerRegistry.has(requestedProviderId) ? requestedProviderId : config.provider;
3701
+ const newCfg = config.providers?.[providerId] ?? {
3702
+ type: providerId,
3703
+ apiKey: config.apiKey,
3704
+ baseUrl: config.baseUrl
3705
+ };
3706
+ const cfgWithType = {
3707
+ ...newCfg,
3708
+ type: providerId,
3709
+ ...model ? { model } : {}
3710
+ };
3711
+ let provider = deps.providerRegistry.has(providerId) ? deps.providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
3712
+ if (deps.modelsRegistry) {
3713
+ const resolvedModel = model ?? config.model;
3714
+ provider = await withCatalogCapabilities(deps.modelsRegistry, providerId, provider, {
3715
+ ...cfgWithType,
3716
+ model: resolvedModel
3717
+ });
3718
+ await refreshRuntimeModelCatalog({
3719
+ modelsRegistry: deps.modelsRegistry,
3720
+ reason: `${providerId}/${resolvedModel}`
3721
+ });
3722
+ const mc = await resolveRuntimeMaxContext({
3723
+ modelsRegistry: deps.modelsRegistry,
3724
+ config,
3725
+ provider,
3726
+ runtimeProviderConfig: cfgWithType,
3727
+ providerId,
3728
+ modelId: resolvedModel
3729
+ });
3730
+ if (mc && mc > 0) provider.capabilities.maxContext = mc;
3731
+ }
3732
+ return provider;
3733
+ }
3734
+ async function resolveHostSubagentReasoningConfig(deps, providerId, modelId) {
3735
+ if (!deps.modelsRegistry) return void 0;
3736
+ try {
3737
+ return (await deps.modelsRegistry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
3738
+ } catch {
3739
+ return void 0;
3740
+ }
3741
+ }
3742
+ function resolveHostSubagentModelSelection(liveConfig, effectiveCfg, matrixTarget) {
3743
+ let effProvider = effectiveCfg.provider ?? matrixTarget?.provider ?? liveConfig.provider;
3744
+ let effModel = effectiveCfg.model ?? matrixTarget?.model ?? liveConfig.model;
3745
+ const modelPolicy = effectiveCfg.modelPolicy;
3746
+ const closedModelPolicy = modelPolicy?.strict === true;
3747
+ const allowedModels = modelPolicy?.allowed ?? [];
3748
+ if (modelPolicy && !allowedModels.some((target) => target.provider === effProvider && target.model === effModel)) {
3749
+ const firstAllowed = allowedModels[0];
3750
+ if (!firstAllowed) throw new Error(`Agent "${effectiveCfg.role}" has no allowed models.`);
3751
+ effProvider = firstAllowed.provider;
3752
+ effModel = firstAllowed.model;
3753
+ }
3754
+ const fallbackProfile = modelPolicy ? void 0 : effectiveCfg.fallbackProfile ?? matrixTarget?.fallbackProfile;
3755
+ const runtimeOverride = effectiveCfg.modelRuntime ?? matrixTarget?.modelRuntime;
3756
+ const startupTargets = modelPolicy ? [
3757
+ { provider: effProvider, model: effModel },
3758
+ ...modelPolicy.fallbacks ?? [],
3759
+ ...closedModelPolicy || effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3760
+ ] : [
3761
+ { provider: effProvider, model: effModel },
3762
+ ...effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3763
+ ];
3764
+ return {
3765
+ effProvider,
3766
+ effModel,
3767
+ fallbackProfile,
3768
+ runtimeOverride,
3769
+ closedModelPolicy,
3770
+ startupTargets
3771
+ };
3772
+ }
3773
+
3639
3774
  // src/fleet/host-shadow-pass.ts
3640
3775
  async function runHostShadowPass(ctx, reason) {
3641
3776
  try {
@@ -3801,84 +3936,6 @@ function aggregateFleetUsage(completed) {
3801
3936
  return { rows, totals };
3802
3937
  }
3803
3938
 
3804
- // src/fleet/host-provider.ts
3805
- import { makeProviderFromConfig, withCatalogCapabilities } from "@wrongstack/providers";
3806
- async function buildHostSubagentProvider(deps, config, overrideId, model) {
3807
- const requestedProviderId = overrideId ?? config.provider;
3808
- const providerId = requestedProviderId === config.provider || config.providers?.[requestedProviderId] !== void 0 || deps.providerRegistry.has(requestedProviderId) ? requestedProviderId : config.provider;
3809
- const newCfg = config.providers?.[providerId] ?? {
3810
- type: providerId,
3811
- apiKey: config.apiKey,
3812
- baseUrl: config.baseUrl
3813
- };
3814
- const cfgWithType = {
3815
- ...newCfg,
3816
- type: providerId,
3817
- ...model ? { model } : {}
3818
- };
3819
- let provider = deps.providerRegistry.has(providerId) ? deps.providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
3820
- if (deps.modelsRegistry) {
3821
- const resolvedModel = model ?? config.model;
3822
- provider = await withCatalogCapabilities(deps.modelsRegistry, providerId, provider, {
3823
- ...cfgWithType,
3824
- model: resolvedModel
3825
- });
3826
- await refreshRuntimeModelCatalog({
3827
- modelsRegistry: deps.modelsRegistry,
3828
- reason: `${providerId}/${resolvedModel}`
3829
- });
3830
- const mc = await resolveRuntimeMaxContext({
3831
- modelsRegistry: deps.modelsRegistry,
3832
- config,
3833
- provider,
3834
- runtimeProviderConfig: cfgWithType,
3835
- providerId,
3836
- modelId: resolvedModel
3837
- });
3838
- if (mc && mc > 0) provider.capabilities.maxContext = mc;
3839
- }
3840
- return provider;
3841
- }
3842
- async function resolveHostSubagentReasoningConfig(deps, providerId, modelId) {
3843
- if (!deps.modelsRegistry) return void 0;
3844
- try {
3845
- return (await deps.modelsRegistry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
3846
- } catch {
3847
- return void 0;
3848
- }
3849
- }
3850
- function resolveHostSubagentModelSelection(liveConfig, effectiveCfg, matrixTarget) {
3851
- let effProvider = effectiveCfg.provider ?? matrixTarget?.provider ?? liveConfig.provider;
3852
- let effModel = effectiveCfg.model ?? matrixTarget?.model ?? liveConfig.model;
3853
- const modelPolicy = effectiveCfg.modelPolicy;
3854
- const closedModelPolicy = modelPolicy?.strict === true;
3855
- const allowedModels = modelPolicy?.allowed ?? [];
3856
- if (modelPolicy && !allowedModels.some((target) => target.provider === effProvider && target.model === effModel)) {
3857
- const firstAllowed = allowedModels[0];
3858
- if (!firstAllowed) throw new Error(`Agent "${effectiveCfg.role}" has no allowed models.`);
3859
- effProvider = firstAllowed.provider;
3860
- effModel = firstAllowed.model;
3861
- }
3862
- const fallbackProfile = modelPolicy ? void 0 : effectiveCfg.fallbackProfile ?? matrixTarget?.fallbackProfile;
3863
- const runtimeOverride = effectiveCfg.modelRuntime ?? matrixTarget?.modelRuntime;
3864
- const startupTargets = modelPolicy ? [
3865
- { provider: effProvider, model: effModel },
3866
- ...modelPolicy.fallbacks ?? [],
3867
- ...closedModelPolicy || effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3868
- ] : [
3869
- { provider: effProvider, model: effModel },
3870
- ...effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3871
- ];
3872
- return {
3873
- effProvider,
3874
- effModel,
3875
- fallbackProfile,
3876
- runtimeOverride,
3877
- closedModelPolicy,
3878
- startupTargets
3879
- };
3880
- }
3881
-
3882
3939
  // src/fleet/host-subagent-factory.ts
3883
3940
  import { randomUUID as randomUUID2 } from "node:crypto";
3884
3941
  import { existsSync, statSync } from "node:fs";
@@ -3914,6 +3971,7 @@ import { AutoApprovePermissionPolicy } from "@wrongstack/core/security";
3914
3971
 
3915
3972
  // src/fleet/host-context.ts
3916
3973
  import {
3974
+ DEFAULT_EAGER_SKILL_LIMIT,
3917
3975
  loadProjectSkillAugmentation,
3918
3976
  missingRequiredRuntimeTools,
3919
3977
  missingRuntimeCapabilities,
@@ -3924,7 +3982,8 @@ import {
3924
3982
  } from "@wrongstack/core/agent-catalog";
3925
3983
  import { TOKENS as TOKENS3 } from "@wrongstack/core/kernel";
3926
3984
  import { getSageRetrieval } from "@wrongstack/sage";
3927
- var EAGER_SKILL_LIMIT = 3;
3985
+ var EAGER_SKILL_LIMIT = DEFAULT_EAGER_SKILL_LIMIT;
3986
+ var MIN_TRIMMED_BODY_CHARS = 800;
3928
3987
  async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availableToolNames = []) {
3929
3988
  const role = subCfg.role;
3930
3989
  const rosterEntry = role ? roster[role] : void 0;
@@ -3940,10 +3999,11 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3940
3999
  ];
3941
4000
  const skillNames = role ? rankRoleSkills(role, pool, deps.projectRoot, EAGER_SKILL_LIMIT) : pool.slice(0, EAGER_SKILL_LIMIT);
3942
4001
  if (skillNames.length === 0 || !deps.skillLoader) {
3943
- return { content: directContent ?? "", selected: [], dropped };
4002
+ return { content: directContent ?? "", selected: [], dropped, trimmed: [] };
3944
4003
  }
3945
4004
  const resolved = [];
3946
4005
  const selected = [];
4006
+ const trimmed = [];
3947
4007
  let usedChars = 0;
3948
4008
  const maxChars = 16e3;
3949
4009
  const maxCharsPerSkill = 4e3;
@@ -3972,10 +4032,12 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3972
4032
  continue;
3973
4033
  }
3974
4034
  const augmentation = role ? loadProjectSkillAugmentation(role, skillName, deps.projectRoot) : "";
3975
- const entry = [
4035
+ const compose = (bodyChars) => [
3976
4036
  `## Skill: ${skillName}`,
3977
4037
  "",
3978
- body.slice(0, maxCharsPerSkill),
4038
+ body.length > bodyChars ? `${body.slice(0, bodyChars).trimEnd()}
4039
+
4040
+ _(body trimmed)_` : body,
3979
4041
  ...augmentation ? [
3980
4042
  "",
3981
4043
  `### Project practice for \`${skillName}\``,
@@ -3985,9 +4047,16 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3985
4047
  augmentation
3986
4048
  ] : []
3987
4049
  ].join("\n");
4050
+ let entry = compose(maxCharsPerSkill);
3988
4051
  if (usedChars + entry.length > maxChars) {
3989
- dropped[skillName] = "budget";
3990
- continue;
4052
+ const overhead = entry.length - Math.min(body.length, maxCharsPerSkill);
4053
+ const room = maxChars - usedChars - overhead;
4054
+ if (!augmentation || room < MIN_TRIMMED_BODY_CHARS) {
4055
+ dropped[skillName] = "budget";
4056
+ continue;
4057
+ }
4058
+ entry = compose(room);
4059
+ trimmed.push(skillName);
3991
4060
  }
3992
4061
  resolved.push(entry);
3993
4062
  selected.push(skillName);
@@ -4010,7 +4079,7 @@ Apply these skills first for this assignment.
4010
4079
 
4011
4080
  ${resolved.join("\n\n---\n\n")}` : void 0
4012
4081
  ].filter((section) => Boolean(section));
4013
- return { content: sections.join("\n\n"), selected, dropped };
4082
+ return { content: sections.join("\n\n"), selected, dropped, trimmed };
4014
4083
  }
4015
4084
  async function retrieveHostSubagentMemory(deps, getLeaderMode, subCfg, taskContext) {
4016
4085
  const memoryPort = deps.container.safeResolve(TOKENS3.MemoryStore);
@@ -4222,10 +4291,8 @@ function createParentSubagentSessionWriter(parentSession) {
4222
4291
  flush: () => parentSession.flush(),
4223
4292
  close: async () => {
4224
4293
  },
4225
- recordFileChange: () => {
4226
- },
4227
- recordSideEffect: () => {
4228
- },
4294
+ recordFileChange: (input) => parentSession.recordFileChange(input),
4295
+ recordSideEffect: (input) => parentSession.recordSideEffect(input),
4229
4296
  writeCheckpoint: async () => {
4230
4297
  },
4231
4298
  writeFileSnapshot: async () => {
@@ -4239,6 +4306,21 @@ function createParentSubagentSessionWriter(parentSession) {
4239
4306
  }
4240
4307
  };
4241
4308
  }
4309
+ function withParentFileSnapshots(subagentSession, parentSession) {
4310
+ if (subagentSession === parentSession) return subagentSession;
4311
+ return new Proxy(subagentSession, {
4312
+ get(target, property, receiver) {
4313
+ if (property === "recordFileChange") {
4314
+ return (input) => {
4315
+ target.recordFileChange(input);
4316
+ parentSession.recordFileChange(input);
4317
+ };
4318
+ }
4319
+ const value = Reflect.get(target, property, receiver);
4320
+ return typeof value === "function" ? value.bind(target) : value;
4321
+ }
4322
+ });
4323
+ }
4242
4324
 
4243
4325
  // src/fleet/host-subagent-factory.ts
4244
4326
  function createHostSubagentFactory(config, host) {
@@ -4370,12 +4452,13 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4370
4452
  baseSystem.push({ type: "text", text: skillResolution.content });
4371
4453
  }
4372
4454
  const droppedSkills = Object.entries(skillResolution.dropped);
4373
- if (droppedSkills.length > 0) {
4455
+ if (droppedSkills.length > 0 || skillResolution.trimmed.length > 0) {
4374
4456
  host.deps.events.emit("subagent.skills.dropped", {
4375
4457
  sessionId: host.deps.session.id,
4376
4458
  role: effectiveCfg.role,
4377
4459
  selected: skillResolution.selected,
4378
- dropped: Object.fromEntries(droppedSkills)
4460
+ dropped: Object.fromEntries(droppedSkills),
4461
+ ...skillResolution.trimmed.length > 0 ? { trimmed: skillResolution.trimmed } : {}
4379
4462
  });
4380
4463
  }
4381
4464
  const rawRolePrompt = effectiveCfg.systemPromptOverride ?? effectiveCfg.prompt ?? (effectiveCfg.role ? host.roster[effectiveCfg.role]?.prompt : void 0);
@@ -4391,12 +4474,15 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4391
4474
  }
4392
4475
  let subSession;
4393
4476
  if (host.sessionFactory) {
4394
- subSession = await host.sessionFactory.createSubagentSession({
4395
- subagentId: subagentName,
4396
- provider: effProvider,
4397
- model: effModel,
4398
- title: `subagent: ${subagentName}`
4399
- });
4477
+ subSession = withParentFileSnapshots(
4478
+ await host.sessionFactory.createSubagentSession({
4479
+ subagentId: subagentName,
4480
+ provider: effProvider,
4481
+ model: effModel,
4482
+ title: `subagent: ${subagentName}`
4483
+ }),
4484
+ host.deps.session
4485
+ );
4400
4486
  } else {
4401
4487
  subSession = createParentSubagentSessionWriter(host.deps.session);
4402
4488
  }
@@ -4445,7 +4531,11 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4445
4531
  maxToolTimeoutMs: config.tools?.maxToolTimeoutMs ?? 3e5,
4446
4532
  perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
4447
4533
  tracer: void 0,
4448
- requireKanbanGovernance: true
4534
+ // Kanban tracks work; it does not gate it. Off unless the operator opts
4535
+ // in — and then subagents inherit it, because a worker dispatched by
4536
+ // `kanban_queue` already carries board/task/lease identity in ctx.meta
4537
+ // and can satisfy the same gate its leader was held to.
4538
+ requireKanbanGovernance: config.tools?.kanbanGovernance ?? false
4449
4539
  });
4450
4540
  const subagentConfigStore = host.deps.configStore;
4451
4541
  const pipelines = createDefaultPipelines();
@@ -4836,6 +4926,12 @@ var MultiAgentHost = class {
4836
4926
  // pass so director.fleetManager is never undefined
4837
4927
  brain: this.opts.brain,
4838
4928
  roster: this.roster,
4929
+ // The dispatcher is two-stage by design — keyword heuristic, then a model
4930
+ // to break the tie — but nothing ever supplied stage two here, so every
4931
+ // description the keywords could not resolve fell through to the
4932
+ // `executor` generalist. That is one half of why a 77-role roster was
4933
+ // being served by a handful of agents.
4934
+ dispatchClassifier: (task, candidates) => this.classifyDispatch(task, candidates),
4839
4935
  // Fire-and-forget report-back: when an assign_task completes with no
4840
4936
  // pending await, post the result to this session's leader via the
4841
4937
  // project mailbox (injected inline before the leader's next step).
@@ -5324,6 +5420,28 @@ var MultiAgentHost = class {
5324
5420
  }
5325
5421
  return this.learningOptimizer;
5326
5422
  }
5423
+ /**
5424
+ * Break a dispatch tie with a model when the keyword heuristic is ambiguous.
5425
+ *
5426
+ * Routes through the `dispatcher` model-matrix slot when one is configured —
5427
+ * picking a role is a short classification, so it belongs on a cheap fast
5428
+ * model rather than the leader's — and the session default otherwise.
5429
+ * Returning `null` on any failure leaves the dispatcher on its heuristic
5430
+ * result, so routing degrades rather than breaking a spawn.
5431
+ */
5432
+ async classifyDispatch(task, candidates) {
5433
+ try {
5434
+ const config = this.deps.configStore.get();
5435
+ const target = resolveSubagentModelTarget2(config, "dispatcher");
5436
+ const providerId = target?.provider ?? config.provider;
5437
+ const model = target?.model ?? config.model;
5438
+ if (!providerId || !model) return null;
5439
+ const provider = await buildHostSubagentProvider(this.deps, config, providerId, model);
5440
+ return await makeProviderClassifier(provider, model)(task, candidates);
5441
+ } catch {
5442
+ return null;
5443
+ }
5444
+ }
5327
5445
  /**
5328
5446
  * Resolve a model for the distillation pass. Uses the `memory-curator` slot
5329
5447
  * of the model matrix when one is configured — curating learned knowledge is
@@ -6417,6 +6535,58 @@ function setupDirectorAndAutonomy(deps) {
6417
6535
  };
6418
6536
  }
6419
6537
 
6538
+ // src/wiring/domain-glossary.ts
6539
+ import { getSageService } from "@wrongstack/sage";
6540
+ var DOMAIN_GLOSSARY_LIMIT = 16;
6541
+ function createDomainGlossaryAdapter(memoryStore) {
6542
+ return {
6543
+ async list(scope, limit) {
6544
+ try {
6545
+ const service = getSageService(memoryStore);
6546
+ if (!service) return [];
6547
+ if (scope !== "project-memory") return [];
6548
+ const effectiveLimit = Math.min(
6549
+ Math.max(1, limit ?? DOMAIN_GLOSSARY_LIMIT),
6550
+ DOMAIN_GLOSSARY_LIMIT
6551
+ );
6552
+ const hits = await service.searchSage("domain-term", { limit: effectiveLimit });
6553
+ return hits.map((hit) => sageToGlossaryEntry(hit));
6554
+ } catch {
6555
+ return [];
6556
+ }
6557
+ }
6558
+ };
6559
+ }
6560
+ function sageToGlossaryEntry(sage) {
6561
+ const priority = sage.importance >= 0.9 ? "critical" : sage.importance >= 0.75 ? "high" : sage.importance >= 0.4 ? "medium" : "low";
6562
+ return {
6563
+ scope: "project-memory",
6564
+ text: sage.text,
6565
+ ts: sage.updatedAt || sage.createdAt,
6566
+ type: "reference",
6567
+ tags: sage.tags.slice(),
6568
+ priority,
6569
+ confidence: sage.confidence,
6570
+ lastAccessed: sage.lastAccessedAt
6571
+ };
6572
+ }
6573
+
6574
+ // src/wiring/domain-terms-mirror.ts
6575
+ import { writeErr as writeErr2 } from "@wrongstack/core/utils";
6576
+ import { SageDomainTermExtractor } from "@wrongstack/sage";
6577
+ async function refreshDomainTermsMirror(options) {
6578
+ try {
6579
+ const extractor = new SageDomainTermExtractor();
6580
+ return await extractor.writeDomainTermsFile(options.projectRoot, options.memoryStore);
6581
+ } catch (err) {
6582
+ writeErr2(
6583
+ `[wrongstack] domain-terms mirror refresh failed: ${err instanceof Error ? err.message : String(err)}
6584
+ `
6585
+ );
6586
+ return null;
6587
+ }
6588
+ }
6589
+
6420
6590
  // src/wiring/eternal-command-handlers.ts
6421
6591
  import { EternalAutonomyEngine as EternalAutonomyEngine2, ParallelEternalEngine } from "@wrongstack/core/execution";
6422
6592
  function createEternalCommandHandlers(input) {
@@ -7750,7 +7920,20 @@ function createAgent(params) {
7750
7920
  tracer: params.tracer,
7751
7921
  logger,
7752
7922
  hookRunner: params.hookRunner,
7753
- requireKanbanGovernance: true
7923
+ // Kanban tracks work; it does not gate it. When this was hard-wired `true`
7924
+ // a mutating tool was refused until a managed card existed, carried a
7925
+ // description and acceptance criteria, and had been started — so the
7926
+ // session spent its effort on card ceremony instead of the work the card
7927
+ // describes. Cards still advance and boards still mirror; path-scoped
7928
+ // `boundary` policies (the actual access control) are unaffected and still
7929
+ // enforced.
7930
+ //
7931
+ // It stays OFF by default, but is now an operator switch rather than a
7932
+ // literal: `tools.kanbanGovernance: true` opts an installation in. The
7933
+ // four hosts (this pipeline, mcp-serve, acp-server-agent, and the fleet
7934
+ // subagent factory) must resolve it identically, or a subagent would run
7935
+ // under a different contract than the leader that dispatched it.
7936
+ requireKanbanGovernance: params.config.tools.kanbanGovernance ?? false
7754
7937
  };
7755
7938
  const toolExecutor = new ToolExecutor2(params.tools, toolExecutorOptions);
7756
7939
  void bootstrapMailboxBridgeAtStartup({
@@ -7771,6 +7954,7 @@ function createAgent(params) {
7771
7954
  events: params.events,
7772
7955
  pipelines: params.pipelines,
7773
7956
  context: params.context,
7957
+ refreshSystemPrompt: true,
7774
7958
  maxIterations: params.config.tools.maxIterations,
7775
7959
  iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
7776
7960
  executionStrategy: params.config.tools.defaultExecutionStrategy,
@@ -8262,7 +8446,7 @@ import {
8262
8446
  watchProviderConfig
8263
8447
  } from "@wrongstack/core/storage";
8264
8448
  import { withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
8265
- import { getSageService } from "@wrongstack/sage";
8449
+ import { getSageService as getSageService2 } from "@wrongstack/sage";
8266
8450
 
8267
8451
  // src/wiring/fallback-gate.ts
8268
8452
  function createFallbackGate(_events) {
@@ -8401,7 +8585,7 @@ function setupProviderRuntime(deps) {
8401
8585
  })
8402
8586
  );
8403
8587
  if (cfg.features.memory && cfg.features.memoryConsolidation !== false) {
8404
- const consSage = getSageService(memoryStore);
8588
+ const consSage = getSageService2(memoryStore);
8405
8589
  agent.extensions.register(
8406
8590
  new SessionMemoryConsolidator({
8407
8591
  memoryStore,
@@ -8818,88 +9002,11 @@ async function setupCodebaseIndexing(deps) {
8818
9002
 
8819
9003
  // src/wiring/sage.ts
8820
9004
  import {
8821
- createSageContextMonitorMiddleware,
8822
- createSageToolCallMiddleware,
8823
- createSageTurnMiddleware,
8824
- getSageRetrieval as getSageRetrieval2,
8825
- InjectionTracker
9005
+ AUTO_HYGIENE_INTERVAL_MS,
9006
+ _resetAutoHygieneThrottleForTesting,
9007
+ sageHygieneOptionsFromConfig,
9008
+ setupSage
8826
9009
  } from "@wrongstack/sage";
8827
- var lastAutoHygieneAt = 0;
8828
- var AUTO_HYGIENE_INTERVAL_MS = 60 * 6e4;
8829
- function setupSage(deps) {
8830
- const cfg = deps.config.Sage;
8831
- const noop = async () => {
8832
- };
8833
- if (deps.config.features.memory === false) return noop;
8834
- if (cfg?.enabled === false) return noop;
8835
- if (!deps.memoryStore) return noop;
8836
- const memoryStore = deps.memoryStore;
8837
- const retrieval = getSageRetrieval2(memoryStore);
8838
- if (!retrieval) {
8839
- deps.logger.debug("sage middleware skipped: memory store does not support retrieval");
8840
- return noop;
8841
- }
8842
- const injectionTracker = new InjectionTracker();
8843
- if (cfg?.inject?.toolResults !== false) {
8844
- deps.pipelines.toolCall.use(
8845
- createSageToolCallMiddleware({
8846
- memory: retrieval,
8847
- maxHintsPerTool: cfg?.inject?.maxHintsPerTool,
8848
- maxCharsPerTool: cfg?.inject?.maxCharsPerTool,
8849
- taskAware: cfg?.inject?.taskAware,
8850
- minScore: cfg?.inject?.minScore,
8851
- minImportance: cfg?.inject?.minImportance,
8852
- relationFloor: cfg?.inject?.relationFloor,
8853
- repeatCooldownMs: cfg?.inject?.repeatCooldownMs,
8854
- verifyOnMutation: cfg?.hygiene?.autoOnFileChange,
8855
- triggers: cfg?.inject?.triggers,
8856
- tracker: injectionTracker,
8857
- events: deps.events,
8858
- getSessionId: deps.getSessionId
8859
- })
8860
- );
8861
- }
8862
- if (cfg?.inject?.turnContext === true) {
8863
- deps.pipelines.request.use(
8864
- createSageTurnMiddleware({
8865
- memory: retrieval,
8866
- maxMemories: cfg?.inject?.maxTurnMemories,
8867
- maxChars: cfg?.inject?.maxCharsPerTurn,
8868
- minScore: cfg?.inject?.minScore,
8869
- metadataWeight: cfg?.retrieval?.metadataWeight,
8870
- tracker: injectionTracker,
8871
- getSessionId: deps.getSessionId
8872
- })
8873
- );
8874
- }
8875
- deps.pipelines.request.use(
8876
- createSageContextMonitorMiddleware({
8877
- tracker: injectionTracker,
8878
- events: deps.events,
8879
- getSessionId: deps.getSessionId
8880
- })
8881
- );
8882
- return async () => {
8883
- await retrieval.flushPendingCounters?.();
8884
- if (cfg?.hygiene?.autoAfterSession === false) return;
8885
- const now = Date.now();
8886
- if (now - lastAutoHygieneAt < AUTO_HYGIENE_INTERVAL_MS) {
8887
- deps.logger.debug(
8888
- `sage auto-hygiene skipped: last run ${Math.round((now - lastAutoHygieneAt) / 1e3)}s ago (throttle: ${AUTO_HYGIENE_INTERVAL_MS / 1e3}s)`
8889
- );
8890
- return;
8891
- }
8892
- await memoryStore.hygiene?.({
8893
- retentionDays: cfg?.hygiene?.retentionDays,
8894
- sessionRetentionDays: cfg?.hygiene?.sessionRetentionDays,
8895
- archiveLowConfidenceAfterDays: cfg?.hygiene?.archiveLowConfidenceAfterDays,
8896
- archiveUnusedAfterDays: cfg?.hygiene?.archiveUnusedAfterDays,
8897
- unusedMinInjections: cfg?.hygiene?.unusedMinInjections,
8898
- purgeDeletedAfterDays: cfg?.hygiene?.purgeDeletedAfterDays
8899
- });
8900
- lastAutoHygieneAt = now;
8901
- };
8902
- }
8903
9010
 
8904
9011
  // src/wiring/runtime-dispatch-state.ts
8905
9012
  async function prepareRuntimeDispatch(input) {
@@ -8909,7 +9016,8 @@ async function prepareRuntimeDispatch(input) {
8909
9016
  memoryStore: input.memoryStore,
8910
9017
  logger: input.logger,
8911
9018
  events: input.events,
8912
- getSessionId: () => input.agent.ctx.session.id
9019
+ getSessionId: () => input.agent.ctx.session.id,
9020
+ projectRoot: input.projectRoot
8913
9021
  });
8914
9022
  const disposeIndexing = await setupCodebaseIndexing({
8915
9023
  config: input.getConfig(),
@@ -8954,6 +9062,7 @@ async function prepareRuntimeDispatch(input) {
8954
9062
  owner,
8955
9063
  category: tool.category ?? "Other",
8956
9064
  enabled: !input.toolRegistry.isDisabled(tool.name),
9065
+ exposure: input.toolRegistry.isDisabled(tool.name) ? "disabled" : input.toolRegistry.isExposedToProvider(tool.name) ? "direct" : "lazy",
8957
9066
  mutating: tool.mutating,
8958
9067
  permission: tool.permission,
8959
9068
  descMode: getToolDescriptionMode(input.toolRegistry, tool.name),
@@ -9433,7 +9542,11 @@ import {
9433
9542
  sessionScopedPath as sessionScopedPath3,
9434
9543
  toErrorMessage as toErrorMessage5
9435
9544
  } from "@wrongstack/core/utils";
9436
- import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
9545
+ import {
9546
+ attachSessionKanbanMirror,
9547
+ hydrateSessionKanban,
9548
+ sessionKanbanDegradation
9549
+ } from "@wrongstack/tools/session-kanban";
9437
9550
  async function setupSession(params) {
9438
9551
  const {
9439
9552
  config,
@@ -9597,6 +9710,12 @@ async function setupSession(params) {
9597
9710
  const taskPath = sessionScopedPath3(wpaths.projectSessions, sessionId, ".tasks.json");
9598
9711
  context.state.setMeta("task.path", taskPath);
9599
9712
  await hydrateSessionKanban(context);
9713
+ const kanbanDown = sessionKanbanDegradation();
9714
+ if (kanbanDown) {
9715
+ renderer.writeInfo(
9716
+ `Kanban board sync unavailable \u2014 continuing without it (${kanbanDown}). Run \`wstack doctor --daemons\` for detail.`
9717
+ );
9718
+ }
9600
9719
  const detachSessionKanbanMirror = attachSessionKanbanMirror(context);
9601
9720
  const detachTodosCheckpoint = async () => {
9602
9721
  detachSessionKanbanMirror();
@@ -9688,38 +9807,6 @@ ${diff}`;
9688
9807
  return "chore: update";
9689
9808
  }
9690
9809
 
9691
- // src/services/dispatch-classifier.ts
9692
- import { makeLLMClassifier } from "@wrongstack/core/coordination";
9693
- function makeProviderClassifier(provider, model) {
9694
- return makeLLMClassifier(async (prompt) => {
9695
- const ctrl = new AbortController();
9696
- const timeout = setTimeout(() => ctrl.abort(), 15e3);
9697
- try {
9698
- const resp = await provider.complete(
9699
- {
9700
- model,
9701
- system: [
9702
- {
9703
- type: "text",
9704
- text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
9705
- }
9706
- ],
9707
- messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
9708
- maxTokens: 120,
9709
- temperature: 0
9710
- },
9711
- { signal: ctrl.signal }
9712
- );
9713
- const content = resp.content;
9714
- return Array.isArray(content) ? content[0]?.text ?? "" : "";
9715
- } catch {
9716
- return "";
9717
- } finally {
9718
- clearTimeout(timeout);
9719
- }
9720
- });
9721
- }
9722
-
9723
9810
  // src/services/mcp-management.ts
9724
9811
  import {
9725
9812
  color as color8,
@@ -10681,10 +10768,17 @@ ${lines.join("\n")}`;
10681
10768
  const learned = loadProjectAgentLearned(role, projectRoot);
10682
10769
  const kn = loadRoleKnowledgeManifest(role, projectRoot);
10683
10770
  const developedSkills = listProjectSkillAugmentations(role, projectRoot);
10771
+ const stats = getProjectAgentLearnStats(role, projectRoot);
10684
10772
  const lines = [`${color11.bold(role)} project identity:`];
10685
10773
  if (developedSkills.length > 0) {
10686
10774
  lines.push(` skills developed: ${developedSkills.join(", ")}`);
10687
10775
  }
10776
+ if (stats.entryCount > 0) {
10777
+ const hitRate = stats.directiveHitRate === null ? "no directive exercised yet" : `${Math.round(stats.directiveHitRate * 100)}% hit rate over ${stats.appliedEntryCount} exercised`;
10778
+ lines.push(
10779
+ ` directives: ${stats.entryCount} buffered \xB7 ${hitRate} \xB7 ${stats.deadEntryCount} never used`
10780
+ );
10781
+ }
10688
10782
  if (cfg) lines.push(` config: ${JSON.stringify(cfg)}`);
10689
10783
  if (kn)
10690
10784
  lines.push(
@@ -12886,6 +12980,7 @@ var SOURCE_LABELS = {
12886
12980
  "leader-after-task": "leader-after-task (post-tool summary)",
12887
12981
  contributor: "contributor",
12888
12982
  ledger: "completed-work ledger",
12983
+ glossary: "project jargon dictionary",
12889
12984
  nextsteps: "next-steps gate",
12890
12985
  other: "other (untagged)"
12891
12986
  };
@@ -13697,6 +13792,8 @@ var KNOWN_TOP_LEVEL_KEYS = [
13697
13792
  "fallbackBridge",
13698
13793
  "fallbackProfiles",
13699
13794
  "fallbackAuto",
13795
+ "fallbackMaxLastResortCandidates",
13796
+ "fallbackStickiness",
13700
13797
  "hooks",
13701
13798
  "plugins",
13702
13799
  "pluginManager",
@@ -13842,6 +13939,37 @@ function diagnoseConfig(cfg, plugins = []) {
13842
13939
  }
13843
13940
  }
13844
13941
  }
13942
+ if ("fallbackMaxLastResortCandidates" in fixed) {
13943
+ const v = fixed["fallbackMaxLastResortCandidates"];
13944
+ const n = coerceNumber(v);
13945
+ if (n === void 0) {
13946
+ delete fixed["fallbackMaxLastResortCandidates"];
13947
+ findings.push({
13948
+ path: "fallbackMaxLastResortCandidates",
13949
+ problem: `expected a non-negative number, got ${JSON.stringify(v)}`,
13950
+ severity: "error",
13951
+ fix: "removed (built-in default of 12 applies)"
13952
+ });
13953
+ } else {
13954
+ const clamped = Math.max(0, Math.floor(n));
13955
+ if (clamped !== v) {
13956
+ fixed["fallbackMaxLastResortCandidates"] = clamped;
13957
+ findings.push({
13958
+ path: "fallbackMaxLastResortCandidates",
13959
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
13960
+ severity: "error",
13961
+ fix: `set to ${clamped}`
13962
+ });
13963
+ } else if (clamped === 0) {
13964
+ findings.push({
13965
+ path: "fallbackMaxLastResortCandidates",
13966
+ problem: "is 0 \u2014 last-resort auto-discovery append is disabled",
13967
+ severity: "warning",
13968
+ fix: "no change (explicit user choice)"
13969
+ });
13970
+ }
13971
+ }
13972
+ }
13845
13973
  if ("fleet" in fixed) {
13846
13974
  if (!isPlainObject(fixed["fleet"])) {
13847
13975
  findings.push({
@@ -14673,6 +14801,9 @@ function buildFallbackCommand(opts) {
14673
14801
  const favorites = config.favoriteModels ?? [];
14674
14802
  const bridge = config.fallbackBridge?.trim();
14675
14803
  const auto = config.fallbackAuto !== false;
14804
+ const rawCap = config.fallbackMaxLastResortCandidates;
14805
+ const capValue = typeof rawCap === "number" && Number.isFinite(rawCap) && rawCap >= 0 ? Math.floor(rawCap) : 12;
14806
+ const capLabel = capValue === 0 ? color24.dim("disabled") : color24.green(String(capValue));
14676
14807
  const filteredReason = (ref) => refInactiveReason(ref, config);
14677
14808
  const lines = [
14678
14809
  `${color24.bold("WrongStack")} ${color24.dim("\u2014 Fallback chain")}`,
@@ -14710,6 +14841,7 @@ function buildFallbackCommand(opts) {
14710
14841
  "",
14711
14842
  ` ${color24.bold("auto")} ${auto ? color24.green("on") : color24.dim("off")} ${color24.dim("/fallback auto on|off")}`,
14712
14843
  ` ${color24.bold("favorites only")} ${config.favoriteModelsOnly ? color24.green("on") : color24.dim("off")} ${color24.dim("/fallback fav only on|off")}`,
14844
+ ` ${color24.bold("last-resort cap")} ${capLabel} ${color24.dim("(max auto-discovered models appended, 0=disabled)")}`,
14713
14845
  "",
14714
14846
  ` ${color24.bold("profiles")} ${Object.keys(profiles).length ? "" : color24.dim("(none)")}`,
14715
14847
  ...Object.entries(profiles).sort(([a], [b]) => a.localeCompare(b)).flatMap(([name, chain]) => {
@@ -17298,7 +17430,6 @@ function buildIntakeCommand(opts) {
17298
17430
  import { color as color32 } from "@wrongstack/core/utils";
17299
17431
  import {
17300
17432
  addCheckToTask,
17301
- addColumn,
17302
17433
  addDependency,
17303
17434
  addGoalMetricToTask,
17304
17435
  addNoteToTask,
@@ -17324,7 +17455,6 @@ import {
17324
17455
  parseLinesIntoTasks,
17325
17456
  releaseTaskClaim,
17326
17457
  removeBoard,
17327
- removeColumn,
17328
17458
  removeTask,
17329
17459
  setTaskChain,
17330
17460
  splitTask,
@@ -17752,9 +17882,6 @@ var KANBAN_COMMAND_HELP = [
17752
17882
  "",
17753
17883
  " /kanban task check add <boardId> <taskId> <desc> Add success check",
17754
17884
  "",
17755
- " /kanban column add <boardId> <title> Add column",
17756
- " /kanban column rm <boardId> <colId> Remove column",
17757
- "",
17758
17885
  " /kanban generate <description> Auto-generate board from text",
17759
17886
  " /kanban export <boardId> Export board as markdown",
17760
17887
  " /kanban graph export <boardId> [graphId] Save board as SDD TaskGraph",
@@ -17930,9 +18057,6 @@ function buildKanbanCommand(opts) {
17930
18057
  if (cmd === "task" || cmd === "t") {
17931
18058
  return handleTaskSubcommand(opts, projectRoot, rest, showHelp);
17932
18059
  }
17933
- if (cmd === "column" || cmd === "col" || cmd === "c") {
17934
- return handleColumnSubcommand(projectRoot, rest, showHelp);
17935
- }
17936
18060
  return {
17937
18061
  message: unknownSubcommand(
17938
18062
  cmd,
@@ -18612,27 +18736,6 @@ ${summary}` };
18612
18736
  }
18613
18737
  return showHelp();
18614
18738
  }
18615
- async function handleColumnSubcommand(projectRoot, args, showHelp) {
18616
- const [sub, boardId, ...rest] = args;
18617
- if (!sub || !boardId) return showHelp();
18618
- if (sub === "add") {
18619
- const title = rest.join(" ");
18620
- if (!title) return { message: color32.red("Usage: /kanban column add <boardId> <title>") };
18621
- const result = await addColumn(projectRoot, boardId, { title });
18622
- if (!result) return { message: color32.red(`Board not found: ${boardId}`) };
18623
- return { message: color32.green(`\u2705 Column added: ${result.column.title}`) };
18624
- }
18625
- if (sub === "rm" || sub === "remove" || sub === "delete") {
18626
- const colId = rest[0];
18627
- if (!colId) return { message: color32.red("Usage: /kanban column rm <boardId> <columnId>") };
18628
- const updated = await removeColumn(projectRoot, boardId, colId, {
18629
- moveTasksToColumnId: rest[1]
18630
- });
18631
- if (!updated) return { message: color32.red(`Column not found: ${colId}`) };
18632
- return { message: color32.green(`\u2705 Column removed: ${colId}`) };
18633
- }
18634
- return showHelp();
18635
- }
18636
18739
  function extractSpawnedSubagentId(summary) {
18637
18740
  return summary.match(/Spawned subagent\s+([^\s]+)/)?.[1];
18638
18741
  }
@@ -18650,12 +18753,6 @@ function formatLifecycleDiagnosis(err, action) {
18650
18753
  if (field === "assignee") {
18651
18754
  return `\u274C /kanban task ${action} needs an assignee. Run \`/kanban task assign <boardId> <taskId> <agent>\` first.`;
18652
18755
  }
18653
- if (field === "dueDate") {
18654
- return `\u274C /kanban task ${action} needs a valid due date. Add one via the \`kanban.update_task\` tool action (patch.dueDate = "YYYY-MM-DD").`;
18655
- }
18656
- if (field === "labels") {
18657
- return `\u274C /kanban task ${action} needs at least one label. Add one via the \`kanban.update_task\` tool action (patch.labels = ["..."]).`;
18658
- }
18659
18756
  if (field === "childTaskIds") {
18660
18757
  return `\u274C /kanban task ${action} is an atomic parent with no persisted children. Decompose it via the kanban tool (\`split_atomic\`) before moving it forward.`;
18661
18758
  }
@@ -20253,7 +20350,11 @@ function formatSageShow(stats, memories) {
20253
20350
 
20254
20351
  // src/slash-commands/memory-triage.ts
20255
20352
  import { toErrorMessage as toErrorMessage17 } from "@wrongstack/core/utils";
20256
- import { formatTriageReport, runTriage } from "@wrongstack/sage";
20353
+ import {
20354
+ fileTriageProposals,
20355
+ formatTriageReport,
20356
+ runTriage
20357
+ } from "@wrongstack/sage";
20257
20358
  var DEFAULT_MAX_PHASE3 = 1e3;
20258
20359
  var DEFAULT_MAX_PHASE4_PAIRS = 50;
20259
20360
  async function runTriageCommand(opts, args) {
@@ -20450,41 +20551,7 @@ async function applyDispatch(Sage, report) {
20450
20551
  return lines.join("\n");
20451
20552
  }
20452
20553
  async function fileProposals(Sage, proposals) {
20453
- const inputs = [];
20454
- const failures = [];
20455
- let filed = 0;
20456
- for (const proposal of proposals) {
20457
- const input = {
20458
- text: proposal.memoryText,
20459
- targetMemoryId: proposal.memoryId,
20460
- reviewReason: proposal.reason,
20461
- suggestedAction: proposal.suggestedAction,
20462
- kind: "memory_review",
20463
- scope: "project",
20464
- importance: 0.5,
20465
- confidence: 0.9,
20466
- tags: ["triage"],
20467
- anchors: [],
20468
- sources: [{ type: "project_instruction" }]
20469
- };
20470
- inputs.push(input);
20471
- try {
20472
- await Sage.createCandidate(input);
20473
- filed++;
20474
- } catch (err) {
20475
- failures.push({
20476
- memoryId: proposal.memoryId,
20477
- error: toErrorMessage17(err)
20478
- });
20479
- }
20480
- }
20481
- return {
20482
- filed,
20483
- failed: failures.length,
20484
- total: proposals.length,
20485
- failures,
20486
- inputs
20487
- };
20554
+ return fileTriageProposals(Sage, proposals);
20488
20555
  }
20489
20556
 
20490
20557
  // src/slash-commands/memory.ts
@@ -25589,24 +25656,78 @@ function buildWebuiCommand() {
25589
25656
  }
25590
25657
 
25591
25658
  // src/slash-commands/theme.ts
25659
+ import { THEME_PRESET_IDS } from "@wrongstack/core/types";
25592
25660
  import { color as color46, writeOut as writeOut3 } from "@wrongstack/core/utils";
25593
- var THEME_OPTIONS = [
25594
- { id: "catppuccin", name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25595
- { id: "tokyo-night", name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25596
- { id: "nord", name: "Nord", desc: "Cool arctic blue & slate pastels" },
25597
- { id: "cyberpunk", name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25598
- { id: "dracula", name: "Dracula", desc: "Vibrant purple, pink & green" },
25599
- { id: "gruvbox-dark", name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25600
- { id: "solarized-dark", name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25601
- { id: "one-dark", name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25602
- { id: "monokai", name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25603
- { id: "rose-pine", name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25604
- { id: "kanagawa", name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25605
- { id: "ayu-dark", name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25606
- { id: "everforest", name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25607
- { id: "night-owl", name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25608
- { id: "synthwave", name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" }
25609
- ];
25661
+ var THEME_META = {
25662
+ catppuccin: { name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25663
+ "tokyo-night": { name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25664
+ nord: { name: "Nord", desc: "Cool arctic blue & slate pastels" },
25665
+ cyberpunk: { name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25666
+ dracula: { name: "Dracula", desc: "Vibrant purple, pink & green" },
25667
+ "gruvbox-dark": { name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25668
+ "solarized-dark": { name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25669
+ "one-dark": { name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25670
+ monokai: { name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25671
+ "rose-pine": { name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25672
+ kanagawa: { name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25673
+ "ayu-dark": { name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25674
+ everforest: { name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25675
+ "night-owl": { name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25676
+ synthwave: { name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" },
25677
+ "github-dark": { name: "GitHub Dark", desc: "GitHub's default dark \u2014 crisp blue on near-black" },
25678
+ "material-ocean": {
25679
+ name: "Material Ocean",
25680
+ desc: "Deepest Material variant \u2014 ink blue with pastel accents"
25681
+ },
25682
+ nightfox: { name: "Nightfox", desc: "Balanced slate blue with muted sage and rose" },
25683
+ oxocarbon: {
25684
+ name: "Oxocarbon",
25685
+ desc: "IBM Carbon-derived \u2014 neutral greys, electric blue & pink"
25686
+ },
25687
+ "catppuccin-macchiato": {
25688
+ name: "Catppuccin Macchiato",
25689
+ desc: "Warmer, one shade lighter than Mocha"
25690
+ },
25691
+ "catppuccin-frappe": {
25692
+ name: "Catppuccin Frapp\xE9",
25693
+ desc: "The lightest Catppuccin dark \u2014 gentle midday contrast"
25694
+ },
25695
+ "gruvbox-material": {
25696
+ name: "Gruvbox Material",
25697
+ desc: "Softened Gruvbox \u2014 same warmth, lower eye strain"
25698
+ },
25699
+ "tokyo-night-storm": {
25700
+ name: "Tokyo Night Storm",
25701
+ desc: "Tokyo Night on a lifted blue-grey base"
25702
+ },
25703
+ "rose-pine-moon": {
25704
+ name: "Ros\xE9 Pine Moon",
25705
+ desc: "Ros\xE9 Pine at dusk \u2014 deeper base, same soft accents"
25706
+ },
25707
+ zenburn: { name: "Zenburn", desc: "The classic low-contrast grey \u2014 desaturated and calm" },
25708
+ palenight: { name: "Palenight", desc: "Material Palenight \u2014 indigo base, candy accents" },
25709
+ horizon: { name: "Horizon", desc: "Warm coral and mint on charcoal \u2014 sunset gradient" },
25710
+ sonokai: { name: "Sonokai", desc: "Monokai Pro descendant \u2014 punchy on warm graphite" },
25711
+ "edge-dark": { name: "Edge Dark", desc: "Clean, evenly-weighted palette on desaturated navy" },
25712
+ moonfly: { name: "Moonfly", desc: "Near-black base with high-chroma accents \u2014 max contrast" },
25713
+ melange: { name: "Melange", desc: "Warm sepia and clay \u2014 the least blue dark theme here" },
25714
+ poimandres: { name: "Poimandres", desc: "Teal-forward, low-saturation \u2014 mint on deep indigo" },
25715
+ "vitesse-dark": {
25716
+ name: "Vitesse Dark",
25717
+ desc: "Anthony Fu's minimal palette \u2014 muted, print-like"
25718
+ },
25719
+ aura: { name: "Aura Dark", desc: "Vivid purple and spring green on near-black violet" },
25720
+ "dark-plus": { name: "VS Code Dark+", desc: "VS Code's default \u2014 familiar blue/orange/teal" }
25721
+ };
25722
+ var THEME_OPTIONS = THEME_PRESET_IDS.map((id) => ({ id, ...THEME_META[id] }));
25723
+ function presetHelpLines(perLine = 4) {
25724
+ const lines = [];
25725
+ for (let i = 0; i < THEME_PRESET_IDS.length; i += perLine) {
25726
+ const chunk = THEME_PRESET_IDS.slice(i, i + perLine).join(", ");
25727
+ lines.push(` ${chunk}${i + perLine < THEME_PRESET_IDS.length ? "," : ""}`);
25728
+ }
25729
+ return lines;
25730
+ }
25610
25731
  async function runThemePicker(reader, activeId) {
25611
25732
  let cursor = THEME_OPTIONS.findIndex((p) => p.id === activeId);
25612
25733
  if (cursor < 0) cursor = 0;
@@ -25621,7 +25742,7 @@ ${color46.bold(color46.amber("WrongStack") + color46.dim(" \u2014 TUI Theme Sele
25621
25742
  const mark = p.id === activeId ? color46.green(" [active]") : "";
25622
25743
  const prefix = i === currentCursor ? color46.bold("\u276F ") : " ";
25623
25744
  const name = i === currentCursor ? color46.bold(p.name) : p.name;
25624
- lines.push(` ${prefix}${name.padEnd(18)} ${color46.dim(p.desc)}${mark}`);
25745
+ lines.push(` ${prefix}${name.padEnd(21)} ${color46.dim(p.desc)}${mark}`);
25625
25746
  }
25626
25747
  lines.push("");
25627
25748
  return lines.join("\n");
@@ -25651,16 +25772,14 @@ function buildThemeCommand(opts) {
25651
25772
  name: "theme",
25652
25773
  category: "Config",
25653
25774
  description: "Switch or select the TUI color theme preset interactively",
25654
- argsHint: "[catppuccin | tokyo-night | nord | cyberpunk | dracula | gruvbox-dark | solarized-dark | one-dark | monokai | rose-pine | kanagawa | ayu-dark | everforest | night-owl | synthwave]",
25775
+ argsHint: `[<preset>] (${THEME_PRESET_IDS.length} available \u2014 run /theme to pick)`,
25655
25776
  help: [
25656
25777
  "Usage:",
25657
25778
  " /theme Interactive menu selection or view available theme presets",
25658
25779
  " /theme <preset> Switch directly to a theme preset",
25659
25780
  "",
25660
- "Available presets:",
25661
- " catppuccin, tokyo-night, nord, cyberpunk, dracula,",
25662
- " gruvbox-dark, solarized-dark, one-dark, monokai, rose-pine,",
25663
- " kanagawa, ayu-dark, everforest, night-owl, synthwave"
25781
+ `Available presets (${THEME_PRESET_IDS.length}):`,
25782
+ ...presetHelpLines()
25664
25783
  ].join("\n"),
25665
25784
  async run(args) {
25666
25785
  const validPresets = THEME_OPTIONS.map((t) => t.id);
@@ -25690,7 +25809,7 @@ function buildThemeCommand(opts) {
25690
25809
  ];
25691
25810
  for (const t of THEME_OPTIONS) {
25692
25811
  const mark = t.id === activePreset ? color46.green(" [active]") : "";
25693
- lines.push(` \u2022 ${color46.bold(t.id.padEnd(14))} ${t.desc}${mark}`);
25812
+ lines.push(` \u2022 ${color46.bold(t.id.padEnd(21))} ${t.desc}${mark}`);
25694
25813
  }
25695
25814
  lines.push("");
25696
25815
  lines.push(color46.dim("Usage: /theme <preset>"));
@@ -31404,7 +31523,7 @@ async function runInteractive(cliCtx) {
31404
31523
  activeMode
31405
31524
  });
31406
31525
  if (modeResult.kind === "exit") {
31407
- writeErr2(`${modeResult.message}
31526
+ writeErr3(`${modeResult.message}
31408
31527
  `);
31409
31528
  await reader.close();
31410
31529
  return modeResult.code;
@@ -31425,6 +31544,15 @@ async function runInteractive(cliCtx) {
31425
31544
  container,
31426
31545
  modeStore,
31427
31546
  memoryStore,
31547
+ // Provide a narrow domain-term adapter so the prompt builder emits a
31548
+ // compact `[Project Jargon Dictionary]` block. Closes over the resolved
31549
+ // SAGE `memoryStore` (a `MemoryPort`) and uses the typed
31550
+ // `SageServiceLike` capability (NOT `@wrongstack/sage-mcp` — the
31551
+ // in-process consumer rule from `packages/sage/docs/direct-icp-usage.md`).
31552
+ // Returns only memories tagged `domain-term` via the capability's
31553
+ // `searchSage` op, which performs the tag-filter at the SQL layer rather
31554
+ // than scanning the whole corpus on every prompt build.
31555
+ domainGlossary: createDomainGlossaryAdapter(memoryStore),
31428
31556
  skillLoader,
31429
31557
  sessionRef,
31430
31558
  autonomyModeRef,
@@ -31445,6 +31573,7 @@ async function runInteractive(cliCtx) {
31445
31573
  pathJoiner: { join: (a, b) => path31.join(a, b) },
31446
31574
  systemPromptBuilderToken: TOKENS9.SystemPromptBuilder
31447
31575
  });
31576
+ await refreshDomainTermsMirror({ projectRoot, memoryStore });
31448
31577
  const toolRegistry = new ToolRegistry2();
31449
31578
  registerBuiltinTools({
31450
31579
  toolRegistry,
@@ -31540,7 +31669,26 @@ async function runInteractive(cliCtx) {
31540
31669
  }
31541
31670
  });
31542
31671
  const { context, planPath, session } = sessResult;
31672
+ context.meta["promptOnlineAgents"] = onlineAgents;
31543
31673
  sessionRef.current = session;
31674
+ const replayFlag = flags["replay"];
31675
+ const recordFlag = flags["record"];
31676
+ if (typeof replayFlag === "string" || recordFlag === true) {
31677
+ const { bindReplayToContainer } = await import("./replay-XU6ZD7GM.js");
31678
+ const mode = recordFlag === true ? "record" : "replay";
31679
+ bindReplayToContainer({
31680
+ container,
31681
+ wpaths,
31682
+ // Recording follows the live session across `/resume` and new-session
31683
+ // swaps; replaying is pinned to the id the user named.
31684
+ sessionId: typeof replayFlag === "string" ? replayFlag : () => sessionRef.current?.id ?? "",
31685
+ mode,
31686
+ logger
31687
+ });
31688
+ logger.info(
31689
+ `replay: ProviderRunner bound in '${mode}' mode for session ${typeof replayFlag === "string" ? replayFlag : session.id}`
31690
+ );
31691
+ }
31544
31692
  const governanceHandle = await setupCliGovernance({
31545
31693
  projectRoot,
31546
31694
  projectId: wpaths.projectSlug,
@@ -32075,7 +32223,7 @@ async function runInteractive(cliCtx) {
32075
32223
  onEvent: evOn
32076
32224
  });
32077
32225
  const savedProviderCfg = config.providers?.[config.provider];
32078
- const { execute } = await import("./execution-MVOH6PAQ.js");
32226
+ const { execute } = await import("./execution-QYFWDD3Y.js");
32079
32227
  const stopHeapWatchdog = startSharedHeapWatchdog({
32080
32228
  collectStats: () => {
32081
32229
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -32126,6 +32274,11 @@ async function runInteractive(cliCtx) {
32126
32274
  events,
32127
32275
  slashRegistry,
32128
32276
  tokenCounter,
32277
+ // Forward the boot sessionRef so `resumeSession` can repoint
32278
+ // it when an in-process `/resume` swaps the active writer.
32279
+ // Without this, provider-side `getSessionId` callbacks and the
32280
+ // record-mode binding keep referring to the boot session.
32281
+ sessionRef,
32129
32282
  activateSessionIdentity: activateSession,
32130
32283
  config,
32131
32284
  configStore,
@@ -32308,4 +32461,4 @@ export {
32308
32461
  CLI_VERSION,
32309
32462
  runInteractive
32310
32463
  };
32311
- //# sourceMappingURL=cli-main-37XO26GK.js.map
32464
+ //# sourceMappingURL=cli-main-6YE423OH.js.map