@sayknow-cli/coding-agent 0.3.0 → 0.3.2

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 (233) hide show
  1. package/bin/skc.js +4 -0
  2. package/dist/types/cli/mcp-cli.d.ts +25 -0
  3. package/dist/types/cli/notify-cli.d.ts +2 -0
  4. package/dist/types/cli/plugin-cli.d.ts +2 -0
  5. package/dist/types/cli.d.ts +6 -0
  6. package/dist/types/commands/mcp.d.ts +70 -0
  7. package/dist/types/commands/plugin.d.ts +6 -0
  8. package/dist/types/commands/session.d.ts +6 -0
  9. package/dist/types/config/keybindings.d.ts +2 -2
  10. package/dist/types/config/model-profile-activation.d.ts +8 -1
  11. package/dist/types/config/settings-schema.d.ts +39 -2
  12. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  13. package/dist/types/defaults/skc-defaults.d.ts +2 -13
  14. package/dist/types/extensibility/shared-events.d.ts +1 -0
  15. package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
  16. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
  17. package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
  18. package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
  19. package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
  20. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
  21. package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
  22. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
  23. package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
  24. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
  25. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
  26. package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
  27. package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
  28. package/dist/types/lsp/types.d.ts +2 -0
  29. package/dist/types/main.d.ts +2 -0
  30. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  31. package/dist/types/modes/components/model-selector.d.ts +8 -0
  32. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  33. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  34. package/dist/types/notifications/attachment-registry.d.ts +17 -0
  35. package/dist/types/notifications/chat-adapters.d.ts +9 -0
  36. package/dist/types/notifications/config.d.ts +9 -1
  37. package/dist/types/notifications/engine.d.ts +59 -0
  38. package/dist/types/notifications/html-format.d.ts +11 -0
  39. package/dist/types/notifications/index.d.ts +149 -1
  40. package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
  41. package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
  42. package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
  43. package/dist/types/notifications/managed-daemon.d.ts +48 -0
  44. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  45. package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
  46. package/dist/types/notifications/recent-activity.d.ts +35 -0
  47. package/dist/types/notifications/telegram-daemon.d.ts +133 -16
  48. package/dist/types/notifications/telegram-reference.d.ts +3 -1
  49. package/dist/types/notifications/threaded-inbound.d.ts +19 -0
  50. package/dist/types/notifications/threaded-render.d.ts +6 -1
  51. package/dist/types/notifications/topic-registry.d.ts +12 -9
  52. package/dist/types/runtime-mcp/types.d.ts +7 -0
  53. package/dist/types/sdk.d.ts +2 -0
  54. package/dist/types/session/agent-session.d.ts +16 -4
  55. package/dist/types/session/blob-store.d.ts +25 -0
  56. package/dist/types/session/session-manager.d.ts +57 -0
  57. package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
  58. package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
  59. package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
  60. package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
  61. package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
  62. package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
  63. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
  64. package/dist/types/system-prompt.d.ts +2 -0
  65. package/dist/types/task/executor.d.ts +9 -1
  66. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  67. package/dist/types/tools/fetch.d.ts +23 -0
  68. package/dist/types/tools/index.d.ts +4 -1
  69. package/dist/types/tools/telegram-send.d.ts +32 -0
  70. package/dist/types/utils/changelog.d.ts +1 -0
  71. package/dist/types/web/insane/bridge.d.ts +103 -0
  72. package/dist/types/web/insane/url-guard.d.ts +25 -0
  73. package/dist/types/web/scrapers/types.d.ts +5 -0
  74. package/dist/types/web/scrapers/utils.d.ts +7 -1
  75. package/dist/types/web/search/provider.d.ts +18 -1
  76. package/dist/types/web/search/providers/insane.d.ts +53 -0
  77. package/dist/types/web/search/providers/text-citations.d.ts +23 -0
  78. package/dist/types/web/search/types.d.ts +12 -4
  79. package/package.json +14 -10
  80. package/scripts/g004-tmux-smoke.ts +100 -0
  81. package/scripts/g005-daemon-smoke.ts +180 -0
  82. package/scripts/g011-daemon-path-smoke.ts +153 -0
  83. package/scripts/verify-insane-vendor.ts +132 -0
  84. package/src/cli/args.ts +1 -1
  85. package/src/cli/fast-help.ts +30 -2
  86. package/src/cli/mcp-cli.ts +272 -0
  87. package/src/cli/notify-cli.ts +152 -5
  88. package/src/cli/plugin-cli.ts +66 -3
  89. package/src/cli.ts +30 -12
  90. package/src/commands/mcp.ts +117 -0
  91. package/src/commands/plugin.ts +4 -0
  92. package/src/commands/session.ts +18 -0
  93. package/src/commands/team.ts +1 -1
  94. package/src/config/keybindings.ts +2 -2
  95. package/src/config/model-profile-activation.ts +55 -7
  96. package/src/config/settings-schema.ts +30 -1
  97. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  98. package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
  99. package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
  100. package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
  101. package/src/defaults/skc/skills/team/SKILL.md +5 -3
  102. package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
  103. package/src/defaults/skc-defaults.ts +2 -27
  104. package/src/export/html/index.ts +2 -2
  105. package/src/extensibility/extensions/runner.ts +1 -0
  106. package/src/extensibility/shared-events.ts +1 -0
  107. package/src/extensibility/skc-plugins/compiler.ts +351 -0
  108. package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
  109. package/src/extensibility/skc-plugins/index.ts +9 -0
  110. package/src/extensibility/skc-plugins/injection.ts +109 -0
  111. package/src/extensibility/skc-plugins/installer.ts +434 -0
  112. package/src/extensibility/skc-plugins/loader.ts +3 -1
  113. package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
  114. package/src/extensibility/skc-plugins/observability.ts +84 -0
  115. package/src/extensibility/skc-plugins/paths.ts +1 -1
  116. package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
  117. package/src/extensibility/skc-plugins/registry.ts +180 -0
  118. package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
  119. package/src/extensibility/skc-plugins/schema.ts +250 -20
  120. package/src/extensibility/skc-plugins/session-validation.ts +147 -0
  121. package/src/extensibility/skc-plugins/types.ts +199 -3
  122. package/src/extensibility/skc-plugins/validation.ts +80 -0
  123. package/src/extensibility/skills.ts +15 -0
  124. package/src/hooks/skill-state.ts +57 -0
  125. package/src/internal-urls/docs-index.generated.ts +17 -13
  126. package/src/lsp/config.ts +16 -3
  127. package/src/lsp/defaults.json +7 -0
  128. package/src/lsp/types.ts +2 -0
  129. package/src/main.ts +14 -3
  130. package/src/modes/bridge/bridge-mode.ts +11 -0
  131. package/src/modes/components/assistant-message.ts +49 -1
  132. package/src/modes/components/custom-editor.ts +2 -0
  133. package/src/modes/components/footer.ts +2 -3
  134. package/src/modes/components/hook-editor.ts +1 -1
  135. package/src/modes/components/hook-selector.ts +67 -43
  136. package/src/modes/components/model-selector.ts +56 -11
  137. package/src/modes/components/status-line/git-utils.ts +25 -0
  138. package/src/modes/components/status-line.ts +10 -11
  139. package/src/modes/components/welcome.ts +2 -3
  140. package/src/modes/controllers/event-controller.ts +15 -0
  141. package/src/modes/controllers/extension-ui-controller.ts +0 -27
  142. package/src/modes/controllers/selector-controller.ts +53 -11
  143. package/src/modes/interactive-mode.ts +50 -3
  144. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  145. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  146. package/src/modes/theme/defaults/index.ts +2 -0
  147. package/src/modes/utils/context-usage.ts +2 -2
  148. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  149. package/src/notifications/attachment-registry.ts +23 -0
  150. package/src/notifications/chat-adapters.ts +147 -0
  151. package/src/notifications/config.ts +23 -2
  152. package/src/notifications/engine.ts +100 -0
  153. package/src/notifications/html-format.ts +38 -0
  154. package/src/notifications/index.ts +417 -45
  155. package/src/notifications/lifecycle-commands.ts +238 -0
  156. package/src/notifications/lifecycle-control-runtime.ts +405 -0
  157. package/src/notifications/lifecycle-orchestrator.ts +358 -0
  158. package/src/notifications/managed-daemon.ts +163 -0
  159. package/src/notifications/operator-runtime.ts +171 -0
  160. package/src/notifications/rate-limit-pool.ts +19 -0
  161. package/src/notifications/recent-activity.ts +132 -0
  162. package/src/notifications/telegram-daemon.ts +984 -242
  163. package/src/notifications/telegram-reference.ts +25 -7
  164. package/src/notifications/threaded-inbound.ts +60 -4
  165. package/src/notifications/threaded-render.ts +20 -2
  166. package/src/notifications/topic-registry.ts +23 -9
  167. package/src/prompts/agents/executor.md +2 -2
  168. package/src/runtime-mcp/transports/stdio.ts +38 -4
  169. package/src/runtime-mcp/types.ts +7 -0
  170. package/src/sdk.ts +157 -10
  171. package/src/session/agent-session.ts +248 -125
  172. package/src/session/blob-store.ts +196 -8
  173. package/src/session/session-manager.ts +762 -12
  174. package/src/skc-runtime/launch-tmux.ts +85 -22
  175. package/src/skc-runtime/ledger-event-renderer.ts +1 -0
  176. package/src/skc-runtime/psmux-detect.ts +239 -0
  177. package/src/skc-runtime/ralplan-runtime.ts +2 -2
  178. package/src/skc-runtime/team-runtime.ts +56 -23
  179. package/src/skc-runtime/tmux-common.ts +88 -4
  180. package/src/skc-runtime/tmux-sessions.ts +111 -9
  181. package/src/skc-runtime/ultragoal-guard.ts +25 -8
  182. package/src/skc-runtime/ultragoal-runtime.ts +75 -15
  183. package/src/skc-runtime/workflow-manifest.generated.json +29 -0
  184. package/src/skc-runtime/workflow-manifest.ts +7 -2
  185. package/src/slash-commands/builtin-registry.ts +23 -3
  186. package/src/slash-commands/helpers/fast-status-report.ts +13 -3
  187. package/src/slash-commands/helpers/parse.ts +2 -1
  188. package/src/system-prompt.ts +9 -0
  189. package/src/task/executor.ts +31 -7
  190. package/src/task/index.ts +2 -0
  191. package/src/tools/ask.ts +5 -1
  192. package/src/tools/bash.ts +9 -0
  193. package/src/tools/composer-bash-policy.ts +96 -0
  194. package/src/tools/fetch.ts +94 -1
  195. package/src/tools/index.ts +6 -1
  196. package/src/tools/telegram-send.ts +137 -0
  197. package/src/utils/changelog.ts +8 -0
  198. package/src/web/insane/bridge.ts +350 -0
  199. package/src/web/insane/url-guard.ts +159 -0
  200. package/src/web/scrapers/types.ts +143 -45
  201. package/src/web/scrapers/utils.ts +70 -19
  202. package/src/web/search/provider.ts +77 -18
  203. package/src/web/search/providers/anthropic.ts +70 -3
  204. package/src/web/search/providers/codex.ts +1 -119
  205. package/src/web/search/providers/gemini.ts +99 -0
  206. package/src/web/search/providers/insane.ts +551 -0
  207. package/src/web/search/providers/openai-compatible.ts +66 -32
  208. package/src/web/search/providers/text-citations.ts +111 -0
  209. package/src/web/search/types.ts +13 -2
  210. package/vendor/insane-search/LICENSE +21 -0
  211. package/vendor/insane-search/MANIFEST.json +24 -0
  212. package/vendor/insane-search/engine/__init__.py +23 -0
  213. package/vendor/insane-search/engine/__main__.py +128 -0
  214. package/vendor/insane-search/engine/bias_check.py +183 -0
  215. package/vendor/insane-search/engine/executor.py +254 -0
  216. package/vendor/insane-search/engine/fetch_chain.py +725 -0
  217. package/vendor/insane-search/engine/learning.py +175 -0
  218. package/vendor/insane-search/engine/phase0.py +214 -0
  219. package/vendor/insane-search/engine/safety.py +91 -0
  220. package/vendor/insane-search/engine/templates/package.json +11 -0
  221. package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
  222. package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
  223. package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
  224. package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
  225. package/vendor/insane-search/engine/tests/test_u1.py +200 -0
  226. package/vendor/insane-search/engine/tests/test_u4.py +131 -0
  227. package/vendor/insane-search/engine/tests/test_u5.py +163 -0
  228. package/vendor/insane-search/engine/tests/test_u7.py +124 -0
  229. package/vendor/insane-search/engine/transport.py +211 -0
  230. package/vendor/insane-search/engine/url_transforms.py +98 -0
  231. package/vendor/insane-search/engine/validators.py +331 -0
  232. package/vendor/insane-search/engine/waf_detector.py +214 -0
  233. package/vendor/insane-search/engine/waf_profiles.yaml +162 -0
