@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.
- package/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/notify-cli.d.ts +2 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +39 -2
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/defaults/skc-defaults.d.ts +2 -13
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +9 -1
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +133 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/threaded-inbound.d.ts +19 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +16 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
- package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/fetch.d.ts +23 -0
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/telegram-send.d.ts +32 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +25 -0
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/dist/types/web/search/provider.d.ts +18 -1
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/types.d.ts +12 -4
- package/package.json +14 -10
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +180 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/scripts/verify-insane-vendor.ts +132 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/fast-help.ts +30 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/notify-cli.ts +152 -5
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli.ts +30 -12
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/commands/team.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +55 -7
- package/src/config/settings-schema.ts +30 -1
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/skc/skills/ralplan/SKILL.md +11 -4
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/defaults/skc-defaults.ts +2 -27
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/extensibility/skc-plugins/compiler.ts +351 -0
- package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/skc-plugins/index.ts +9 -0
- package/src/extensibility/skc-plugins/injection.ts +109 -0
- package/src/extensibility/skc-plugins/installer.ts +434 -0
- package/src/extensibility/skc-plugins/loader.ts +3 -1
- package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/skc-plugins/observability.ts +84 -0
- package/src/extensibility/skc-plugins/paths.ts +1 -1
- package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/skc-plugins/registry.ts +180 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/skc-plugins/schema.ts +250 -20
- package/src/extensibility/skc-plugins/session-validation.ts +147 -0
- package/src/extensibility/skc-plugins/types.ts +199 -3
- package/src/extensibility/skc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/lsp/config.ts +16 -3
- package/src/lsp/defaults.json +7 -0
- package/src/lsp/types.ts +2 -0
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/assistant-message.ts +49 -1
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +56 -11
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/event-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +53 -11
- package/src/modes/interactive-mode.ts +50 -3
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/attachment-registry.ts +23 -0
- package/src/notifications/chat-adapters.ts +147 -0
- package/src/notifications/config.ts +23 -2
- package/src/notifications/engine.ts +100 -0
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +417 -45
- package/src/notifications/lifecycle-commands.ts +238 -0
- package/src/notifications/lifecycle-control-runtime.ts +405 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/managed-daemon.ts +163 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +984 -242
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/threaded-inbound.ts +60 -4
- package/src/notifications/threaded-render.ts +20 -2
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +248 -125
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +85 -22
- package/src/skc-runtime/ledger-event-renderer.ts +1 -0
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/ralplan-runtime.ts +2 -2
- package/src/skc-runtime/team-runtime.ts +56 -23
- package/src/skc-runtime/tmux-common.ts +88 -4
- package/src/skc-runtime/tmux-sessions.ts +111 -9
- package/src/skc-runtime/ultragoal-guard.ts +25 -8
- package/src/skc-runtime/ultragoal-runtime.ts +75 -15
- package/src/skc-runtime/workflow-manifest.generated.json +29 -0
- package/src/skc-runtime/workflow-manifest.ts +7 -2
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +94 -1
- package/src/tools/index.ts +6 -1
- package/src/tools/telegram-send.ts +137 -0
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/bridge.ts +350 -0
- package/src/web/insane/url-guard.ts +159 -0
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/provider.ts +77 -18
- package/src/web/search/providers/anthropic.ts +70 -3
- package/src/web/search/providers/codex.ts +1 -119
- package/src/web/search/providers/gemini.ts +99 -0
- package/src/web/search/providers/insane.ts +551 -0
- package/src/web/search/providers/openai-compatible.ts +66 -32
- package/src/web/search/providers/text-citations.ts +111 -0
- package/src/web/search/types.ts +13 -2
- package/vendor/insane-search/LICENSE +21 -0
- package/vendor/insane-search/MANIFEST.json +24 -0
- package/vendor/insane-search/engine/__init__.py +23 -0
- package/vendor/insane-search/engine/__main__.py +128 -0
- package/vendor/insane-search/engine/bias_check.py +183 -0
- package/vendor/insane-search/engine/executor.py +254 -0
- package/vendor/insane-search/engine/fetch_chain.py +725 -0
- package/vendor/insane-search/engine/learning.py +175 -0
- package/vendor/insane-search/engine/phase0.py +214 -0
- package/vendor/insane-search/engine/safety.py +91 -0
- package/vendor/insane-search/engine/templates/package.json +11 -0
- package/vendor/insane-search/engine/templates/playwright_mobile_chrome.js +188 -0
- package/vendor/insane-search/engine/templates/playwright_real_chrome.js +243 -0
- package/vendor/insane-search/engine/tests/test_hardening.py +57 -0
- package/vendor/insane-search/engine/tests/test_smoke.py +152 -0
- package/vendor/insane-search/engine/tests/test_u1.py +200 -0
- package/vendor/insane-search/engine/tests/test_u4.py +131 -0
- package/vendor/insane-search/engine/tests/test_u5.py +163 -0
- package/vendor/insane-search/engine/tests/test_u7.py +124 -0
- package/vendor/insane-search/engine/transport.py +211 -0
- package/vendor/insane-search/engine/url_transforms.py +98 -0
- package/vendor/insane-search/engine/validators.py +331 -0
- package/vendor/insane-search/engine/waf_detector.py +214 -0
- 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
|
|
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
|
|
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
|
|
79
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
+
}
|
package/src/commands/plugin.ts
CHANGED
|
@@ -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
|
|
package/src/commands/session.ts
CHANGED
|
@@ -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;
|
package/src/commands/team.ts
CHANGED
|
@@ -95,7 +95,7 @@ export default class Team extends Command {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
static examples = [
|
|
98
|
-
"skc --tmux # start
|
|
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:
|
|
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 {
|
|
11
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
+
}
|
|
@@ -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
|
|
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
|
|
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":
|
|
826
|
+
"maxRounds": 100,
|
|
826
827
|
"softWarningRounds": 10,
|
|
827
828
|
"minRoundsBeforeExit": 3,
|
|
828
829
|
"enableChallengeAgents": true,
|