@sayknow-cli/coding-agent 0.3.7 → 0.3.9
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +119 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/components/thinking-selector.d.ts +2 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +30 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +8 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +12 -3
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon.d.ts +45 -3
- package/dist/types/notifications/telegram-reference.d.ts +7 -5
- package/dist/types/notifications/threaded-render.d.ts +14 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +103 -17
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +148 -33
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +76 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +17 -8
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +12 -11
- package/src/modes/controllers/input-controller.ts +192 -35
- package/src/modes/controllers/selector-controller.ts +168 -24
- package/src/modes/interactive-mode.ts +56 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +52 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +144 -18
- package/src/notifications/lifecycle-commands.ts +74 -23
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +606 -98
- package/src/notifications/telegram-reference.ts +17 -16
- package/src/notifications/threaded-render.ts +45 -2
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +6 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +88 -36
- package/src/session/agent-session.ts +1013 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/tmux-common.ts +1 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1672 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +296 -72
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/utils/pasted-image-path.ts +170 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { ArgDescriptor, CommandCtor, CommandEntry, FlagDescriptor } from "@sayknow-cli/utils/cli";
|
|
5
|
+
|
|
6
|
+
export type FigName = string | string[];
|
|
7
|
+
|
|
8
|
+
export interface FigArg {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
isOptional?: boolean;
|
|
12
|
+
isVariadic?: boolean;
|
|
13
|
+
suggestions?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FigOption {
|
|
17
|
+
name: FigName;
|
|
18
|
+
description?: string;
|
|
19
|
+
args?: FigArg;
|
|
20
|
+
isRepeatable?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FigSubcommand {
|
|
24
|
+
name: FigName;
|
|
25
|
+
description?: string;
|
|
26
|
+
options?: FigOption[];
|
|
27
|
+
args?: FigArg | FigArg[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FigSpec extends FigSubcommand {
|
|
31
|
+
name: string;
|
|
32
|
+
subcommands?: FigSubcommand[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type InshellisenseIndexStatus = "created" | "updated" | "already-compatible" | "overwritten";
|
|
36
|
+
|
|
37
|
+
export interface InstallSkcInshellisenseSpecOptions {
|
|
38
|
+
dir?: string;
|
|
39
|
+
force?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface InstallSkcInshellisenseSpecResult {
|
|
43
|
+
directory: string;
|
|
44
|
+
specPath: string;
|
|
45
|
+
indexPath: string;
|
|
46
|
+
indexStatus: InshellisenseIndexStatus;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const GENERATED_INDEX_MARKER = "// Generated by skc completion inshellisense";
|
|
50
|
+
const BUILTIN_ROOT_OPTIONS: FigOption[] = [
|
|
51
|
+
{ name: ["-h", "--help"], description: "Show help" },
|
|
52
|
+
{ name: ["-v", "--version"], description: "Show version" },
|
|
53
|
+
];
|
|
54
|
+
const BUILTIN_COMMAND_OPTIONS: FigOption[] = [{ name: ["-h", "--help"], description: "Show help" }];
|
|
55
|
+
|
|
56
|
+
export function defaultInshellisenseSpecDir(homeDir: string = os.homedir()): string {
|
|
57
|
+
return path.join(homeDir, ".fig", "autocomplete", "build");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nameList(name: FigName): string[] {
|
|
61
|
+
return Array.isArray(name) ? name : [name];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function optionKey(option: FigOption): string {
|
|
65
|
+
return nameList(option.name).join("\u0000");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function combineOptions(primary: FigOption[], secondary: FigOption[]): FigOption[] {
|
|
69
|
+
const seen = new Set<string>();
|
|
70
|
+
const combined: FigOption[] = [];
|
|
71
|
+
for (const option of [...primary, ...secondary]) {
|
|
72
|
+
const key = optionKey(option);
|
|
73
|
+
if (seen.has(key)) continue;
|
|
74
|
+
seen.add(key);
|
|
75
|
+
combined.push(option);
|
|
76
|
+
}
|
|
77
|
+
return combined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function commandEntryName(entry: CommandEntry): FigName {
|
|
81
|
+
const names = [entry.name, ...(entry.aliases ?? [])];
|
|
82
|
+
return names.length === 1 ? names[0]! : names;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function flagName(name: string, descriptor: FlagDescriptor): FigName {
|
|
86
|
+
const longName = `--${name}`;
|
|
87
|
+
if (!descriptor.char) return longName;
|
|
88
|
+
return [`-${descriptor.char}`, longName];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function argSuggestions(options: readonly string[] | undefined): string[] | undefined {
|
|
92
|
+
return options && options.length > 0 ? [...options] : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function optionArgName(name: string, descriptor: FlagDescriptor): string {
|
|
96
|
+
if (descriptor.options && descriptor.options.length > 0) return name;
|
|
97
|
+
return descriptor.kind === "integer" ? "int" : "value";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function figOptionFromFlag(name: string, descriptor: FlagDescriptor): FigOption {
|
|
101
|
+
const option: FigOption = {
|
|
102
|
+
name: flagName(name, descriptor),
|
|
103
|
+
};
|
|
104
|
+
if (descriptor.description) option.description = descriptor.description;
|
|
105
|
+
if (descriptor.multiple) option.isRepeatable = true;
|
|
106
|
+
if (descriptor.kind !== "boolean") {
|
|
107
|
+
const arg: FigArg = { name: optionArgName(name, descriptor) };
|
|
108
|
+
const suggestions = argSuggestions(descriptor.options);
|
|
109
|
+
if (suggestions) arg.suggestions = suggestions;
|
|
110
|
+
option.args = arg;
|
|
111
|
+
}
|
|
112
|
+
return option;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function figOptionsFromFlags(flags: Record<string, FlagDescriptor> | undefined): FigOption[] {
|
|
116
|
+
if (!flags) return [];
|
|
117
|
+
return Object.entries(flags).map(([name, descriptor]) => figOptionFromFlag(name, descriptor));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function figArgFromDescriptor(name: string, descriptor: ArgDescriptor): FigArg {
|
|
121
|
+
const arg: FigArg = { name };
|
|
122
|
+
if (descriptor.description) arg.description = descriptor.description;
|
|
123
|
+
if (!descriptor.required) arg.isOptional = true;
|
|
124
|
+
if (descriptor.multiple) arg.isVariadic = true;
|
|
125
|
+
const suggestions = argSuggestions(descriptor.options);
|
|
126
|
+
if (suggestions) arg.suggestions = suggestions;
|
|
127
|
+
return arg;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function figArgsFromDescriptors(args: Record<string, ArgDescriptor> | undefined): FigArg | FigArg[] | undefined {
|
|
131
|
+
if (!args) return undefined;
|
|
132
|
+
const values = Object.entries(args).map(([name, descriptor]) => figArgFromDescriptor(name, descriptor));
|
|
133
|
+
if (values.length === 0) return undefined;
|
|
134
|
+
return values.length === 1 ? values[0] : values;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function commandToSubcommand(entry: CommandEntry, command: CommandCtor): FigSubcommand {
|
|
138
|
+
const subcommand: FigSubcommand = { name: commandEntryName(entry) };
|
|
139
|
+
if (command.description) subcommand.description = command.description;
|
|
140
|
+
const options = combineOptions(BUILTIN_COMMAND_OPTIONS, figOptionsFromFlags(command.flags));
|
|
141
|
+
if (options.length > 0) subcommand.options = options;
|
|
142
|
+
const args = figArgsFromDescriptors(command.args);
|
|
143
|
+
if (args) subcommand.args = args;
|
|
144
|
+
return subcommand;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function buildSkcFigSpec(entries: CommandEntry[], rootCommandOverride?: CommandCtor): Promise<FigSpec> {
|
|
148
|
+
const subcommands: FigSubcommand[] = [];
|
|
149
|
+
let rootCommand: CommandCtor | undefined = rootCommandOverride;
|
|
150
|
+
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (entry.name === "launch" && rootCommandOverride) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
let command: CommandCtor;
|
|
156
|
+
try {
|
|
157
|
+
command = await entry.load();
|
|
158
|
+
} catch {
|
|
159
|
+
if (entry.name !== "launch") {
|
|
160
|
+
subcommands.push({ name: commandEntryName(entry) });
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (entry.name === "launch") {
|
|
165
|
+
rootCommand = command;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (command.hidden) continue;
|
|
169
|
+
subcommands.push(commandToSubcommand(entry, command));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const spec: FigSpec = {
|
|
173
|
+
name: "skc",
|
|
174
|
+
description: rootCommand?.description ?? "Sayknow-CLI CLI",
|
|
175
|
+
};
|
|
176
|
+
const rootOptions = combineOptions(BUILTIN_ROOT_OPTIONS, figOptionsFromFlags(rootCommand?.flags));
|
|
177
|
+
if (rootOptions.length > 0) spec.options = rootOptions;
|
|
178
|
+
const rootArgs = figArgsFromDescriptors(rootCommand?.args);
|
|
179
|
+
if (rootArgs) spec.args = rootArgs;
|
|
180
|
+
if (subcommands.length > 0) spec.subcommands = subcommands;
|
|
181
|
+
return spec;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function renderFigSpecModule(spec: FigSpec): string {
|
|
185
|
+
return `const completionSpec = ${JSON.stringify(spec, null, "\t")};\n\nexport default completionSpec;\n`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function renderInshellisenseIndexModule(): string {
|
|
189
|
+
return `${GENERATED_INDEX_MARKER}\n\nexport default ["skc"];\nexport const diffVersionedCompletions = [];\n`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function fileTextIfExists(filePath: string): Promise<string | undefined> {
|
|
193
|
+
try {
|
|
194
|
+
return await Bun.file(filePath).text();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function indexDeclaresSkc(content: string): boolean {
|
|
202
|
+
return /["']skc["']/.test(content);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function resolveIndexStatus(indexPath: string, force: boolean | undefined): Promise<InshellisenseIndexStatus> {
|
|
206
|
+
const existing = await fileTextIfExists(indexPath);
|
|
207
|
+
if (existing === undefined) return "created";
|
|
208
|
+
if (existing.includes(GENERATED_INDEX_MARKER)) {
|
|
209
|
+
return existing === renderInshellisenseIndexModule() ? "already-compatible" : "updated";
|
|
210
|
+
}
|
|
211
|
+
if (indexDeclaresSkc(existing)) return "already-compatible";
|
|
212
|
+
if (force) return "overwritten";
|
|
213
|
+
throw new Error(
|
|
214
|
+
`inshellisense index already exists at ${indexPath} and does not list skc; rerun with --force to overwrite it or pass --dir to install into a SKC-only specs directory`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function installSkcInshellisenseSpec(
|
|
219
|
+
spec: FigSpec,
|
|
220
|
+
options: InstallSkcInshellisenseSpecOptions = {},
|
|
221
|
+
): Promise<InstallSkcInshellisenseSpecResult> {
|
|
222
|
+
const directory = path.resolve(options.dir ?? defaultInshellisenseSpecDir());
|
|
223
|
+
const specPath = path.join(directory, "skc.js");
|
|
224
|
+
const indexPath = path.join(directory, "index.js");
|
|
225
|
+
const indexStatus = await resolveIndexStatus(indexPath, options.force);
|
|
226
|
+
|
|
227
|
+
await fs.mkdir(directory, { recursive: true });
|
|
228
|
+
await Bun.write(specPath, renderFigSpecModule(spec));
|
|
229
|
+
if (indexStatus !== "already-compatible") {
|
|
230
|
+
await Bun.write(indexPath, renderInshellisenseIndexModule());
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { directory, specPath, indexPath, indexStatus };
|
|
234
|
+
}
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface ConfigCommandArgs {
|
|
|
33
33
|
value?: string;
|
|
34
34
|
flags: {
|
|
35
35
|
json?: boolean;
|
|
36
|
+
showSecrets?: boolean;
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
// =============================================================================
|
|
@@ -47,6 +48,43 @@ type CliSettingDef = {
|
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
|
|
51
|
+
const REDACTED_SECRET_VALUE = "<redacted>";
|
|
52
|
+
const SECRET_SETTING_WORDS = new Set(["token", "secret", "password", "passwd", "pwd", "credential", "credentials"]);
|
|
53
|
+
const SECRET_SETTING_COMPOUND_PREFIXES = [
|
|
54
|
+
"api",
|
|
55
|
+
"auth",
|
|
56
|
+
"access",
|
|
57
|
+
"refresh",
|
|
58
|
+
"bearer",
|
|
59
|
+
"session",
|
|
60
|
+
"client",
|
|
61
|
+
"broker",
|
|
62
|
+
"bot",
|
|
63
|
+
"basic",
|
|
64
|
+
];
|
|
65
|
+
const SECRET_SETTING_COMPOUND_SUFFIXES = ["token", "secret", "password", "credential"];
|
|
66
|
+
|
|
67
|
+
function isSecretSettingSegment(segment: string): boolean {
|
|
68
|
+
const normalized = segment.toLowerCase();
|
|
69
|
+
if (SECRET_SETTING_WORDS.has(normalized)) return true;
|
|
70
|
+
if (/api[-_]?key/i.test(segment)) return true;
|
|
71
|
+
const words = normalized.split(/[-_]/).filter(Boolean);
|
|
72
|
+
if (words.some(word => SECRET_SETTING_WORDS.has(word))) return true;
|
|
73
|
+
return SECRET_SETTING_COMPOUND_PREFIXES.some(prefix =>
|
|
74
|
+
SECRET_SETTING_COMPOUND_SUFFIXES.some(suffix => normalized === `${prefix}${suffix}`),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isSecretSettingPath(path: string): boolean {
|
|
79
|
+
return path.split(".").some(segment => isSecretSettingSegment(segment));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function redactConfigValue(path: string, value: unknown, showSecrets?: boolean): unknown {
|
|
83
|
+
if (showSecrets || value === undefined || value === null || !isSecretSettingPath(path)) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
return REDACTED_SECRET_VALUE;
|
|
87
|
+
}
|
|
50
88
|
|
|
51
89
|
/** Find setting definition by path */
|
|
52
90
|
function findSettingDef(path: string): CliSettingDef | undefined {
|
|
@@ -105,6 +143,8 @@ export function parseConfigArgs(args: string[]): ConfigCommandArgs | undefined {
|
|
|
105
143
|
const arg = args[i];
|
|
106
144
|
if (arg === "--json") {
|
|
107
145
|
result.flags.json = true;
|
|
146
|
+
} else if (arg === "--show-secrets") {
|
|
147
|
+
result.flags.showSecrets = true;
|
|
108
148
|
} else if (!arg.startsWith("-")) {
|
|
109
149
|
positionalArgs.push(arg);
|
|
110
150
|
}
|
|
@@ -265,14 +305,15 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
|
|
|
265
305
|
}
|
|
266
306
|
}
|
|
267
307
|
|
|
268
|
-
function handleList(flags: { json?: boolean }): void {
|
|
308
|
+
function handleList(flags: { json?: boolean; showSecrets?: boolean }): void {
|
|
269
309
|
const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
|
|
270
310
|
|
|
271
311
|
if (flags.json) {
|
|
272
312
|
const result: Record<string, { value: unknown; type: string; description: string }> = {};
|
|
273
313
|
for (const def of defs) {
|
|
314
|
+
const value = settings.get(def.path);
|
|
274
315
|
result[def.path] = {
|
|
275
|
-
value:
|
|
316
|
+
value: redactConfigValue(def.path, value, flags.showSecrets),
|
|
276
317
|
type: def.type,
|
|
277
318
|
description: def.description,
|
|
278
319
|
};
|
|
@@ -301,7 +342,8 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
301
342
|
console.log(chalk.bold.blue(`[${group}]`));
|
|
302
343
|
for (const def of groups[group]) {
|
|
303
344
|
const value = settings.get(def.path);
|
|
304
|
-
const
|
|
345
|
+
const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
|
|
346
|
+
const valueStr = formatValue(displayValue);
|
|
305
347
|
const typeStr = getTypeDisplay(def);
|
|
306
348
|
console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
|
|
307
349
|
}
|
|
@@ -309,7 +351,7 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
309
351
|
}
|
|
310
352
|
}
|
|
311
353
|
|
|
312
|
-
function handleGet(key: string | undefined, flags: { json?: boolean }): void {
|
|
354
|
+
function handleGet(key: string | undefined, flags: { json?: boolean; showSecrets?: boolean }): void {
|
|
313
355
|
if (!key) {
|
|
314
356
|
console.error(chalk.red(`Usage: ${APP_NAME} config get <key>`));
|
|
315
357
|
console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
|
|
@@ -324,16 +366,23 @@ function handleGet(key: string | undefined, flags: { json?: boolean }): void {
|
|
|
324
366
|
}
|
|
325
367
|
|
|
326
368
|
const value = settings.get(def.path);
|
|
369
|
+
const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
|
|
327
370
|
|
|
328
371
|
if (flags.json) {
|
|
329
|
-
console.log(
|
|
372
|
+
console.log(
|
|
373
|
+
JSON.stringify({ key: def.path, value: displayValue, type: def.type, description: def.description }, null, 2),
|
|
374
|
+
);
|
|
330
375
|
return;
|
|
331
376
|
}
|
|
332
377
|
|
|
333
|
-
console.log(formatValue(
|
|
378
|
+
console.log(formatValue(displayValue));
|
|
334
379
|
}
|
|
335
380
|
|
|
336
|
-
async function handleSet(
|
|
381
|
+
async function handleSet(
|
|
382
|
+
key: string | undefined,
|
|
383
|
+
value: string | undefined,
|
|
384
|
+
flags: { json?: boolean; showSecrets?: boolean },
|
|
385
|
+
): Promise<void> {
|
|
337
386
|
if (!key || value === undefined) {
|
|
338
387
|
console.error(chalk.red(`Usage: ${APP_NAME} config set <key> <value>`));
|
|
339
388
|
console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
|
|
@@ -355,11 +404,12 @@ async function handleSet(key: string | undefined, value: string | undefined, fla
|
|
|
355
404
|
}
|
|
356
405
|
|
|
357
406
|
const newValue = settings.get(def.path);
|
|
407
|
+
const displayValue = redactConfigValue(def.path, newValue, flags.showSecrets);
|
|
358
408
|
|
|
359
409
|
if (flags.json) {
|
|
360
|
-
console.log(JSON.stringify({ key: def.path, value:
|
|
410
|
+
console.log(JSON.stringify({ key: def.path, value: displayValue }));
|
|
361
411
|
} else {
|
|
362
|
-
console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(
|
|
412
|
+
console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(displayValue)}`));
|
|
363
413
|
}
|
|
364
414
|
}
|
|
365
415
|
|
|
@@ -409,6 +459,7 @@ ${chalk.bold("Commands:")}
|
|
|
409
459
|
|
|
410
460
|
${chalk.bold("Options:")}
|
|
411
461
|
--json Output as JSON
|
|
462
|
+
--show-secrets Show secret-like setting values without redaction (unsafe)
|
|
412
463
|
|
|
413
464
|
${chalk.bold("Examples:")}
|
|
414
465
|
${APP_NAME} config list
|
|
@@ -418,6 +469,7 @@ ${chalk.bold("Examples:")}
|
|
|
418
469
|
${APP_NAME} config set defaultThinkingLevel medium
|
|
419
470
|
${APP_NAME} config reset steeringMode
|
|
420
471
|
${APP_NAME} config list --json
|
|
472
|
+
${APP_NAME} config get auth.broker.token --show-secrets
|
|
421
473
|
${APP_NAME} config init-xdg
|
|
422
474
|
|
|
423
475
|
${chalk.bold("Boolean Values:")}
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function getExtraHelpText(): string {
|
|
|
15
15
|
${APP_NAME} deep-interview - Run requirements interview workflow
|
|
16
16
|
${APP_NAME} skills - List/read embedded workflow skills
|
|
17
17
|
${APP_NAME} config - List, get, and set configuration values
|
|
18
|
+
${APP_NAME} stats - View AI usage statistics (tokens, cost, requests)
|
|
18
19
|
${APP_NAME} notify - Send or test notifications
|
|
19
20
|
${APP_NAME} daemon - Manage background daemon helpers
|
|
20
21
|
${APP_NAME} mcp - Manage MCP server registrations
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { sessionRoot } from "../skc-runtime/session-layout";
|
|
6
|
+
import type { TaskTokenLog } from "../task/types";
|
|
7
|
+
import { buildFixtureReport, type LiveRunReportShape, runFixtureReport } from "./fixture-report";
|
|
8
|
+
|
|
9
|
+
function expectParseReportCompatible(report: LiveRunReportShape, fixtureId: string): void {
|
|
10
|
+
expect(report.schemaVersion).toBe(1);
|
|
11
|
+
expect(typeof report.binaryId).toBe("string");
|
|
12
|
+
expect(report.binaryId.length).toBeGreaterThan(0);
|
|
13
|
+
expect(report.fixtureId).toBe(fixtureId);
|
|
14
|
+
for (const value of Object.values(report.totals)) {
|
|
15
|
+
expect(typeof value).toBe("number");
|
|
16
|
+
expect(Number.isFinite(value)).toBe(true);
|
|
17
|
+
}
|
|
18
|
+
expect(report.receiptArtifactRatio).toBeNull();
|
|
19
|
+
expect(report.spawnDecisions).toBeNull();
|
|
20
|
+
expect(report.roi).toBeNull();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("fixture report", () => {
|
|
24
|
+
it("reports empty logs as a schema-v1 null-cache-hit report", () => {
|
|
25
|
+
const report = buildFixtureReport("empty-fixture", []);
|
|
26
|
+
|
|
27
|
+
expectParseReportCompatible(report, "empty-fixture");
|
|
28
|
+
expect(report.totals).toEqual({
|
|
29
|
+
turns: 0,
|
|
30
|
+
inputTokens: 0,
|
|
31
|
+
outputTokens: 0,
|
|
32
|
+
cacheReadTokens: 0,
|
|
33
|
+
cacheWriteTokens: 0,
|
|
34
|
+
totalTokens: 0,
|
|
35
|
+
});
|
|
36
|
+
expect(report.cacheHitRate).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("sums deterministic fixed-fixture raw token buckets without folding cache into input", () => {
|
|
40
|
+
const logs: TaskTokenLog[] = [
|
|
41
|
+
{
|
|
42
|
+
subagentId: "root",
|
|
43
|
+
agent: "main",
|
|
44
|
+
turn: 1,
|
|
45
|
+
at: "2026-01-01T00:00:00.000Z",
|
|
46
|
+
input: 100,
|
|
47
|
+
output: 20,
|
|
48
|
+
cacheRead: 40,
|
|
49
|
+
cacheWrite: 10,
|
|
50
|
+
totalTokens: 170,
|
|
51
|
+
model: "fixture-model",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
subagentId: "1-executor",
|
|
55
|
+
agent: "executor",
|
|
56
|
+
turn: 1,
|
|
57
|
+
at: "2026-01-01T00:00:01.000Z",
|
|
58
|
+
input: 60,
|
|
59
|
+
output: 15,
|
|
60
|
+
cacheRead: 20,
|
|
61
|
+
cacheWrite: 5,
|
|
62
|
+
totalTokens: 100,
|
|
63
|
+
model: "fixture-model",
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
const report = buildFixtureReport("fixed-fixture", logs);
|
|
67
|
+
const inputTokens = logs.reduce((sum, log) => sum + log.input, 0);
|
|
68
|
+
const outputTokens = logs.reduce((sum, log) => sum + log.output, 0);
|
|
69
|
+
const cacheReadTokens = logs.reduce((sum, log) => sum + log.cacheRead, 0);
|
|
70
|
+
const cacheWriteTokens = logs.reduce((sum, log) => sum + log.cacheWrite, 0);
|
|
71
|
+
const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
|
|
72
|
+
|
|
73
|
+
expectParseReportCompatible(report, "fixed-fixture");
|
|
74
|
+
expect(report.totals).toEqual({
|
|
75
|
+
turns: logs.length,
|
|
76
|
+
inputTokens,
|
|
77
|
+
outputTokens,
|
|
78
|
+
cacheReadTokens,
|
|
79
|
+
cacheWriteTokens,
|
|
80
|
+
totalTokens,
|
|
81
|
+
});
|
|
82
|
+
expect(report.cacheHitRate).toBe(cacheReadTokens / (inputTokens + cacheReadTokens));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("emits only parseReport-compatible schema-v1 fields", () => {
|
|
86
|
+
const report = buildFixtureReport("shape-fixture", [
|
|
87
|
+
{
|
|
88
|
+
subagentId: "root",
|
|
89
|
+
turn: 1,
|
|
90
|
+
at: "2026-01-01T00:00:00.000Z",
|
|
91
|
+
input: 0,
|
|
92
|
+
output: 0,
|
|
93
|
+
cacheRead: 1,
|
|
94
|
+
cacheWrite: 0,
|
|
95
|
+
totalTokens: 1,
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
expect(Object.keys(report).sort()).toEqual([
|
|
100
|
+
"binaryId",
|
|
101
|
+
"cacheHitRate",
|
|
102
|
+
"fixtureId",
|
|
103
|
+
"receiptArtifactRatio",
|
|
104
|
+
"roi",
|
|
105
|
+
"schemaVersion",
|
|
106
|
+
"spawnDecisions",
|
|
107
|
+
"totals",
|
|
108
|
+
]);
|
|
109
|
+
expect(Object.keys(report.totals).sort()).toEqual([
|
|
110
|
+
"cacheReadTokens",
|
|
111
|
+
"cacheWriteTokens",
|
|
112
|
+
"inputTokens",
|
|
113
|
+
"outputTokens",
|
|
114
|
+
"totalTokens",
|
|
115
|
+
"turns",
|
|
116
|
+
]);
|
|
117
|
+
expectParseReportCompatible(report, "shape-fixture");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
async function captureRun(fixtureId: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
122
|
+
const originalCwd = process.cwd();
|
|
123
|
+
const originalStdout = process.stdout.write.bind(process.stdout);
|
|
124
|
+
const originalStderr = process.stderr.write.bind(process.stderr);
|
|
125
|
+
let exitCode = 0;
|
|
126
|
+
let stdout = "";
|
|
127
|
+
let stderr = "";
|
|
128
|
+
process.stdout.write = ((chunk: string | Uint8Array): boolean => {
|
|
129
|
+
stdout += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8");
|
|
130
|
+
return true;
|
|
131
|
+
}) as typeof process.stdout.write;
|
|
132
|
+
process.stderr.write = ((chunk: string | Uint8Array): boolean => {
|
|
133
|
+
stderr += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8");
|
|
134
|
+
return true;
|
|
135
|
+
}) as typeof process.stderr.write;
|
|
136
|
+
try {
|
|
137
|
+
exitCode = await runFixtureReport(fixtureId);
|
|
138
|
+
return { stdout, stderr, exitCode };
|
|
139
|
+
} finally {
|
|
140
|
+
process.stdout.write = originalStdout;
|
|
141
|
+
process.stderr.write = originalStderr;
|
|
142
|
+
process.chdir(originalCwd);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe("runFixtureReport CLI degradation", () => {
|
|
147
|
+
it("exits non-zero without emitting JSON for an unknown fixture id", async () => {
|
|
148
|
+
await withTempCwd(async () => {
|
|
149
|
+
const { stdout, stderr, exitCode } = await captureRun("definitely-not-a-real-session-id");
|
|
150
|
+
expect(exitCode).toBe(1);
|
|
151
|
+
expect(stdout).toBe("");
|
|
152
|
+
expect(stderr).toContain("unknown fixture id");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("reads only the requested session token-log directory", async () => {
|
|
157
|
+
await withTempCwd(async () => {
|
|
158
|
+
const firstSession = "fixture-session-a";
|
|
159
|
+
const secondSession = "fixture-session-b";
|
|
160
|
+
await writeTokenLog(firstSession, { input: 100, output: 10, cacheRead: 0, cacheWrite: 0, totalTokens: 110 });
|
|
161
|
+
await writeTokenLog(secondSession, { input: 900, output: 90, cacheRead: 0, cacheWrite: 0, totalTokens: 990 });
|
|
162
|
+
|
|
163
|
+
const { stdout, exitCode } = await captureRun(firstSession);
|
|
164
|
+
const report = JSON.parse(stdout) as LiveRunReportShape;
|
|
165
|
+
|
|
166
|
+
expect(exitCode).toBe(0);
|
|
167
|
+
expect(report.fixtureId).toBe(firstSession);
|
|
168
|
+
expect(report.totals.totalTokens).toBe(110);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("emits deterministic PR9 default-candidate fixture reports", async () => {
|
|
173
|
+
await withTempCwd(async () => {
|
|
174
|
+
const { stdout, exitCode } = await captureRun("pr9.read-artifact-spill-threshold.after");
|
|
175
|
+
const report = JSON.parse(stdout) as LiveRunReportShape;
|
|
176
|
+
|
|
177
|
+
expect(exitCode).toBe(0);
|
|
178
|
+
expectParseReportCompatible(report, "pr9.read-artifact-spill-threshold.after");
|
|
179
|
+
expect(report.totals.totalTokens).toBe(20_100);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("exits non-zero without emitting JSON for a corrupt token-log", async () => {
|
|
184
|
+
await withTempCwd(async () => {
|
|
185
|
+
const id = "corrupt-session";
|
|
186
|
+
const dir = join(sessionRoot(process.cwd(), id), "token-logs");
|
|
187
|
+
await mkdir(dir, { recursive: true });
|
|
188
|
+
await writeFile(join(dir, "token-log.jsonl"), "not json\n{}\n", "utf-8");
|
|
189
|
+
const { stdout, stderr, exitCode } = await captureRun(id);
|
|
190
|
+
expect(exitCode).toBe(1);
|
|
191
|
+
expect(stdout).toBe("");
|
|
192
|
+
expect(stderr).toContain("failed to build fixture report");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
async function withTempCwd(fn: (cwd: string) => Promise<void>): Promise<void> {
|
|
198
|
+
const originalCwd = process.cwd();
|
|
199
|
+
const dir = await mkdtemp(join(tmpdir(), "skc-fixture-report-"));
|
|
200
|
+
process.chdir(dir);
|
|
201
|
+
try {
|
|
202
|
+
await fn(dir);
|
|
203
|
+
} finally {
|
|
204
|
+
// Restore the real cwd BEFORE removing the temp dir, otherwise the process
|
|
205
|
+
// is left sitting in a deleted directory and later tests in the same
|
|
206
|
+
// process fail with ENOENT on relative-path/process.cwd() operations.
|
|
207
|
+
process.chdir(originalCwd);
|
|
208
|
+
await rm(dir, { recursive: true, force: true });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function writeTokenLog(
|
|
213
|
+
sessionId: string,
|
|
214
|
+
metrics: { input: number; output: number; cacheRead: number; cacheWrite: number; totalTokens: number },
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
const dir = join(sessionRoot(process.cwd(), sessionId), "token-logs");
|
|
217
|
+
await mkdir(dir, { recursive: true });
|
|
218
|
+
await writeFile(
|
|
219
|
+
join(dir, "token-log.jsonl"),
|
|
220
|
+
`${JSON.stringify({
|
|
221
|
+
subagentId: "root",
|
|
222
|
+
agent: "main",
|
|
223
|
+
turn: 1,
|
|
224
|
+
at: "2026-01-01T00:00:00.000Z",
|
|
225
|
+
model: "fixture-model",
|
|
226
|
+
...metrics,
|
|
227
|
+
})}\n`,
|
|
228
|
+
"utf-8",
|
|
229
|
+
);
|
|
230
|
+
}
|