@wrongstack/core 0.298.3 → 0.300.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 (70) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/director.d.ts +8 -0
  4. package/dist/coordination/fleet-manager.d.ts +48 -3
  5. package/dist/coordination/ifleet-manager.d.ts +2 -0
  6. package/dist/coordination/index.js +127 -24
  7. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  8. package/dist/core/fallback-model.d.ts +48 -0
  9. package/dist/core/index.d.ts +3 -2
  10. package/dist/core/index.js +288 -34
  11. package/dist/core/instruction-template.d.ts +80 -0
  12. package/dist/core/system-prompt-blocks.d.ts +10 -1
  13. package/dist/core/system-prompt-builder.d.ts +35 -1
  14. package/dist/defaults/index.js +358 -117
  15. package/dist/design/index.js +4 -1
  16. package/dist/execution/autonomy-brain.d.ts +7 -0
  17. package/dist/execution/council-brain.d.ts +17 -2
  18. package/dist/execution/council-orchestrator.d.ts +23 -4
  19. package/dist/execution/council-personas.d.ts +10 -0
  20. package/dist/execution/council-prompts.d.ts +12 -1
  21. package/dist/execution/index.d.ts +1 -1
  22. package/dist/execution/index.js +412 -145
  23. package/dist/fleet-notifier.d.ts +9 -2
  24. package/dist/goal/index.js +4 -1
  25. package/dist/hooks/index.js +140 -10
  26. package/dist/hq/exposure.d.ts +0 -11
  27. package/dist/hq/index.js +34 -8
  28. package/dist/hq/protocol/client.d.ts +14 -1
  29. package/dist/hq/protocol/fleet.d.ts +22 -0
  30. package/dist/hq/protocol.js +12 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +1718 -753
  33. package/dist/infrastructure/index.js +50 -2
  34. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  35. package/dist/kernel/events/brain-events.d.ts +9 -0
  36. package/dist/kernel/events/provider-events.d.ts +49 -2
  37. package/dist/kernel/events/sdd-events.d.ts +2 -0
  38. package/dist/models/index.js +1 -1
  39. package/dist/plugin/api.d.ts +6 -0
  40. package/dist/plugin/config.d.ts +55 -0
  41. package/dist/plugin/index.d.ts +1 -1
  42. package/dist/plugin/index.js +138 -22
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +157 -42
  45. package/dist/security/permission-helpers.d.ts +23 -6
  46. package/dist/security/permission-policy.d.ts +16 -0
  47. package/dist/security/totp.d.ts +14 -0
  48. package/dist/storage/director-state.d.ts +7 -0
  49. package/dist/storage/index.js +46 -9
  50. package/dist/tools/council-tool.d.ts +1 -1
  51. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  52. package/dist/tools/index.js +449 -112
  53. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  54. package/dist/types/config/tools.d.ts +99 -0
  55. package/dist/types/council.d.ts +11 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/index.js +3 -3
  58. package/dist/types/multi-agent.d.ts +10 -0
  59. package/dist/types/one-shot-llm.d.ts +31 -3
  60. package/dist/types/plugin.d.ts +28 -0
  61. package/dist/types/session.d.ts +5 -1
  62. package/dist/utils/index.js +4 -1
  63. package/dist/utils/wstack-paths.d.ts +2 -0
  64. package/dist/worktree/index.js +47 -25
  65. package/dist/worktree/worktree-manager.d.ts +16 -10
  66. package/instructions/coordination/subagent-baseline.md +8 -0
  67. package/instructions/system-lite.md +83 -3
  68. package/instructions/system-pro.md +286 -97
  69. package/instructions/system.md +236 -85
  70. package/package.json +3 -3