package/src/cli.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  /**
4
- * CLI entry point — registers all commands explicitly and delegates to the
4
+ * CLI entry point — registers all commands explicitly and delegates to the
5
5
  * lightweight CLI runner from pi-utils.
6
6
  */
7
7
  import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@sayknow-cli/utils/cli";
@@ -22,7 +22,7 @@ process.title = APP_NAME;
22
22
  const rootHelpFlags = ["--help", "-h", "help"];
23
23
  const versionFlags = ["--version", "-v"];
24
24
 
25
- const commands: CommandEntry[] = [
25
+ export const commands: CommandEntry[] = [
26
26
  { name: "codex-native-hook", load: () => import("./commands/codex-native-hook").then(m => m.default) },
27
27
  { name: "state", load: () => import("./commands/state").then(m => m.default) },
28
28
  { name: "setup", load: () => import("./commands/setup").then(m => m.default) },
@@ -39,6 +39,7 @@ const commands: CommandEntry[] = [
39
39
  { name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
40
40
  { name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
41
41
  { name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
42
+ { name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
42
43
  {
43
44
  name: "contribute-pr",
44
45
  aliases: ["contribution-prep"],
@@ -48,6 +49,7 @@ const commands: CommandEntry[] = [
48
49
  { name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
49
50
  { name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
50
51
  { name: "update", load: () => import("./commands/update").then(m => m.default) },
52
+ { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
51
53
  { name: "launch", load: () => import("./commands/launch").then(m => m.default) },
52
54
  ];
53
55
 
@@ -62,10 +64,7 @@ async function showHelp(config: CliConfig): Promise<void> {
62
64
  }
63
65
 
64
66
  async function installRuntimeGlobals(): Promise<void> {
65
- const [{ installH2Fetch }, { procmgr }] = await Promise.all([
66
- import("@sayknow-cli/ai"),
67
- import("@sayknow-cli/utils"),
68
- ]);
67
+ const { installH2Fetch } = await import("@sayknow-cli/ai/utils/h2-fetch");
69
68
  // Activate HTTP/2 for all `fetch()` calls (provider streams, OAuth, model
70
69
  // discovery, web tools). Bun's HTTP/2 client is gated on a startup flag we
71
70
  // can't toggle from JS, so we patch globalThis.fetch to pass
@@ -75,8 +74,25 @@ async function installRuntimeGlobals(): Promise<void> {
75
74
 
76
75
  // Strip macOS malloc-stack-logging env vars before any subprocess is spawned.
77
76
  // Otherwise every child bun process (subagents, plugin installs, ptree spawns,
78
- // etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
79
- procmgr.scrubProcessEnv();
77
+ // etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
78
+ delete process.env.MallocStackLogging;
79
+ delete process.env.MallocStackLoggingNoCompact;
80
+ }
81
+
82
+ function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
83
+ for (const arg of argv) {
84
+ if (isSubcommand(arg)) return false;
85
+ if (flags.includes(arg)) return true;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ function hasRootHelpFlag(argv: string[]): boolean {
91
+ return hasRootFastFlag(argv, rootHelpFlags);
92
+ }
93
+
94
+ function hasRootVersionFlag(argv: string[]): boolean {
95
+ return hasRootFastFlag(argv, versionFlags);
80
96
  }
81
97
 
82
98
  class RootHelpCommand extends Command {
@@ -165,7 +181,7 @@ function isSubcommand(first: string | undefined): boolean {
165
181
  *
166
182
  * Purpose: catch the silent worker-load regressions that hit compiled
167
183
  * binaries (issues #1011 and #1027). Neither `--version` nor
168
- * `stats --summary` actually spawns a Worker on a fresh install — the
184
+ * `stats --summary` actually spawns a Worker on a fresh install — the
169
185
  * sync path early-returns when no session files exist. This probe is the
170
186
  * minimal end-to-end test that proves `new Worker(...)` resolves and the
171
187
  * bundled worker module evaluates successfully. Wired into
@@ -196,7 +212,7 @@ export async function runCli(argv: string[]): Promise<void> {
196
212
  await runSmokeTest();
197
213
  return;
198
214
  }
199
- if (rootHelpFlags.includes(argv[0] ?? "")) {
215
+ if (hasRootHelpFlag(argv)) {
200
216
  const { renderRootHelp } = await import("@sayknow-cli/utils/cli");
201
217
  const { getExtraHelpText } = await import("./cli/fast-help");
202
218
  renderRootHelp({ bin: APP_NAME, version: VERSION, commands: new Map([["launch", RootHelpCommand]]) });
@@ -206,7 +222,7 @@ export async function runCli(argv: string[]): Promise<void> {
206
222
  }
207
223
  return;
208
224
  }
209
- if (versionFlags.includes(argv[0] ?? "")) {
225
+ if (hasRootVersionFlag(argv)) {
210
226
  process.stdout.write(`${APP_NAME}/${VERSION}\n`);
211
227
  return;
212
228
  }
@@ -223,4 +239,6 @@ export async function runCli(argv: string[]): Promise<void> {
223
239
  return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
224
240
  }
225
241
 
226
- await runCli(process.argv.slice(2));
242
+ if (import.meta.main) {
243
+ await runCli(process.argv.slice(2));
244
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Direct MCP server registration for standalone SKC.
3
+ */
4
+ import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
5
+ import { type MCPAction, type MCPCommandArgs, runMCPCommand } from "../cli/mcp-cli";
6
+
7
+ const ACTIONS: MCPAction[] = ["add", "list", "remove"];
8
+
9
+ export default class MCP extends Command {
10
+ static description = "Register standalone MCP servers explicitly in SKC config";
11
+ static delegateHelp = true;
12
+
13
+ static examples = [
14
+ "skc mcp add context7 npx -y @upstash/context7-mcp",
15
+ "skc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN",
16
+ "skc mcp list --json",
17
+ "skc mcp remove context7",
18
+ ];
19
+
20
+ static args = {
21
+ action: Args.string({ description: "MCP action", required: false, options: ACTIONS }),
22
+ name: Args.string({ description: "Server name", required: false }),
23
+ commandArgs: Args.string({
24
+ description: "Command/URL and trailing args for add",
25
+ required: false,
26
+ multiple: true,
27
+ }),
28
+ };
29
+
30
+ static flags = {
31
+ project: Flags.boolean({ description: "Write/read project scope (./.skc/mcp.json) instead of user scope" }),
32
+ force: Flags.boolean({ description: "Overwrite an existing server during add", default: false }),
33
+ json: Flags.boolean({
34
+ char: "j",
35
+ description: "Emit machine-readable JSON with sensitive values redacted",
36
+ default: false,
37
+ }),
38
+ type: Flags.string({ description: "Server transport type", options: ["stdio", "http", "sse"] }),
39
+ command: Flags.string({ description: "Stdio server command for add" }),
40
+ url: Flags.string({ description: "HTTP/SSE server URL for add" }),
41
+ arg: Flags.string({ description: "Argument passed to a stdio server (repeatable)", multiple: true }),
42
+ env: Flags.string({
43
+ description: "Environment variable for stdio server as KEY=VALUE (repeatable)",
44
+ multiple: true,
45
+ }),
46
+ header: Flags.string({
47
+ description: "HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)",
48
+ multiple: true,
49
+ }),
50
+ cwd: Flags.string({ description: "Working directory for stdio server" }),
51
+ timeout: Flags.integer({ description: "Connection timeout in milliseconds" }),
52
+ };
53
+
54
+ async run(): Promise<void> {
55
+ if (this.argv.includes("--help") || this.argv.includes("-h")) {
56
+ this.printHelp();
57
+ return;
58
+ }
59
+
60
+ const { args, flags } = await this.parse(MCP);
61
+ const action = (args.action ?? "list") as MCPAction;
62
+ const cmd: MCPCommandArgs = {
63
+ action,
64
+ name: args.name,
65
+ commandArgs: args.commandArgs,
66
+ flags: {
67
+ project: flags.project,
68
+ force: flags.force,
69
+ json: flags.json,
70
+ type: flags.type as MCPCommandArgs["flags"]["type"],
71
+ command: flags.command,
72
+ url: flags.url,
73
+ arg: flags.arg,
74
+ env: flags.env,
75
+ header: flags.header,
76
+ cwd: flags.cwd,
77
+ timeout: flags.timeout,
78
+ },
79
+ };
80
+ await runMCPCommand(cmd);
81
+ }
82
+
83
+ private printHelp(): void {
84
+ process.stdout.write(`Register standalone MCP servers explicitly in SKC config
85
+
86
+ USAGE
87
+ $ skc mcp [add|list|remove] [NAME] [COMMAND_OR_URL] [ARGS...] [FLAGS]
88
+
89
+ COMMANDS
90
+ add Add an explicit user-provided MCP server definition
91
+ list List registered servers with env/header/auth values redacted
92
+ remove Remove a registered server and print the removed definition redacted
93
+
94
+ FLAGS
95
+ --project Use project scope (./.skc/mcp.json) instead of user scope
96
+ --force Overwrite an existing server during add
97
+ -j, --json Emit machine-readable JSON with sensitive values redacted
98
+ --type=<value> stdio | http | sse (default: stdio, or http when --url is set)
99
+ --command=<value> Stdio server command for add
100
+ --url=<value> HTTP/SSE server URL for add
101
+ --arg=<value> Stdio server argument (repeatable)
102
+ --env=<value> Stdio env var as KEY=VALUE (repeatable; redacted in output)
103
+ --header=<value> HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)
104
+ --cwd=<value> Working directory for stdio server
105
+ --timeout=<int> Connection timeout in milliseconds
106
+
107
+ EXAMPLES
108
+ $ skc mcp add context7 npx -y @upstash/context7-mcp
109
+ $ skc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN
110
+ $ skc mcp list --json
111
+ $ skc mcp remove context7
112
+
113
+ SECURITY
114
+ This command writes only the server definition supplied on this invocation. It does not import or inherit Claude Code, Codex, OpenCode, or other live MCP configs. Public output redacts env, header, auth, and OAuth credential values.
115
+ `);
116
+ }
117
+ }
@@ -49,6 +49,8 @@ export default class Plugin extends Command {
49
49
  description: 'Install scope: "user" (default) or "project"',
50
50
  options: ["user", "project"],
51
51
  }),
52
+ user: Flags.boolean({ description: "Install SKC plugin bundle into the user root" }),
53
+ project: Flags.boolean({ description: "Install SKC plugin bundle into the project root" }),
52
54
  };
53
55
 
54
56
  async run(): Promise<void> {
@@ -69,6 +71,8 @@ export default class Plugin extends Command {
69
71
  disable: flags.disable,
70
72
  set: flags.set,
71
73
  scope: flags.scope as "user" | "project" | undefined,
74
+ user: flags.user,
75
+ project: flags.project,
72
76
  },
73
77
  };
74
78
 
@@ -2,6 +2,7 @@ import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
2
2
  import {
3
3
  attachSkcTmuxSession,
4
4
  createSkcTmuxSession,
5
+ forceCloseSkcTmuxSession,
5
6
  listSkcTmuxSessions,
6
7
  removeSkcTmuxSession,
7
8
  statusSkcTmuxSession,
@@ -60,6 +61,12 @@ export default class Session extends Command {
60
61
 
61
62
  static flags = {
62
63
  json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
64
+ "session-id": Flags.string({
65
+ description: "Expected @skc-session-id tag for force-close (defense-in-depth match)",
66
+ }),
67
+ "state-file": Flags.string({
68
+ description: "Expected @skc-session-state-file tag for force-close (defense-in-depth match)",
69
+ }),
63
70
  };
64
71
 
65
72
  static examples = [
@@ -68,6 +75,7 @@ export default class Session extends Command {
68
75
  "skc session status <session>",
69
76
  "skc session attach <session>",
70
77
  "skc session remove <session>",
78
+ "skc session force-close <session> --session-id <id>",
71
79
  ];
72
80
 
73
81
  async run(): Promise<void> {
@@ -136,6 +144,16 @@ export default class Session extends Command {
136
144
  return;
137
145
  }
138
146
 
147
+ if (action === "force-close" || action === "force-remove") {
148
+ const closed = forceCloseSkcTmuxSession(sessionName, process.env, flags["session-id"], flags["state-file"]);
149
+ if (json) {
150
+ writeJson({ ok: true, session: sessionJson(closed) });
151
+ return;
152
+ }
153
+ writeText([`force-closed: ${closed.name}`]);
154
+ return;
155
+ }
156
+
139
157
  if (action === "attach") {
140
158
  attachSkcTmuxSession(sessionName);
141
159
  return;
@@ -95,7 +95,7 @@ export default class Team extends Command {
95
95
  };
96
96
 
97
97
  static examples = [
98
- "skc --tmux # start/attach the required tmux-backed leader session first",
98
+ "skc --tmux # start the required tmux-backed leader session first",
99
99
  'skc team 3:executor "Implement the approved plan"',
100
100
  "skc team status <team-name> --json",
101
101
  "skc team monitor <team-name> --json",
@@ -117,8 +117,8 @@ export const KEYBINDINGS = {
117
117
  description: "Open external editor",
118
118
  },
119
119
  "app.message.followUp": {
120
- defaultKeys: "ctrl+enter",
121
- description: "Send follow-up message",
120
+ defaultKeys: [],
121
+ description: "Send follow-up message (no default; Ctrl+Enter inserts a newline)",
122
122
  },
123
123
  "app.message.queue": {
124
124
  defaultKeys: "alt+enter",
@@ -7,8 +7,13 @@ import {
7
7
  formatAvailableProfileNames,
8
8
  resolveProfileBindings,
9
9
  } from "./model-profiles";
10
- import { isAuthenticated, type ModelRegistry, type SkcModelAssignmentTargetId } from "./model-registry";
11
- import { resolveModelRoleValue } from "./model-resolver";
10
+ import {
11
+ isAuthenticated,
12
+ type ModelRegistry,
13
+ SKC_MODEL_ASSIGNMENT_TARGETS,
14
+ type SkcModelAssignmentTargetId,
15
+ } from "./model-registry";
16
+ import { formatModelSelectorValue, resolveModelRoleValue } from "./model-resolver";
12
17
  import type { Settings } from "./settings";
13
18
 
14
19
  const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
@@ -60,6 +65,48 @@ export interface PreparedModelProfileActivation {
60
65
  */
61
66
  previousSessionDefaultModel: string | undefined;
62
67
  }
68
+ export interface MaterializeModelProfileAssignmentOptions {
69
+ session: Pick<
70
+ ModelProfileActivationSession,
71
+ "model" | "thinkingLevel" | "setActiveModelProfile" | "getActiveModelProfile"
72
+ >;
73
+ settings: Pick<Settings, "clearOverride" | "get" | "override" | "set">;
74
+ role: SkcModelAssignmentTargetId;
75
+ selector: string;
76
+ }
77
+
78
+ export function materializeActiveModelProfileAssignment(options: MaterializeModelProfileAssignmentOptions): boolean {
79
+ const activeProfile = options.session.getActiveModelProfile?.() ?? options.settings.get("modelProfile.default");
80
+ if (!activeProfile) return false;
81
+
82
+ const nextModelRoles = { ...options.settings.get("modelRoles") };
83
+ const nextAgentModelOverrides = { ...options.settings.get("task.agentModelOverrides") };
84
+ const target = SKC_MODEL_ASSIGNMENT_TARGETS[options.role];
85
+
86
+ if (options.role === "default") {
87
+ nextModelRoles.default = options.selector;
88
+ } else if (!nextModelRoles.default && options.session.model) {
89
+ nextModelRoles.default = formatModelSelectorValue(
90
+ `${options.session.model.provider}/${options.session.model.id}`,
91
+ options.session.thinkingLevel,
92
+ );
93
+ }
94
+
95
+ if (target.settingsPath === "modelRoles") {
96
+ nextModelRoles[options.role] = options.selector;
97
+ } else {
98
+ nextAgentModelOverrides[options.role] = options.selector;
99
+ }
100
+
101
+ options.settings.set("modelRoles", nextModelRoles);
102
+ options.settings.set("task.agentModelOverrides", nextAgentModelOverrides);
103
+ options.settings.set("modelProfile.default", undefined);
104
+ options.settings.clearOverride("modelProfile.default");
105
+ options.settings.override("modelRoles", nextModelRoles);
106
+ options.settings.override("task.agentModelOverrides", nextAgentModelOverrides);
107
+ options.session.setActiveModelProfile?.(undefined);
108
+ return true;
109
+ }
63
110
 
64
111
  export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
65
112
  return `Model profile "${profileName}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
@@ -255,20 +302,19 @@ export async function applyPreparedModelProfileActivation(
255
302
  modelChanged = true;
256
303
  }
257
304
  if (Object.keys(prepared.modelRoles).length > 0) {
258
- prepared.settings.override("modelRoles", {
259
- ...prepared.settings.get("modelRoles"),
260
- ...prepared.modelRoles,
261
- });
305
+ prepared.settings.override("modelRoles", { ...previousModelRoles, ...prepared.modelRoles });
262
306
  modelRolesChanged = true;
263
307
  }
264
308
  if (Object.keys(prepared.agentModelOverrides).length > 0) {
265
309
  prepared.settings.override("task.agentModelOverrides", {
266
- ...prepared.settings.get("task.agentModelOverrides"),
310
+ ...previousAgentModelOverrides,
267
311
  ...prepared.agentModelOverrides,
268
312
  });
269
313
  overridesChanged = true;
270
314
  }
271
315
  if (options.persistDefault) {
316
+ prepared.settings.set("modelRoles", {});
317
+ prepared.settings.set("task.agentModelOverrides", {});
272
318
  prepared.settings.set("modelProfile.default", prepared.profileName);
273
319
  defaultChanged = true;
274
320
  await prepared.settings.flush();
@@ -277,6 +323,8 @@ export async function applyPreparedModelProfileActivation(
277
323
  } catch (error) {
278
324
  if (defaultChanged) {
279
325
  prepared.settings.set("modelProfile.default", previousPersistedDefault);
326
+ prepared.settings.set("modelRoles", previousModelRoles);
327
+ prepared.settings.set("task.agentModelOverrides", previousAgentModelOverrides);
280
328
  }
281
329
  if (modelRolesChanged) {
282
330
  prepared.settings.override("modelRoles", previousModelRoles);
@@ -255,10 +255,14 @@ export const SETTINGS_SCHEMA = {
255
255
  "auth.broker.url": { type: "string", default: undefined },
256
256
  "auth.broker.token": { type: "string", default: undefined },
257
257
 
258
- // Notifications (Telegram bundled reference client)
258
+ // Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
259
259
  "notifications.enabled": { type: "boolean", default: false },
260
260
  "notifications.telegram.botToken": { type: "string", default: undefined },
261
261
  "notifications.telegram.chatId": { type: "string", default: undefined },
262
+ "notifications.discord.botToken": { type: "string", default: undefined },
263
+ "notifications.discord.channelId": { type: "string", default: undefined },
264
+ "notifications.slack.botToken": { type: "string", default: undefined },
265
+ "notifications.slack.channelId": { type: "string", default: undefined },
262
266
  "notifications.redact": { type: "boolean", default: false },
263
267
  "notifications.verbosity": {
264
268
  type: "string",
@@ -2126,6 +2130,17 @@ export const SETTINGS_SCHEMA = {
2126
2130
  ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
2127
2131
  },
2128
2132
 
2133
+ "web.insaneFallback": {
2134
+ type: "boolean",
2135
+ default: false,
2136
+ ui: {
2137
+ tab: "tools",
2138
+ label: "Insane Search Fallback",
2139
+ description:
2140
+ "Opt in to the vendored insane-search escalation for blocked public URL reads (403/WAF/JS-gated). Off by default. Requires preinstalled python3 + curl_cffi (and node + playwright/stealth for the browser phase); changes network posture by enabling TLS/browser impersonation for public pages.",
2141
+ },
2142
+ },
2143
+
2129
2144
  "github.enabled": {
2130
2145
  type: "boolean",
2131
2146
  default: false,
@@ -2753,6 +2768,7 @@ export const SETTINGS_SCHEMA = {
2753
2768
  values: [
2754
2769
  "auto",
2755
2770
  "duckduckgo",
2771
+ "insane",
2756
2772
  "exa",
2757
2773
  "brave",
2758
2774
  "jina",
@@ -2785,6 +2801,11 @@ export const SETTINGS_SCHEMA = {
2785
2801
  label: "DuckDuckGo",
2786
2802
  description: "Keyless default — no API key or OAuth required",
2787
2803
  },
2804
+ {
2805
+ value: "insane",
2806
+ label: "Insane",
2807
+ description: "Keyless safe public-route fallback inspired by upstream insane-search",
2808
+ },
2788
2809
  { value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set" },
2789
2810
  { value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
2790
2811
  { value: "jina", label: "Jina", description: "Requires JINA_API_KEY" },
@@ -3231,6 +3252,14 @@ export interface NotificationsSettings {
3231
3252
  botToken: string | undefined;
3232
3253
  chatId: string | undefined;
3233
3254
  };
3255
+ discord: {
3256
+ botToken: string | undefined;
3257
+ channelId: string | undefined;
3258
+ };
3259
+ slack: {
3260
+ botToken: string | undefined;
3261
+ channelId: string | undefined;
3262
+ };
3234
3263
  redact: boolean;
3235
3264
  daemon: {
3236
3265
  idleTimeoutMs: number;
@@ -0,0 +1,94 @@
1
+ export type DeepInterviewPlaintextAskLeakOption = "Yes, crystallize" | "Adjust wording" | "Missing scope";
2
+
3
+ export type DeepInterviewPlaintextAskLeakResult = {
4
+ kind: "deep_interview_plaintext_ask_leak";
5
+ matchedOptions: DeepInterviewPlaintextAskLeakOption[];
6
+ signals: {
7
+ optionsHeading: true;
8
+ restateIntent: true;
9
+ deepInterviewContext: boolean;
10
+ };
11
+ };
12
+
13
+ const CANONICAL_OPTIONS: DeepInterviewPlaintextAskLeakOption[] = [
14
+ "Yes, crystallize",
15
+ "Adjust wording",
16
+ "Missing scope",
17
+ ];
18
+
19
+ const RESTATE_INTENT_PATTERNS: RegExp[] = [
20
+ /\brestate\b/i,
21
+ /\bconfirmation\b/i,
22
+ /\bconfirm(?:ation|ed|ing)?\b/i,
23
+ /\bcrystall?ize\b/i,
24
+ /\bdoes\s+this\s+(?:capture|match|reflect|summari[sz]e)\b/i,
25
+ /\bbefore\s+(?:i|we)\s+(?:write|finali[sz]e|crystall?ize)\b/i,
26
+ /\bread\s+only\s+this\s+line\b/i,
27
+ /\bturn\s+this\s+into\s+(?:the\s+)?(?:spec|final)\b/i,
28
+ /재진술/,
29
+ /다시\s*말/,
30
+ /확인/,
31
+ /확정/,
32
+ /정리/,
33
+ /요약/,
34
+ /맞(?:습니까|나요|는지)/,
35
+ ];
36
+
37
+ const DEEP_INTERVIEW_CONTEXT_PATTERNS: RegExp[] = [
38
+ /\bdeep[ -]?interview\b/i,
39
+ /\bsocratic\b/i,
40
+ /\binterview\s+round\b/i,
41
+ /\bround\s+\d+\b/i,
42
+ /\bambiguity\b/i,
43
+ /\btopology\s+confirmation\b/i,
44
+ /딥\s*인터뷰/,
45
+ /심층\s*인터뷰/,
46
+ /인터뷰\s*(?:라운드|진행|확인)/,
47
+ ];
48
+
49
+ const FINAL_ARTIFACT_PATTERNS: RegExp[] = [
50
+ /^\s*#{1,3}\s*(?:(?:final|approved|completed|완료|최종)\s+)?(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/im,
51
+ /^\s*#{1,3}\s*(?:deep[ -]?interview\s+)?(?:final|approved|completed|완료|최종)\s+(?:spec(?:ification)?|transcript)\b/im,
52
+ /\b(?:final|approved|completed)\s+(?:deep[ -]?interview\s+)?(?:spec(?:ification)?|transcript)\b/i,
53
+ /\binterview\s+transcript\b/i,
54
+ /\.skc\/specs\//i,
55
+ /최종\s*(?:명세|스펙|기록)/,
56
+ ];
57
+
58
+ function hasOptionsHeading(text: string): boolean {
59
+ return /(?:^|\n)\s*options\s*:/i.test(text);
60
+ }
61
+
62
+ function hasPattern(text: string, patterns: RegExp[]): boolean {
63
+ return patterns.some(pattern => pattern.test(text));
64
+ }
65
+
66
+ function matchedCanonicalOptions(text: string): DeepInterviewPlaintextAskLeakOption[] {
67
+ const folded = text.toLocaleLowerCase("en-US");
68
+ return CANONICAL_OPTIONS.filter(option => folded.includes(option.toLocaleLowerCase("en-US")));
69
+ }
70
+
71
+ function looksLikeFinalArtifact(text: string): boolean {
72
+ return hasPattern(text, FINAL_ARTIFACT_PATTERNS);
73
+ }
74
+
75
+ export function detectDeepInterviewPlaintextAskLeak(text: string): DeepInterviewPlaintextAskLeakResult | null {
76
+ const trimmed = text.trim();
77
+ if (trimmed.length === 0) return null;
78
+ if (looksLikeFinalArtifact(trimmed)) return null;
79
+ if (!hasOptionsHeading(trimmed)) return null;
80
+
81
+ const matchedOptions = matchedCanonicalOptions(trimmed);
82
+ if (matchedOptions.length < 2) return null;
83
+ if (!hasPattern(trimmed, RESTATE_INTENT_PATTERNS)) return null;
84
+
85
+ return {
86
+ kind: "deep_interview_plaintext_ask_leak",
87
+ matchedOptions,
88
+ signals: {
89
+ optionsHeading: true,
90
+ restateIntent: true,
91
+ deepInterviewContext: hasPattern(trimmed, DEEP_INTERVIEW_CONTEXT_PATTERNS),
92
+ },
93
+ };
94
+ }
@@ -17,7 +17,7 @@
17
17
  "linter": {
18
18
  "enabled": true,
19
19
  "rules": {
20
- "recommended": true
20
+ "preset": "recommended"
21
21
  }
22
22
  },
23
23
  "javascript": {
@@ -292,7 +292,7 @@ Auto-research must never add a public skill entrypoint, never be slash-command/d
292
292
 
293
293
  ### Step 2b: Ask the Question
294
294
 
295
- Use the `ask` tool with the generated question. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
295
+ Use the `ask` tool with the generated question. When a question has options, you MUST call `ask` and must not print `Question:`/`Options:` blocks as assistant prose. If you already printed a question/options block as prose, your next action is to call `ask` with the same question/options, not to wait for a typed answer. Before rendering the prompt/options, apply `language.instruction` from state when present so the entire user-facing question remains in the preserved session language. Present it clearly with the current ambiguity context:
296
296
 
297
297
  ```
298
298
  Round {n} | Component: {target_component_name} | Targeting: {weakest_dimension} | Why now: {one_sentence_targeting_rationale} | Ambiguity: {score}%
@@ -458,7 +458,7 @@ Also recompute and persist `ambiguity_milestone` each round (detect band transit
458
458
 
459
459
  - **Round 3+**: Allow early exit if user says "enough", "let's go", "build it"
460
460
  - **Round 10**: Show soft warning: "We're at 10 rounds. Current ambiguity: {score}%. Continue or proceed with current clarity?"
461
- - **Round 20**: Hard cap: "Maximum interview rounds reached. Proceeding with current clarity level ({score}%)."
461
+ - **Round 100**: Hard cap: "Maximum interview rounds reached. Proceeding with current clarity level ({score}%)."
462
462
 
463
463
  ## Phase 3: Lateral Review Panel (milestone-triggered)
464
464
 
@@ -495,7 +495,7 @@ When ambiguity ≤ threshold (or hard cap / early exit):
495
495
 
496
496
  **4a. Closure / Acceptance Guard.** Even when ambiguity ≤ threshold, do not treat the math as completion. Run an independent readiness audit from the full main-session perspective (including explore findings, established facts, and triggers the scorer may not have fully weighed). Confirm every active topology component has goal/constraint/criteria coverage, no unresolved or disputed trigger remains on a path that matters, and no low-confidence auto-answer is standing in for user-confirmed truth above the clarity cap. If a material gap exists, explicitly override the gate to the user — "The math says ready, but I am not accepting it yet because {gap}" — and ask the single highest-impact follow-up, returning to Phase 2. Record any override in `state.closure_overrides`.
497
497
 
498
- **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
498
+ **4b. Restate gate.** Once closure passes, collapse the agreed answers into ONE sentence goal that covers every active component, and confirm it with a single `ask`: "If someone read only this line, would they reach the same outcome you have in mind?" Offer **Yes, crystallize**, **Adjust wording**, and **Missing scope**, plus free-text, applying `language.instruction` when present. Because this gate has options, it MUST go through `ask`: do not print the Restate question and options as assistant prose with `Question:`/`Options:` labels. If the Restate gate was already printed that way, immediately call `ask` with the same question/options before accepting or waiting for any answer. On "Adjust wording" / "Missing scope", collect the exact correction with one follow-up `ask`, route it back through Step 2c scoring and established-facts maintenance (a correction can change ambiguity), then re-run closure and ask the Restate gate again. Cap at two loops; if alignment is not reached, return to Phase 2 with a targeted question instead of forcing a goal line. Persist the confirmed line as `state.restated_goal`.
499
499
 
500
500
  1. **Generate the specification** using opus model with the prompt-safe transcript. If the full interview transcript or initial context is too large, include the summary plus all concrete decisions, acceptance criteria, unresolved gaps, and ontology snapshots; never overflow the prompt with raw oversized context.
501
501
  - Apply `language.instruction` when present so user-facing prose in the spec preserves the session language; keep code identifiers, file paths, commands, JSON/settings keys, and quoted source text unchanged.
@@ -690,6 +690,7 @@ Skipping any stage is possible but reduces quality assurance:
690
690
 
691
691
  <Tool_Usage>
692
692
  - Use the `ask` tool for each interview question — provides clickable UI with contextual options
693
+ - For any option-bearing question, call `ask`; never print `Question:`/`Options:` blocks as assistant prose. If such a block was already printed, call `ask` with the same question/options as the very next action instead of waiting for a typed/prose answer
693
694
  - Preserve the SKC `ask` tool path for native interaction; do not introduce parallel structured-question transport into this skill
694
695
  - Use `read/search/find exploration or a bounded read-only planner/architect subagent` for brownfield codebase exploration (run BEFORE asking user about codebase)
695
696
  - Use opus model (temperature 0.1) for ambiguity scoring — consistency is critical
@@ -698,7 +699,7 @@ Skipping any stage is possible but reduces quality assurance:
698
699
  - Use the SKC workflow CLI to save the final spec at `.skc/_session-{sessionid}/specs/deep-interview-{slug}.md` exactly; do not use `write`, `edit`, or `ast_edit` directly on `.skc/` paths without force override.
699
700
  - Use public SKC workflow entrypoints to bridge to ralplan, ultragoal, or team only after explicit execution approval — never implement directly. Implementation handoff defaults to ultragoal; reserve team for when tmux-based interactive worker parallelization is genuinely required.
700
701
  - The lateral-review panel spawns read-only persona subagents (Task tool) in parallel with independent context; it is an assist layer, never an executor and never the completion authority
701
- - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each
702
+ - Apply the Refine gate (Step 2b″), the Dialectic Rhythm Guard (Step 2a), and the Closure + Restate gates (Phase 4) through the `ask` tool, preserving `language.instruction` for each; if any of these gates has options, the assistant must call `ask` and must not print `Question:`/`Options:` blocks as assistant prose
702
703
  - Use internal fragment auto-modes only at their documented hooks: `auto-research-greenfield.md` between Step 2a and 2b for greenfield `research: true` questions, `auto-answer-uncertain.md` as Step 2b′ after `ask` resolves and before scoring, and `lateral-review-panel.md` for the Phase 3 panel personas at ambiguity-milestone transitions and before synthesizing agent-supplied answers.
703
704
  - Fragment auto-modes are loaded on demand as `kind: "skill-fragment"`; they are not public workflow skills, not slash-command/discoverable, and not `skill://` registrations.
704
705
  </Tool_Usage>
@@ -787,7 +788,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
787
788
  </Examples>
788
789
 
789
790
  <Escalation_And_Stop_Conditions>
790
- - **Hard cap at 20 rounds**: Proceed with whatever clarity exists, noting the risk
791
+ - **Hard cap at 100 rounds**: Proceed with whatever clarity exists, noting the risk
791
792
  - **Soft warning at 10 rounds**: Offer to continue or proceed
792
793
  - **Early exit (round 3+)**: Allow with warning if ambiguity > threshold
793
794
  - **User says "stop", "cancel", "abort"**: Stop immediately, save state for resume
@@ -822,7 +823,7 @@ Optional settings in `.skc/settings.json`:
822
823
  "skc": {
823
824
  "deepInterview": {
824
825
  "ambiguityThreshold": <resolvedThreshold>,
825
- "maxRounds": 20,
826
+ "maxRounds": 100,
826
827
  "softWarningRounds": 10,
827
828
  "minRoundsBeforeExit": 3,
828
829
  "enableChallengeAgents": true,