@@ -170,8 +170,10 @@ export interface BrainModelEntry {
170
170
  export interface BrainCouncilVoterConfig extends BrainModelEntry {
171
171
  /**
172
172
  * Decision lens for this seat. Built-ins: 'executor' (progress-biased),
173
- * 'skeptic' (risk-hunting), 'auditor' (cost/waste-focused). Any other
174
- * string is injected verbatim as the persona description.
173
+ * 'skeptic' (risk-hunting), 'auditor' (cost/waste-focused), 'security'
174
+ * (trust boundaries, abuse cases), 'maintainer' (complexity, compatibility)
175
+ * and 'user-advocate' (usability, recovery). Any other string is registered
176
+ * as an ad-hoc lens whose instruction is the string itself.
175
177
  */
176
178
  persona?: string | undefined;
177
179
  /** Vote weight in the tally. Default 1. */
@@ -50,6 +50,21 @@ export interface ToolsConfig {
50
50
  * it is honored from any source.
51
51
  */
52
52
  exec?: ExecToolConfig | undefined;
53
+ /**
54
+ * Agent-callable `council` tool: which panel profile it runs by default and
55
+ * which extra lenses/profiles are available to it.
56
+ *
57
+ * Distinct from `brain.council`, which is the Brain's own decision tier —
58
+ * that one is convened by the Brain on high-risk questions, this one is
59
+ * invoked by the agent.
60
+ *
61
+ * SECURITY: in the in-project DENY list. A persona's `instruction` is
62
+ * rendered into the voter SYSTEM prompt, and a profile seat may pin a
63
+ * `providerId`/`model` — so a repo-committed config could otherwise inject
64
+ * system-level instructions into every seat and reroute the calls to an
65
+ * attacker-chosen provider. Only honoured from the active-profile config.
66
+ */
67
+ council?: CouncilToolConfig | undefined;
53
68
  /**
54
69
  * Agent-loop repetition detector tuning. The detector watches two signals:
55
70
  * consecutive effectively-identical iterations (same tool-name set + inputs
@@ -86,6 +101,90 @@ export interface LoopDetectionConfig {
86
101
  */
87
102
  callRepeatThreshold?: number | undefined;
88
103
  }
104
+ /**
105
+ * Configuration for the agent-callable `council` tool.
106
+ *
107
+ * The Council orchestrator has always accepted custom persona and profile
108
+ * registries, but no host ever built one — the tool was pinned to the three
109
+ * built-in profiles with no way to add a lens, change the default panel, or
110
+ * tune concurrency. This is that surface.
111
+ */
112
+ export interface CouncilToolConfig {
113
+ /**
114
+ * Profile the tool runs when a call names none. Must be a built-in id
115
+ * (`balanced`, `fast`, `risk-review`) or one defined in `profiles`.
116
+ * Default `balanced`.
117
+ */
118
+ defaultProfile?: string | undefined;
119
+ /** Seats polled concurrently, 1..8. Default 3. */
120
+ maxConcurrency?: number | undefined;
121
+ /**
122
+ * Extra decision lenses, registered alongside the six built-ins. Each needs
123
+ * a kebab-case `id`, a `name`, a `description` and the `instruction` that
124
+ * becomes the seat's system prompt.
125
+ */
126
+ personas?: CouncilPersonaDefinition[] | undefined;
127
+ /**
128
+ * Extra panel profiles, registered alongside the built-ins. A profile whose
129
+ * id matches a built-in replaces it.
130
+ */
131
+ profiles?: CouncilToolProfileDefinition[] | undefined;
132
+ }
133
+ /** A custom Council decision lens declared in configuration. */
134
+ export interface CouncilPersonaDefinition {
135
+ /** Kebab-case identifier, e.g. "latency-hawk". */
136
+ id: string;
137
+ name: string;
138
+ description: string;
139
+ /** Trusted system-level instruction for this lens. */
140
+ instruction: string;
141
+ defaultWeight?: number | undefined;
142
+ defaultVeto?: boolean | undefined;
143
+ tags?: string[] | undefined;
144
+ }
145
+ /**
146
+ * A custom Council panel declared in configuration.
147
+ *
148
+ * Structurally the config-facing half of `CouncilProfileConfig`; kept as its
149
+ * own type so the config surface stays JSON-shaped (no readonly arrays) and
150
+ * documents itself where users read it.
151
+ */
152
+ export interface CouncilToolProfileDefinition {
153
+ id: string;
154
+ name?: string | undefined;
155
+ description?: string | undefined;
156
+ seats: Array<{
157
+ id?: string | undefined;
158
+ label?: string | undefined;
159
+ /** Persona id — a built-in or one declared in `personas`. */
160
+ persona: string;
161
+ target?: {
162
+ providerId?: string | undefined;
163
+ model?: string | undefined;
164
+ /** Model-matrix role, resolved before the explicit target. */
165
+ role?: string | undefined;
166
+ fallbackProfile?: string | undefined;
167
+ fallbackModels?: string[] | undefined;
168
+ } | undefined;
169
+ weight?: number | undefined;
170
+ veto?: boolean | undefined;
171
+ }>;
172
+ /** `false` disables judging; omitted also means no judge. */
173
+ judge?: false | {
174
+ providerId?: string | undefined;
175
+ model?: string | undefined;
176
+ role?: string | undefined;
177
+ fallbackProfile?: string | undefined;
178
+ fallbackModels?: string[] | undefined;
179
+ } | undefined;
180
+ quorumFraction?: number | undefined;
181
+ approvalFraction?: number | undefined;
182
+ distinctness?: 'none' | 'model' | 'provider' | undefined;
183
+ voterMaxTokens?: number | undefined;
184
+ judgeMaxTokens?: number | undefined;
185
+ perCallTimeoutMs?: number | undefined;
186
+ overallTimeoutMs?: number | undefined;
187
+ }
89
188
  /** Allow/deny extension of the `exec` tool's built-in command allowlist. */
90
189
  export interface ExecToolConfig {
91
190
  /**
@@ -28,6 +28,10 @@ export interface CouncilSeatConfig {
28
28
  label?: string | undefined;
29
29
  persona: string;
30
30
  target?: CouncilModelTarget | undefined;
31
+ /**
32
+ * Vote weight in the tally. Ignored for optionless questions — open
33
+ * stances are not tallied, so every valid stance counts once.
34
+ */
31
35
  weight?: number | undefined;
32
36
  veto?: boolean | undefined;
33
37
  }
@@ -37,6 +41,7 @@ export interface ResolvedCouncilSeat {
37
41
  label: string;
38
42
  persona: string;
39
43
  target?: CouncilModelTarget | undefined;
44
+ /** Vote weight in the tally. No-op for optionless questions (see above). */
40
45
  weight: number;
41
46
  veto: boolean;
42
47
  }
@@ -56,6 +61,11 @@ export interface CouncilProfileConfig {
56
61
  /** False disables judging. Omitted profiles also default to no judge. */
57
62
  judge?: CouncilModelTarget | false | undefined;
58
63
  quorumFraction?: number | undefined;
64
+ /**
65
+ * Winning option must exceed this fraction of cast vote weight. No-op for
66
+ * optionless questions — open stances are not tallied; divergence is
67
+ * escalated instead (see `resolveOpenQuestion`).
68
+ */
59
69
  approvalFraction?: number | undefined;
60
70
  distinctness?: CouncilDistinctness | undefined;
61
71
  voterMaxTokens?: number | undefined;
@@ -71,6 +81,7 @@ export interface ResolvedCouncilProfile {
71
81
  seats: readonly ResolvedCouncilSeat[];
72
82
  judge: CouncilModelTarget | false;
73
83
  quorumFraction: number;
84
+ /** No-op for optionless questions (see the config-level doc above). */
74
85
  approvalFraction: number;
75
86
  distinctness: CouncilDistinctness;
76
87
  voterMaxTokens: number;
@@ -14,7 +14,7 @@ export type { CacheTtl, Capabilities, JsonSchemaSpec, Provider, ProviderErrorBod
14
14
  export { StreamHangError } from './provider.js';
15
15
  export type { ProviderRunner, RunProviderOptions } from './provider-runner.js';
16
16
  export { DEFAULT_TUI_THINKING_WORD, FLEET_CHAT_VERBOSITY_VALUES, MAX_TUI_THINKING_WORD_LENGTH, normalizeTokenSavingTier, normalizeTuiThinkingWord, resolveFleetChatVerbosity, resolveTokenSavingTier } from './config.js';
17
- export type { AdaptiveConcurrencyConfig, AutonomyConfig, BrainConfig, BrainCouncilConfig, BrainCouncilVoterConfig, BrainModelEntry, CircuitBreakerRuntimeConfig, ConcreteTokenSavingTier, Config, ConfigLoader, ConfigStore, ContextConfig, CustomModelDefinition, ExecDangerConfig, ExecToolConfig, FeaturesConfig, FleetChatVerbosity, FleetConfig, FleetSupervisorConfig, GitBehaviorConfig, HqClientConfig, IndexingConfig, InputHistoryConfig, LaunchConfig, LaunchMenuChoice, LogConfig, LoopDetectionConfig, MCPHealthConfig, MCPHealthThresholds, MCPServerConfig, ModelMatrixEntry, ModelRuntimeCacheConfig, ModelRuntimeConfig, ModelRuntimeParametersConfig, ModelRuntimeReasoningConfig, PluginConfig, PluginManagerConfig, ProviderApiKey, ProviderConfig, SageConfig, SessionLoggingConfig, SkillsConfig, SyncCategory, SyncConfig, TokenSavingTier, ToolDescriptionMode, ToolDescriptionModeConfig, ToolResultRenderMode, ToolResultRenderModeConfig, ToolsConfig } from './config.js';
17
+ export type { AdaptiveConcurrencyConfig, AutonomyConfig, BrainConfig, BrainCouncilConfig, BrainCouncilVoterConfig, BrainModelEntry, CircuitBreakerRuntimeConfig, ConcreteTokenSavingTier, Config, ConfigLoader, ConfigStore, ContextConfig, CouncilPersonaDefinition, CouncilToolConfig, CouncilToolProfileDefinition, CustomModelDefinition, ExecDangerConfig, ExecToolConfig, FeaturesConfig, FleetChatVerbosity, FleetConfig, FleetSupervisorConfig, GitBehaviorConfig, HqClientConfig, IndexingConfig, InputHistoryConfig, LaunchConfig, LaunchMenuChoice, LogConfig, LoopDetectionConfig, MCPHealthConfig, MCPHealthThresholds, MCPServerConfig, ModelMatrixEntry, ModelRuntimeCacheConfig, ModelRuntimeConfig, ModelRuntimeParametersConfig, ModelRuntimeReasoningConfig, PluginConfig, PluginManagerConfig, ProviderApiKey, ProviderConfig, SageConfig, SessionLoggingConfig, SkillsConfig, SyncCategory, SyncConfig, TokenSavingTier, ToolDescriptionMode, ToolDescriptionModeConfig, ToolResultRenderMode, ToolResultRenderModeConfig, ToolsConfig } from './config.js';
18
18
  export type { AnyHookOutcome, ConfiguredHook, HookEntry, HookEvent, HookFailurePolicy, HookInput, HookInvocationContext, HookMatcher, HookOutcome, HookRegistrationOptions, HttpHook, InProcessHook, PreToolUseOutcome, PreToolUseStage, ShellHook } from './hooks.js';
19
19
  export type { Compactor, CompactReport } from './compactor.js';
20
20
  export type { DirectoryPolicy, DirectoryRule, PermissionDecision, PermissionPolicy, PermissionTrace, PermissionTraceStep, TrustPolicy } from './permission.js';
@@ -40,7 +40,7 @@ export { flattenSystemPromptRegions } from './system-prompt.js';
40
40
  export type { BuildContext, ModelCapabilities, SystemPromptBuilder, SystemPromptRegions } from './system-prompt.js';
41
41
  export type { Renderer } from './renderer.js';
42
42
  export type { InputReader, PromptOption } from './input-reader.js';
43
- export type { MCPRegistryView, MetricsSinkView, Notifier, Plugin, PluginAPI, PluginCapabilities, PluginConfigFieldLifecycle, PluginConfigFieldMetadata, PluginConfigFields, PluginDependency, PluginLLM, PluginLLMOptions, PluginLLMResult, PluginPipelines, PluginRuntime, ProviderFactory, ProviderRegistryView, SessionWriterView, SlashCommandRegistryView, ToolRegistryView } from './plugin.js';
43
+ export type { MCPRegistryView, MetricsSinkView, Notifier, Plugin, PluginAPI, PluginCapabilities, PluginConfigFieldLifecycle, PluginConfigFieldMetadata, PluginConfigFields, PluginCouncilOptions, PluginDependency, PluginLLM, PluginLLMOptions, PluginLLMResult, PluginPipelines, PluginRuntime, ProviderFactory, ProviderRegistryView, SessionWriterView, SlashCommandRegistryView, ToolRegistryView } from './plugin.js';
44
44
  export { AgentError, ConfigError, ERROR_CODES, FetchError, FsError, ParseError, PluginError, SddError, SessionError, ToolError, ToolValidationError, WrongStackError, isAgentError, isConfigError, isFetchError, isFsError, isParseError, isPluginError, isSddError, isSessionError, isToolError, isToolValidationError, isWrongStackError, toWrongStackError } from './errors.js';
45
45
  export type { ErrorCode, ErrorSeverity, ErrorSubsystem } from './errors.js';
46
46
  export type { ModelsDevModel, ModelsDevPayload, ModelsDevProvider, ModelsDevReasoningOption, ModelsRegistry, ResolvedModel, ResolvedProvider, WireFamily } from './models-registry.js';
@@ -50,6 +50,7 @@ export { CONTEXT_WINDOW_MODES, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEX
50
50
  export type { ContextSnapshot, ContextWindowAggressiveOn, ContextWindowConfigLike, ContextWindowMode, ContextWindowModeId, ContextWindowModeSelectionId, ContextWindowPolicy, ContextWindowThresholds, DeprecatedContextWindowModeId } from './context-window.js';
51
51
  export type { CompletedWorkEvidence, CompletedWorkSource, ContextEvidenceState, ContextFileEvidence, ContextIntentEvidence, ContextRepeatedReadEvidence, ToolEvidenceStatus, ToolOutputMetadata } from './context-evidence.js';
52
52
  export type { CouncilDistinctness, CouncilLLMCaller, CouncilModelTarget, CouncilOption, CouncilPersona, CouncilProfileConfig, CouncilQuestion, CouncilResolutionMethod, CouncilResult, CouncilSeatConfig, CouncilUsage, CouncilVoteResult, CouncilVoteStatus, ResolvedCouncilProfile, ResolvedCouncilSeat } from './council.js';
53
+ export type { OneShotLLMInput, OneShotLLMResult, OneShotModelPick, OneShotModelRouter, OneShotOrchestratorOptions } from './one-shot-llm.js';
53
54
  export type { AwaitAnyResult, CoordinatorEvents, CoordinatorStatus, DoneCondition, MultiAgentConfig, MultiAgentCoordinator, SpawnResult, SubagentConfig, SubagentContext, SubagentError, SubagentErrorKind, SubagentPartialResult, SubagentRunContext, SubagentRunOutcome, SubagentRunner, SubagentSpawnLineage, SubagentStructuredReport, TaskDelegation, TaskResult, TaskSpec } from './multi-agent.js';
54
55
  export { DEFAULT_SPEC_TEMPLATE } from './spec.js';
55
56
  export type { SpecAnalysis, SpecApiEndpoint, SpecRequirement, SpecSection, SpecSectionType, SpecStatus, SpecTemplate, SpecValidationResult, Specification } from './spec.js';
@@ -324,7 +324,7 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
324
324
  function effectiveInputTokens(usage) {
325
325
  return usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
326
326
  }
327
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
327
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
328
328
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
329
329
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
330
330
  function classifyProviderError(status, body, message) {
@@ -334,7 +334,7 @@ function classifyProviderError(status, body, message) {
334
334
  if (status === 408) return "timeout";
335
335
  if (status === 599) return "stream_hang";
336
336
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
337
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
337
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
338
338
  return "quota_exhausted";
339
339
  }
340
340
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -411,7 +411,7 @@ var ProviderError = class extends WrongStackError {
411
411
  const e = err;
412
412
  const name = e.name;
413
413
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
414
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
414
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
415
415
  }
416
416
  constructor(message, status, retryable, providerId, opts = {}) {
417
417
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -65,6 +65,16 @@ export interface SubagentConfig {
65
65
  allowedCapabilities?: readonly string[] | undefined;
66
66
  model?: string | undefined;
67
67
  priority?: number | undefined;
68
+ /**
69
+ * Exempt this spawn from the director's lifetime `maxSpawns` budget.
70
+ * Ephemeral infrastructure agents (Chimera reviewers, cascade agents) set
71
+ * this so background review traffic cannot exhaust the leader's
72
+ * deliberate-delegation budget: the `max_spawns` admission check is skipped
73
+ * and the lifetime spawn counter is NOT incremented. All other caps
74
+ * (spawn depth, fleet cost/tokens, leader context load) still apply.
75
+ * Deliberate leader spawns must leave this unset.
76
+ */
77
+ spawnBudgetExempt?: boolean | undefined;
68
78
  /**
69
79
  * Director-authored recursion/budget inheritance. Director.spawn overwrites
70
80
  * caller input so a model cannot forge a shallower depth or larger budget.
@@ -92,6 +92,15 @@ export interface OneShotLLMResult {
92
92
  durationMs: number;
93
93
  /** Whether the response came from a fallback model. */
94
94
  fromFallback: boolean;
95
+ /**
96
+ * Number of provider invocations behind this result (primary attempt +
97
+ * every fallback entry actually tried). Absent for callers without
98
+ * fallback machinery (Brain seats, test doubles) — consumers treat
99
+ * absence as one call. Producers report explicit 0 only for pre-call
100
+ * failures where no provider was invoked (e.g. unresolved target), so
101
+ * consumers should clamp to at least 1 when a seat ran at all.
102
+ */
103
+ attempts?: number | undefined;
95
104
  /** Provider-level stop reason, if available. */
96
105
  stopReason?: string | undefined;
97
106
  /**
@@ -100,6 +109,21 @@ export interface OneShotLLMResult {
100
109
  */
101
110
  error?: string | undefined;
102
111
  }
112
+ /** One role→model resolution. Mirrors `ModelPick` from the ModelRouter. */
113
+ export interface OneShotModelPick {
114
+ provider: string;
115
+ model: string;
116
+ /**
117
+ * True when the pick came from the user's `/setmodel` matrix (explicit
118
+ * intent) rather than from capability heuristics. Only a matrix pick may
119
+ * override a caller's explicit `providerId`/`model`.
120
+ */
121
+ fromMatrix?: boolean | undefined;
122
+ }
123
+ /** Minimal router contract consumed by role-based routing. */
124
+ export interface OneShotModelRouter {
125
+ pickForTask(role: string, description: string): OneShotModelPick | undefined;
126
+ }
103
127
  /**
104
128
  * Dependencies for OneShotOrchestrator.
105
129
  */
@@ -119,10 +143,14 @@ export interface OneShotOrchestratorOptions {
119
143
  */
120
144
  fallbackProfileManager: import('../core/fallback-profile-manager.js').FallbackProfileManager;
121
145
  /**
122
- * Optional ModelRouter for role-based model selection.
123
- * When absent, role-based routing is skipped.
146
+ * Optional role→model router. When absent, role-based routing is skipped
147
+ * and `role` on an input is inert.
148
+ *
149
+ * Structurally typed rather than pinned to the concrete `ModelRouter` class
150
+ * so a host can supply a router rebuilt from the LIVE config on every call
151
+ * (the `/setmodel` matrix changes mid-session). `ModelRouter` satisfies it.
124
152
  */
125
- modelRouter?: import('../models/model-router.js').ModelRouter | undefined;
153
+ modelRouter?: OneShotModelRouter | undefined;
126
154
  /**
127
155
  * Optional logger for fallback warnings.
128
156
  */
@@ -103,6 +103,12 @@ export interface PluginLLMOptions {
103
103
  responseFormat?: 'text' | 'json' | undefined;
104
104
  /** Abort signal — plugins should pass one for cancellable work. */
105
105
  signal?: AbortSignal | undefined;
106
+ /** Model-matrix role hint used by the host One Shot router. */
107
+ role?: string | undefined;
108
+ /** Explicit fallback model references for this call. */
109
+ fallbackModels?: string[] | undefined;
110
+ /** Hard timeout in milliseconds. Defaults to the host One Shot timeout. */
111
+ timeoutMs?: number | undefined;
106
112
  }
107
113
  export interface PluginLLMResult {
108
114
  /** Concatenated text blocks of the response. */
@@ -116,6 +122,23 @@ export interface PluginLLMResult {
116
122
  output: number;
117
123
  };
118
124
  stopReason: string;
125
+ /** True when the host served the completion through a fallback target. */
126
+ fromFallback?: boolean | undefined;
127
+ /** Provider invocations made by the One Shot fallback ladder. */
128
+ attempts?: number | undefined;
129
+ /** End-to-end completion duration when reported by the host. */
130
+ durationMs?: number | undefined;
131
+ }
132
+ /** Options for a bounded, read-only plugin Council request. */
133
+ export interface PluginCouncilOptions {
134
+ /** Evidence and constraints supplied separately from the question. */
135
+ context?: string | undefined;
136
+ /** Registered Council profile or an ad-hoc profile. */
137
+ profile?: string | import('./council.js').CouncilProfileConfig | undefined;
138
+ /** Optional closed set of choices. Omit for an open synthesis. */
139
+ options?: readonly import('./council.js').CouncilOption[] | undefined;
140
+ /** Abort signal propagated to every Council seat and judge. */
141
+ signal?: AbortSignal | undefined;
119
142
  }
120
143
  /**
121
144
  * LLM access for plugins, routed through the host's provider layer —
@@ -138,6 +161,11 @@ export interface PluginLLM {
138
161
  };
139
162
  /** One-shot completion. Throws on provider errors. */
140
163
  complete(prompt: string, opts?: PluginLLMOptions): Promise<PluginLLMResult>;
164
+ /**
165
+ * Multi-model Council deliberation. Present only when the host wires the
166
+ * Council runtime; plugins must retain a One Shot or deterministic fallback.
167
+ */
168
+ council?(question: string, opts?: PluginCouncilOptions): Promise<import('./council.js').CouncilResult>;
141
169
  }
142
170
  export interface PluginPipelines {
143
171
  request: ReadonlyPipeline<Request>;
@@ -1,6 +1,6 @@
1
1
  import type { ContentBlock } from './blocks.js';
2
2
  import type { Message } from './messages.js';
3
- import type { Usage } from './provider.js';
3
+ import type { ProviderErrorBody, Usage } from './provider.js';
4
4
  export interface SessionMetadata {
5
5
  id: string;
6
6
  title?: string | undefined;
@@ -295,6 +295,8 @@ export type SessionEvent = {
295
295
  delayMs: number;
296
296
  status?: number | undefined;
297
297
  description: string;
298
+ /** Scrubbed raw provider error envelope/body for post-run diagnosis. */
299
+ errorBody?: ProviderErrorBody | undefined;
298
300
  } | {
299
301
  type: 'provider_error';
300
302
  ts: string;
@@ -302,6 +304,8 @@ export type SessionEvent = {
302
304
  status?: number | undefined;
303
305
  description: string;
304
306
  retryable: boolean;
307
+ /** Scrubbed raw provider error envelope/body for post-run diagnosis. */
308
+ errorBody?: ProviderErrorBody | undefined;
305
309
  } | {
306
310
  type: 'checkpoint';
307
311
  ts: string;
@@ -2478,7 +2478,9 @@ function safeProfileName(name) {
2478
2478
  function activeProfileName(globalRoot) {
2479
2479
  try {
2480
2480
  const parsed = JSON.parse(fs3.readFileSync(path3.join(globalRoot, "config.json"), "utf8"));
2481
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2481
+ return safeProfileName(
2482
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2483
+ );
2482
2484
  } catch {
2483
2485
  return "default";
2484
2486
  }
@@ -2558,6 +2560,7 @@ function resolveWstackPaths(opts) {
2558
2560
  projectPlan: path3.join(projectDir, "plan.json"),
2559
2561
  projectAutophase: path3.join(projectDir, "autophase"),
2560
2562
  projectSddBoards: path3.join(projectDir, "sdd-boards"),
2563
+ projectRequirementIntakes: path3.join(projectDir, "requirement-intakes"),
2561
2564
  syncConfig: path3.join(profileDir, "sync.json"),
2562
2565
  configHistoryDir: path3.join(globalRoot, "config-history"),
2563
2566
  projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
@@ -124,6 +124,8 @@ export interface WstackPaths {
124
124
  projectAutophase: string;
125
125
  /** ~/.wrongstack/projects/<hash>/sdd-boards — live SDD board snapshots + JSONL event logs */
126
126
  projectSddBoards: string;
127
+ /** ~/.wrongstack/projects/<hash>/requirement-intakes — requirement intake records */
128
+ projectRequirementIntakes: string;
127
129
  /** ~/.wrongstack/profiles/<activeProfile>/sync.json — CloudSync configuration */
128
130
  syncConfig: string;
129
131
  /** ~/.wrongstack/config-history — timestamped backups on every config write */
@@ -1,5 +1,5 @@
1
1
  // src/worktree/worktree-manager.ts
2
- import { mkdir } from "node:fs/promises";
2
+ import { mkdir, readFile } from "node:fs/promises";
3
3
  import { join, resolve as resolve3, sep as sep3 } from "node:path";
4
4
 
5
5
  // src/utils/error.ts
@@ -466,11 +466,11 @@ var WorktreeManager = class {
466
466
  }
467
467
  const mergeArgs = squash ? ["merge", "--squash", handle.branch] : ["merge", "--no-ff", handle.branch];
468
468
  const merged = await this.runGit(mergeArgs, this.projectRoot);
469
- if (merged.code !== 0) {
470
- const fromOutput = parseConflictPaths(`${merged.stdout}
469
+ const fromOutput = parseConflictPaths(`${merged.stdout}
471
470
  ${merged.stderr}`);
472
- const fromIndex = await this.unmergedFiles();
473
- const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
471
+ const fromIndex = await this.unmergedFiles();
472
+ const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
473
+ if (merged.code !== 0 || conflictFiles.length > 0) {
474
474
  if (opts.resolve) {
475
475
  const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
476
476
  if (finalized) return finalized;
@@ -523,6 +523,10 @@ ${merged.stderr}`);
523
523
  * Safe because SDD merges are serialized — no other commit lands in between.
524
524
  */
525
525
  async revertBaseTo(handle, sha) {
526
+ const status = await this.runGit(["status", "--porcelain"], this.projectRoot);
527
+ if (status.code !== 0) return false;
528
+ const hasTrackedDirt = status.stdout.split("\n").some((line) => line.trim().length > 0 && !line.startsWith("??"));
529
+ if (hasTrackedDirt) return false;
526
530
  const co = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
527
531
  if (co.code !== 0) return false;
528
532
  const reset = await this.runGit(["reset", "--hard", sha], this.projectRoot);
@@ -737,29 +741,47 @@ ${merged.stderr}`);
737
741
  /**
738
742
  * True when staged content still carries conflict markers.
739
743
  *
740
- * Primary probe: `git grep --cached` scans the STAGED blobs directly for a
741
- * full marker line (`<<<<<<< `, `=======`, `>>>>>>> `, `||||||| `) exit 0
742
- * means found. This is byte-level and independent of git version, locale,
743
- * and human-output phrasing. When the caller knows which files conflicted,
744
- * the scan is restricted to them so an unrelated `=======` underline in
745
- * some document can't false-positive.
744
+ * Reads the working-tree files directly and matches a full marker line
745
+ * (`<<<<<<< `, `=======`, `>>>>>>> `, `||||||| `) with a lenient regex
746
+ * (`{7,}`) that stays platform- and config-independent unlike the old
747
+ * `git diff --check` (only fires when `core.whitespace` opts in) and
748
+ * `git grep` (assumes the default 7-char markers).
749
+ *
750
+ * Scan set: when the caller supplies the conflicted paths (the normal
751
+ * path from {@link tryResolveConflict}), ONLY those files are scanned so
752
+ * an unrelated `=======` setext-heading underline in a staged document
753
+ * can't false-positive and discard valid resolution work. The staged-file
754
+ * listing (`git diff --cached --name-only`) is used only as a fallback
755
+ * when no paths are given.
746
756
  *
747
- * Fallback probe: `git diff --cached --check` prints a "leftover conflict
748
- * marker" line per survivor. Kept as belt-and-braces — it was the original
749
- * sole probe, but CI runners were observed to miss markers through it
750
- * (output parsing), which let a half-resolved merge commit.
757
+ * CRLF caveat: conflicted files checked out with CRLF (`core.autocrlf=true`,
758
+ * `.gitattributes eol=crlf`) carry bare markers as `=======\r\n`. Because
759
+ * `$` in multiline mode matches only before `\n`, carriage returns are
760
+ * stripped before matching so `\r` never defeats the end-of-line anchor.
751
761
  */
752
762
  async hasConflictMarkers(files) {
753
- const pathspec = files && files.length > 0 ? ["--", ...files] : [];
754
- const grep = await this.runGit(
755
- ["grep", "--cached", "-q", "-E", "^(<{7} |={7}$|>{7} |\\|{7} )", ...pathspec],
756
- this.projectRoot
757
- );
758
- if (grep.code === 0) return true;
759
- const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
760
- if (check.code === 0) return false;
761
- return /conflict marker/i.test(`${check.stdout}
762
- ${check.stderr}`);
763
+ const listed = files ?? [];
764
+ const paths = listed.length > 0 ? listed : (await this.runGit(["diff", "--cached", "--name-only", "-z"], this.projectRoot)).stdout.split("\0").filter(Boolean);
765
+ const marker = /^(?:<{7,}(?: |$)|={7,}$|>{7,}(?: |$)|\|{7,}(?: |$))/m;
766
+ const startMarker = /^(?:<{7,}(?: |$)|\|{7,}(?: |$))/m;
767
+ for (const rel of paths) {
768
+ try {
769
+ const content = (await readFile(join(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
770
+ const lines = content.split("\n");
771
+ let seenStart = false;
772
+ for (const line of lines) {
773
+ if (!marker.test(line)) continue;
774
+ if (line.startsWith("=====")) {
775
+ if (!seenStart) continue;
776
+ } else if (startMarker.test(line)) {
777
+ seenStart = true;
778
+ }
779
+ return true;
780
+ }
781
+ } catch {
782
+ }
783
+ }
784
+ return false;
763
785
  }
764
786
  /**
765
787
  * Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
@@ -153,17 +153,23 @@ export declare class WorktreeManager {
153
153
  /**
154
154
  * True when staged content still carries conflict markers.
155
155
  *
156
- * Primary probe: `git grep --cached` scans the STAGED blobs directly for a
157
- * full marker line (`<<<<<<< `, `=======`, `>>>>>>> `, `||||||| `) exit 0
158
- * means found. This is byte-level and independent of git version, locale,
159
- * and human-output phrasing. When the caller knows which files conflicted,
160
- * the scan is restricted to them so an unrelated `=======` underline in
161
- * some document can't false-positive.
156
+ * Reads the working-tree files directly and matches a full marker line
157
+ * (`<<<<<<< `, `=======`, `>>>>>>> `, `||||||| `) with a lenient regex
158
+ * (`{7,}`) that stays platform- and config-independent unlike the old
159
+ * `git diff --check` (only fires when `core.whitespace` opts in) and
160
+ * `git grep` (assumes the default 7-char markers).
162
161
  *
163
- * Fallback probe: `git diff --cached --check` prints a "leftover conflict
164
- * marker" line per survivor. Kept as belt-and-braces it was the original
165
- * sole probe, but CI runners were observed to miss markers through it
166
- * (output parsing), which let a half-resolved merge commit.
162
+ * Scan set: when the caller supplies the conflicted paths (the normal
163
+ * path from {@link tryResolveConflict}), ONLY those files are scanned so
164
+ * an unrelated `=======` setext-heading underline in a staged document
165
+ * can't false-positive and discard valid resolution work. The staged-file
166
+ * listing (`git diff --cached --name-only`) is used only as a fallback
167
+ * when no paths are given.
168
+ *
169
+ * CRLF caveat: conflicted files checked out with CRLF (`core.autocrlf=true`,
170
+ * `.gitattributes eol=crlf`) carry bare markers as `=======\r\n`. Because
171
+ * `$` in multiline mode matches only before `\n`, carriage returns are
172
+ * stripped before matching so `\r` never defeats the end-of-line anchor.
167
173
  */
168
174
  private hasConflictMarkers;
169
175
  /**
@@ -40,6 +40,14 @@ deletion, history rewriting, force-push, or database destruction unless the
40
40
  task explicitly requires the action and identifies the exact target. Recheck
41
41
  the target before any destructive step.
42
42
 
43
+ ## Codebase discovery
44
+
45
+ When the index is available, use it before broad `grep`/`glob` exploration:
46
+ - `codebase-search` to locate symbols, definitions, and candidate modules.
47
+ - `codebase-incoming-calls` to find all callers of a symbol before refactoring.
48
+ - `codebase-outgoing-calls` to understand a symbol's dependencies.
49
+ Read source files returned by search before relying on them.
50
+
43
51
  ## No further delegation
44
52
 
45
53
  You MUST NOT call `delegate`, `spawn_subagent`, `assign_task`, or any equivalent.