@sayknow-cli/coding-agent 0.2.4 → 0.2.5
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/cli/migrate-cli.d.ts +20 -0
- package/dist/types/cli/update-cli.d.ts +3 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +27 -0
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/lsp/startup-events.d.ts +1 -0
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/skc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/skc-runtime/session-layout.d.ts +59 -0
- package/dist/types/skc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/skc-runtime/state-graph.d.ts +1 -1
- package/dist/types/skc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/skc-runtime/state-schema.d.ts +2 -0
- package/dist/types/skc-runtime/state-writer.d.ts +36 -7
- package/dist/types/skc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +7 -4
- package/dist/types/skc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/skc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +5 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/package.json +7 -7
- package/scripts/build-binary.ts +0 -7
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/setup-cli.ts +14 -1
- package/src/cli/update-cli.ts +53 -3
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/launch.ts +1 -1
- package/src/commands/migrate.ts +46 -0
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-registry.ts +9 -2
- package/src/config/model-resolver.ts +13 -2
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +17 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +30 -24
- package/src/defaults/skc/skills/ralplan/SKILL.md +10 -4
- package/src/defaults/skc/skills/team/SKILL.md +51 -47
- package/src/defaults/skc/skills/ultragoal/SKILL.md +17 -13
- package/src/exec/bash-executor.ts +3 -1
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/skc-plugins/injection.ts +23 -4
- package/src/extensibility/skc-plugins/state.ts +16 -1
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +11 -9
- package/src/lsp/startup-events.ts +24 -0
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/welcome.ts +20 -9
- package/src/modes/controllers/input-controller.ts +21 -3
- package/src/modes/interactive-mode.ts +27 -19
- package/src/modes/prompt-action-autocomplete.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +7 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/session/agent-session.ts +43 -41
- package/src/session/session-manager.ts +19 -2
- package/src/setup/hermes/templates/operator-instructions.v1.md +8 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/skc-runtime/deep-interview-recorder.ts +51 -18
- package/src/skc-runtime/deep-interview-runtime.ts +49 -23
- package/src/skc-runtime/goal-mode-request.ts +26 -11
- package/src/skc-runtime/launch-tmux.ts +68 -15
- package/src/skc-runtime/ralplan-runtime.ts +79 -50
- package/src/skc-runtime/session-layout.ts +180 -0
- package/src/skc-runtime/session-resolution.ts +217 -0
- package/src/skc-runtime/state-graph.ts +1 -2
- package/src/skc-runtime/state-migrations.ts +1 -0
- package/src/skc-runtime/state-runtime.ts +237 -114
- package/src/skc-runtime/state-schema.ts +2 -0
- package/src/skc-runtime/state-writer.ts +310 -42
- package/src/skc-runtime/team-runtime.ts +43 -19
- package/src/skc-runtime/tmux-sessions.ts +43 -2
- package/src/skc-runtime/ultragoal-guard.ts +45 -2
- package/src/skc-runtime/ultragoal-runtime.ts +121 -41
- package/src/skc-runtime/workflow-command-ref.ts +1 -2
- package/src/skc-runtime/workflow-manifest.ts +1 -2
- package/src/skill-state/active-state.ts +116 -129
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +238 -111
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +8 -4
- package/src/system-prompt.ts +11 -9
- package/src/task/agents.ts +1 -22
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +34 -12
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/computer.ts +58 -4
- package/src/utils/edit-mode.ts +1 -1
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a skill from another agent into a native SKC `SKILL.md`.
|
|
3
|
+
*
|
|
4
|
+
* SKC derives a skill's loaded name from its directory (`<slug>/SKILL.md`) when no
|
|
5
|
+
* frontmatter `name` is present, and requires a `description`. To guarantee the
|
|
6
|
+
* effective loaded name equals the lowercase-hyphen slug, we drop any frontmatter
|
|
7
|
+
* `name` and place the file at `<slug>/SKILL.md`, synthesizing a `description`
|
|
8
|
+
* when the source lacks one.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parseFrontmatter } from "@sayknow-cli/utils";
|
|
12
|
+
import { YAML } from "bun";
|
|
13
|
+
|
|
14
|
+
export interface NormalizeSkillInput {
|
|
15
|
+
/** Raw name from the source (filename stem, frontmatter name, etc.). */
|
|
16
|
+
rawName: string;
|
|
17
|
+
/** Full source markdown (may or may not have frontmatter). */
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NormalizedSkill {
|
|
22
|
+
slug: string;
|
|
23
|
+
content: string;
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Convert an arbitrary name into a lowercase-hyphen slug. */
|
|
28
|
+
export function slugify(name: string): string {
|
|
29
|
+
const slug = name
|
|
30
|
+
.normalize("NFKD")
|
|
31
|
+
.replace(/[^\w\s-]/g, "")
|
|
32
|
+
.trim()
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[\s_]+/g, "-")
|
|
35
|
+
.replace(/-+/g, "-")
|
|
36
|
+
.replace(/^-+|-+$/g, "");
|
|
37
|
+
return slug;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function firstNonEmptyLine(body: string): string | undefined {
|
|
41
|
+
for (const raw of body.split("\n")) {
|
|
42
|
+
const line = raw.replace(/^#+\s*/, "").trim();
|
|
43
|
+
if (line) return line;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Produce a `{ slug, content }` pair whose effective SKC-loaded name equals `slug`.
|
|
50
|
+
* Throws only on an unusable name (cannot produce a slug).
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeSkill(input: NormalizeSkillInput): NormalizedSkill {
|
|
53
|
+
const warnings: string[] = [];
|
|
54
|
+
const { frontmatter, body } = parseFrontmatter(input.content, { level: "off" });
|
|
55
|
+
|
|
56
|
+
const sourceName =
|
|
57
|
+
typeof frontmatter.name === "string" && frontmatter.name.trim() ? frontmatter.name : input.rawName;
|
|
58
|
+
const slug = slugify(sourceName);
|
|
59
|
+
if (!slug) {
|
|
60
|
+
throw new Error(`cannot derive a valid slug from skill name "${input.rawName}"`);
|
|
61
|
+
}
|
|
62
|
+
if (slugify(input.rawName) !== slug && typeof frontmatter.name === "string") {
|
|
63
|
+
warnings.push(`renamed skill "${input.rawName}" to slug "${slug}"`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build the destination frontmatter: drop `name` (loaded name comes from the dir),
|
|
67
|
+
// keep other fields, and ensure a non-empty description.
|
|
68
|
+
const { name: _droppedName, description: rawDescription, ...rest } = frontmatter;
|
|
69
|
+
let description = typeof rawDescription === "string" ? rawDescription.trim() : "";
|
|
70
|
+
if (!description) {
|
|
71
|
+
description = firstNonEmptyLine(body) ?? `Imported ${slug} skill.`;
|
|
72
|
+
warnings.push(`synthesized description for skill "${slug}"`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const fm: Record<string, unknown> = { description, ...rest };
|
|
76
|
+
const yaml = YAML.stringify(fm).trimEnd();
|
|
77
|
+
const content = `---\n${yaml}\n---\n\n${body.trim()}\n`;
|
|
78
|
+
|
|
79
|
+
return { slug, content, warnings };
|
|
80
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for `skc migrate`.
|
|
3
|
+
*
|
|
4
|
+
* Imports MCP servers and skills from other coding agents (Claude Code, Codex,
|
|
5
|
+
* OpenCode) into native SKC config. See the consensus plan under
|
|
6
|
+
* `.skc/plans/ralplan/` for the full taxonomy and force/collision semantics.
|
|
7
|
+
*/
|
|
8
|
+
import type { MCPServerConfig } from "../runtime-mcp/types";
|
|
9
|
+
|
|
10
|
+
/** Supported migration sources. */
|
|
11
|
+
export type MigrateSource = "claude-code" | "codex" | "opencode";
|
|
12
|
+
|
|
13
|
+
export const MIGRATE_SOURCES: readonly MigrateSource[] = ["claude-code", "codex", "opencode"];
|
|
14
|
+
|
|
15
|
+
/** Canonical, deterministic ordering used when expanding `--from all` / repeated `--from`. */
|
|
16
|
+
export const CANONICAL_SOURCE_ORDER: readonly MigrateSource[] = MIGRATE_SOURCES;
|
|
17
|
+
|
|
18
|
+
/** What kind of thing an action/coverage row is about. */
|
|
19
|
+
export type MigrateItemType = "mcp" | "skill" | "source";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-item outcome taxonomy.
|
|
23
|
+
*
|
|
24
|
+
* `skipped_*` outcomes are non-fatal (exit 0). Any `failed_*` outcome sets
|
|
25
|
+
* `ok=false` and a non-zero process exit code.
|
|
26
|
+
*/
|
|
27
|
+
export type MigrationStatus =
|
|
28
|
+
| "imported"
|
|
29
|
+
| "updated"
|
|
30
|
+
| "skipped_exists"
|
|
31
|
+
| "skipped_absent_source"
|
|
32
|
+
| "skipped_unmappable"
|
|
33
|
+
| "failed_invalid_source"
|
|
34
|
+
| "failed_invalid_destination"
|
|
35
|
+
| "failed_io";
|
|
36
|
+
|
|
37
|
+
export const MIGRATION_STATUSES: readonly MigrationStatus[] = [
|
|
38
|
+
"imported",
|
|
39
|
+
"updated",
|
|
40
|
+
"skipped_exists",
|
|
41
|
+
"skipped_absent_source",
|
|
42
|
+
"skipped_unmappable",
|
|
43
|
+
"failed_invalid_source",
|
|
44
|
+
"failed_invalid_destination",
|
|
45
|
+
"failed_io",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** Statuses that represent a hard failure (drive `ok=false` + non-zero exit). */
|
|
49
|
+
export const FAILURE_STATUSES: ReadonlySet<MigrationStatus> = new Set<MigrationStatus>([
|
|
50
|
+
"failed_invalid_source",
|
|
51
|
+
"failed_invalid_destination",
|
|
52
|
+
"failed_io",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
export function isFailureStatus(status: MigrationStatus): boolean {
|
|
56
|
+
return FAILURE_STATUSES.has(status);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Operation the planner decided for an item. */
|
|
60
|
+
export type MigrateOperation = "create" | "update" | "skip" | "fail";
|
|
61
|
+
|
|
62
|
+
/** A raw MCP server candidate parsed from a source, before mapping/destination planning. */
|
|
63
|
+
export interface McpCandidate {
|
|
64
|
+
source: MigrateSource;
|
|
65
|
+
name: string;
|
|
66
|
+
/** The raw, unmapped server entry from the source config (mapped by the planner). */
|
|
67
|
+
raw: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A raw skill candidate parsed from a source, before normalization/destination planning. */
|
|
71
|
+
export interface SkillCandidate {
|
|
72
|
+
source: MigrateSource;
|
|
73
|
+
/** Slug used as the destination directory and effective loaded name. */
|
|
74
|
+
slug: string;
|
|
75
|
+
/** Full SKILL.md content (frontmatter already normalized so loaded name == slug). */
|
|
76
|
+
content: string;
|
|
77
|
+
warnings: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Source-level diagnostic for a single source/type pair (e.g. "codex mcp config
|
|
82
|
+
* was malformed"). Distinct from per-item actions so absent/unreadable sources
|
|
83
|
+
* are reported once instead of per item.
|
|
84
|
+
*/
|
|
85
|
+
export interface SourceDiagnostic {
|
|
86
|
+
source: MigrateSource;
|
|
87
|
+
type: Exclude<MigrateItemType, "source"> | "source";
|
|
88
|
+
status: Extract<MigrationStatus, "skipped_absent_source" | "failed_invalid_source" | "failed_io">;
|
|
89
|
+
message: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Normalized candidates + diagnostics returned by an adapter. */
|
|
93
|
+
export interface AdapterResult {
|
|
94
|
+
mcpCandidates: McpCandidate[];
|
|
95
|
+
skillCandidates: SkillCandidate[];
|
|
96
|
+
diagnostics: SourceDiagnostic[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A single planned action consumed identically by dry-run and live execution. */
|
|
100
|
+
export interface MigrateAction {
|
|
101
|
+
source: MigrateSource;
|
|
102
|
+
type: MigrateItemType;
|
|
103
|
+
name?: string;
|
|
104
|
+
/** For skills: the effective SKC-loaded name (== slug). */
|
|
105
|
+
effectiveName?: string;
|
|
106
|
+
/** Absolute destination path (mcp.json for MCP, <skillsDir>/<slug>/SKILL.md for skills). */
|
|
107
|
+
destination?: string;
|
|
108
|
+
operation: MigrateOperation;
|
|
109
|
+
status: MigrationStatus;
|
|
110
|
+
reason?: string;
|
|
111
|
+
warnings?: string[];
|
|
112
|
+
/** Resolved payload the executor needs; never serialized to the report. */
|
|
113
|
+
mcp?: { config: MCPServerConfig; force: boolean };
|
|
114
|
+
skill?: { content: string };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface MigrateWarning {
|
|
118
|
+
source: MigrateSource;
|
|
119
|
+
type: string;
|
|
120
|
+
name?: string;
|
|
121
|
+
message: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type StatusCounts = Record<MigrationStatus, number>;
|
|
125
|
+
|
|
126
|
+
export interface MigrateDestinations {
|
|
127
|
+
mcpConfigPath: string;
|
|
128
|
+
skillsDir: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The full machine-readable report emitted with `--json`. */
|
|
132
|
+
export interface MigrateReport {
|
|
133
|
+
ok: boolean;
|
|
134
|
+
dryRun: boolean;
|
|
135
|
+
project: boolean;
|
|
136
|
+
force: boolean;
|
|
137
|
+
sources: MigrateSource[];
|
|
138
|
+
destinations: MigrateDestinations;
|
|
139
|
+
summary: {
|
|
140
|
+
total: StatusCounts;
|
|
141
|
+
byType: { mcp: StatusCounts; skill: StatusCounts; source: StatusCounts };
|
|
142
|
+
bySource: Record<MigrateSource, StatusCounts>;
|
|
143
|
+
};
|
|
144
|
+
actions: Array<{
|
|
145
|
+
source: MigrateSource;
|
|
146
|
+
type: MigrateItemType;
|
|
147
|
+
name?: string;
|
|
148
|
+
effectiveName?: string;
|
|
149
|
+
destination?: string;
|
|
150
|
+
operation: MigrateOperation;
|
|
151
|
+
status: MigrationStatus;
|
|
152
|
+
reason?: string;
|
|
153
|
+
warnings?: string[];
|
|
154
|
+
}>;
|
|
155
|
+
warnings: MigrateWarning[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Create a zeroed status-count record. */
|
|
159
|
+
export function emptyStatusCounts(): StatusCounts {
|
|
160
|
+
const counts = {} as StatusCounts;
|
|
161
|
+
for (const status of MIGRATION_STATUSES) counts[status] = 0;
|
|
162
|
+
return counts;
|
|
163
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import type { ExtensionUIContext } from "../../extensibility/extensions";
|
|
3
2
|
import type { AgentSession } from "../../session/agent-session";
|
|
4
3
|
import type { ClientBridgePermissionOutcome } from "../../session/client-bridge";
|
|
4
|
+
import { workflowGatePath } from "../../skc-runtime/session-layout";
|
|
5
5
|
import type { RpcCommand, RpcResponse, RpcWorkflowGateResponse } from "../rpc/rpc-types";
|
|
6
6
|
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
7
7
|
import { isRpcCommand } from "../shared/agent-wire/command-validation";
|
|
@@ -611,7 +611,7 @@ export async function runBridgeMode(
|
|
|
611
611
|
});
|
|
612
612
|
};
|
|
613
613
|
const gateStore = new FileGateStore(
|
|
614
|
-
|
|
614
|
+
workflowGatePath(session.sessionManager.getCwd(), session.sessionId, session.sessionId),
|
|
615
615
|
);
|
|
616
616
|
const unattendedControlPlane = new UnattendedSessionControlPlane({
|
|
617
617
|
runId: session.sessionId,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Editor, type KeyId, matchesKey, parseKittySequence } from "@sayknow-cli/tui";
|
|
2
2
|
import { BracketedPasteHandler } from "@sayknow-cli/tui/bracketed-paste";
|
|
3
|
-
import type
|
|
3
|
+
import { type AppKeybinding, KEYBINDINGS } from "../../config/keybindings";
|
|
4
4
|
|
|
5
5
|
type ConfigurableEditorAction = Extract<
|
|
6
6
|
AppKeybinding,
|
|
@@ -23,25 +23,35 @@ type ConfigurableEditorAction = Extract<
|
|
|
23
23
|
| "app.clipboard.copyPrompt"
|
|
24
24
|
>;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"app.
|
|
31
|
-
"app.
|
|
32
|
-
"app.
|
|
33
|
-
"app.
|
|
34
|
-
"app.
|
|
35
|
-
"app.model.
|
|
36
|
-
"app.
|
|
37
|
-
"app.
|
|
38
|
-
"app.
|
|
39
|
-
"app.
|
|
40
|
-
"app.
|
|
41
|
-
"app.
|
|
42
|
-
"app.
|
|
43
|
-
"app.
|
|
44
|
-
|
|
26
|
+
// Editor-configurable app actions. Defaults are derived from the central
|
|
27
|
+
// KEYBINDINGS registry so there is a single source of truth (e.g. the
|
|
28
|
+
// platform-aware app.clipboard.pasteImage default is not duplicated here).
|
|
29
|
+
const CONFIGURABLE_EDITOR_ACTIONS = [
|
|
30
|
+
"app.interrupt",
|
|
31
|
+
"app.clear",
|
|
32
|
+
"app.exit",
|
|
33
|
+
"app.suspend",
|
|
34
|
+
"app.thinking.cycle",
|
|
35
|
+
"app.model.cycleForward",
|
|
36
|
+
"app.model.cycleBackward",
|
|
37
|
+
"app.model.select",
|
|
38
|
+
"app.model.selectTemporary",
|
|
39
|
+
"app.tools.expand",
|
|
40
|
+
"app.thinking.toggle",
|
|
41
|
+
"app.editor.external",
|
|
42
|
+
"app.history.search",
|
|
43
|
+
"app.message.queue",
|
|
44
|
+
"app.message.dequeue",
|
|
45
|
+
"app.clipboard.pasteImage",
|
|
46
|
+
"app.clipboard.copyPrompt",
|
|
47
|
+
] as const satisfies readonly ConfigurableEditorAction[];
|
|
48
|
+
|
|
49
|
+
const DEFAULT_ACTION_KEYS = Object.fromEntries(
|
|
50
|
+
CONFIGURABLE_EDITOR_ACTIONS.map(action => {
|
|
51
|
+
const defaultKeys = KEYBINDINGS[action].defaultKeys;
|
|
52
|
+
return [action, Array.isArray(defaultKeys) ? [...defaultKeys] : [defaultKeys]];
|
|
53
|
+
}),
|
|
54
|
+
) as Record<ConfigurableEditorAction, KeyId[]>;
|
|
45
55
|
|
|
46
56
|
const PASTE_DECISION_TIMEOUT_MS = 5_000;
|
|
47
57
|
const PENDING_PASTE_INPUT_MAX = 64;
|
|
@@ -14,6 +14,8 @@ export interface LspServerInfo {
|
|
|
14
14
|
fileTypes: string[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type WelcomeLogoMode = "unicode" | "square" | "ascii";
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* Sayknow-CLI launch surface: a blue-gradient SAYKNOW wordmark, compact
|
|
19
21
|
* command affordances, and project signals — a distinct identity, not a
|
|
@@ -29,6 +31,7 @@ export class WelcomeComponent implements Component {
|
|
|
29
31
|
private providerName: string,
|
|
30
32
|
private recentSessions: RecentSession[] = [],
|
|
31
33
|
private lspServers: LspServerInfo[] = [],
|
|
34
|
+
private readonly logoMode: WelcomeLogoMode = "unicode",
|
|
32
35
|
) {}
|
|
33
36
|
|
|
34
37
|
invalidate(): void {}
|
|
@@ -85,7 +88,8 @@ export class WelcomeComponent implements Component {
|
|
|
85
88
|
const minRightCol = 20;
|
|
86
89
|
const modelPill = this.#pill(theme.icon.model || "model", this.modelName, "statusLineModel");
|
|
87
90
|
const providerPill = this.#pill(theme.icon.package || "provider", this.providerName, "statusLinePath");
|
|
88
|
-
const
|
|
91
|
+
const logoLines = this.#logoLines();
|
|
92
|
+
const logoMinWidth = Math.max(...logoLines.map(line => visibleWidth(line)));
|
|
89
93
|
const leftMinContentWidth = Math.max(
|
|
90
94
|
minLeftCol,
|
|
91
95
|
logoMinWidth,
|
|
@@ -103,8 +107,7 @@ export class WelcomeComponent implements Component {
|
|
|
103
107
|
const leftCol = showRightColumn ? dualLeftCol : boxWidth - 2;
|
|
104
108
|
const rightCol = showRightColumn ? dualRightCol : 0;
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
const logoColored = this.#currentLogoFrame();
|
|
110
|
+
const logoColored = this.#currentLogoFrame(logoLines);
|
|
108
111
|
|
|
109
112
|
// When no model is resolved yet, guide the user instead of showing "Unknown".
|
|
110
113
|
const hasModel = this.modelName !== "Unknown" && this.modelName.length > 0;
|
|
@@ -281,10 +284,10 @@ export class WelcomeComponent implements Component {
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
/** Pick the logo frame for the current intro phase, or the resting frame. */
|
|
284
|
-
#currentLogoFrame(): readonly string[] {
|
|
285
|
-
if (this.#animStart == null) return
|
|
287
|
+
#currentLogoFrame(logoLines: readonly string[]): readonly string[] {
|
|
288
|
+
if (this.#animStart == null) return REST_FRAMES[this.logoMode];
|
|
286
289
|
const elapsed = performance.now() - this.#animStart;
|
|
287
|
-
if (elapsed >= INTRO_MS) return
|
|
290
|
+
if (elapsed >= INTRO_MS) return REST_FRAMES[this.logoMode];
|
|
288
291
|
// Ease-out cubic so the spin decelerates into the resting state.
|
|
289
292
|
const progress = elapsed / INTRO_MS;
|
|
290
293
|
const eased = 1 - (1 - progress) ** 3;
|
|
@@ -296,7 +299,11 @@ export class WelcomeComponent implements Component {
|
|
|
296
299
|
// the same ease-out curve so the highlight is gone by the resting frame.
|
|
297
300
|
const shinePos = (((progress * INTRO_SHINE_TRAVERSALS) % 1) + 1) % 1;
|
|
298
301
|
const shineStrength = (1 - eased) ** 1.5;
|
|
299
|
-
return gradientLogo(
|
|
302
|
+
return gradientLogo(logoLines, phase, { strength: shineStrength, pos: shinePos });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#logoLines(): readonly string[] {
|
|
306
|
+
return BRAND_LOGO;
|
|
300
307
|
}
|
|
301
308
|
}
|
|
302
309
|
|
|
@@ -405,5 +412,9 @@ const INTRO_SWEEPS = 2.5;
|
|
|
405
412
|
/** Number of times the shine highlight crosses the diagonal across the intro. */
|
|
406
413
|
const INTRO_SHINE_TRAVERSALS = 3;
|
|
407
414
|
|
|
408
|
-
/** Resting gradient
|
|
409
|
-
const
|
|
415
|
+
/** Resting gradient frames, cached for re-renders outside of the intro. */
|
|
416
|
+
const REST_FRAMES: Record<WelcomeLogoMode, readonly string[]> = {
|
|
417
|
+
unicode: gradientLogo(BRAND_LOGO, 0),
|
|
418
|
+
square: gradientLogo(BRAND_LOGO, 0),
|
|
419
|
+
ascii: gradientLogo(BRAND_LOGO, 0),
|
|
420
|
+
};
|
|
@@ -199,6 +199,9 @@ export class InputController {
|
|
|
199
199
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
200
200
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
201
201
|
this.ctx.editor.onQueue = () => void this.handleQueueSubmit();
|
|
202
|
+
this.ctx.editor.onTabDeclined = () => {
|
|
203
|
+
if (this.ctx.session.isStreaming) void this.handleQueueSubmit();
|
|
204
|
+
};
|
|
202
205
|
|
|
203
206
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
204
207
|
// Wire up extension shortcuts
|
|
@@ -228,7 +231,11 @@ export class InputController {
|
|
|
228
231
|
});
|
|
229
232
|
}
|
|
230
233
|
for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
|
|
231
|
-
this.ctx.editor.setCustomKeyHandler(key, () =>
|
|
234
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
235
|
+
if (!this.#isFollowUpShortcutActive()) return false;
|
|
236
|
+
void this.handleFollowUp();
|
|
237
|
+
return true;
|
|
238
|
+
});
|
|
232
239
|
}
|
|
233
240
|
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
234
241
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
@@ -503,6 +510,15 @@ export class InputController {
|
|
|
503
510
|
return this.ctx.settings.get("busyPromptMode") === "steer" ? "steer" : "followUp";
|
|
504
511
|
}
|
|
505
512
|
|
|
513
|
+
#isFollowUpShortcutActive(): boolean {
|
|
514
|
+
return (
|
|
515
|
+
this.ctx.session.isStreaming ||
|
|
516
|
+
this.ctx.session.isCompacting ||
|
|
517
|
+
this.ctx.session.isBashRunning ||
|
|
518
|
+
this.ctx.session.isEvalRunning
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
506
522
|
/**
|
|
507
523
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
508
524
|
* using the supplied `streamingBehavior`. Returns true if the text was a
|
|
@@ -824,8 +840,9 @@ export class InputController {
|
|
|
824
840
|
this.ctx.ui.requestRender();
|
|
825
841
|
return true;
|
|
826
842
|
}
|
|
827
|
-
|
|
828
|
-
|
|
843
|
+
this.ctx.showStatus(
|
|
844
|
+
"No image in clipboard. Use #paste-image, paste a copied image, or attach an image file with @path/to/image.png.",
|
|
845
|
+
);
|
|
829
846
|
return false;
|
|
830
847
|
} catch {
|
|
831
848
|
this.ctx.showStatus("Failed to read clipboard");
|
|
@@ -840,6 +857,7 @@ export class InputController {
|
|
|
840
857
|
keybindings: this.ctx.keybindings,
|
|
841
858
|
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
842
859
|
copyPrompt: () => this.handleCopyPrompt(),
|
|
860
|
+
pasteImage: () => void this.handleImagePaste(),
|
|
843
861
|
undo: prefix => this.ctx.editor.undoPastTransientText(prefix),
|
|
844
862
|
moveCursorToMessageEnd: () => this.ctx.editor.moveToMessageEnd(),
|
|
845
863
|
moveCursorToMessageStart: () => this.ctx.editor.moveToMessageStart(),
|
|
@@ -44,7 +44,7 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
|
|
|
44
44
|
import { type Goal, type GoalModeState, normalizeGoal } from "../goals/state";
|
|
45
45
|
import { t } from "../i18n";
|
|
46
46
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
47
|
-
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
47
|
+
import { getLspStartupWarningMessage, LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
48
48
|
import {
|
|
49
49
|
humanizePlanTitle,
|
|
50
50
|
type PlanApprovalDetails,
|
|
@@ -72,6 +72,7 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
72
72
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
73
73
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
74
74
|
import { ToolError } from "../tools/tool-errors";
|
|
75
|
+
|
|
75
76
|
import type { EventBus } from "../utils/event-bus";
|
|
76
77
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
77
78
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
@@ -86,7 +87,11 @@ import type { HookInputComponent } from "./components/hook-input";
|
|
|
86
87
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
87
88
|
import { StatusLineComponent } from "./components/status-line";
|
|
88
89
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
89
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
WelcomeComponent,
|
|
92
|
+
type WelcomeLogoMode,
|
|
93
|
+
type LspServerInfo as WelcomeLspServerInfo,
|
|
94
|
+
} from "./components/welcome";
|
|
90
95
|
import { BtwController } from "./controllers/btw-controller";
|
|
91
96
|
import { CommandController } from "./controllers/command-controller";
|
|
92
97
|
import { EventController } from "./controllers/event-controller";
|
|
@@ -218,6 +223,21 @@ function parseGoalSubcommand(args: string): { sub: GoalSubcommand | undefined; r
|
|
|
218
223
|
return { sub: undefined, rest: trimmed };
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
export type WelcomeBannerSettingMode = "auto" | "unicode" | "square" | "ascii";
|
|
227
|
+
|
|
228
|
+
export function resolveWelcomeLogoMode(
|
|
229
|
+
mode: WelcomeBannerSettingMode,
|
|
230
|
+
env: Record<string, string | undefined> = Bun.env,
|
|
231
|
+
platform: NodeJS.Platform = process.platform,
|
|
232
|
+
): WelcomeLogoMode {
|
|
233
|
+
void env;
|
|
234
|
+
void platform;
|
|
235
|
+
if (mode === "unicode") return "unicode";
|
|
236
|
+
if (mode === "square") return "square";
|
|
237
|
+
if (mode === "ascii") return "ascii";
|
|
238
|
+
return "unicode";
|
|
239
|
+
}
|
|
240
|
+
|
|
221
241
|
/** Options for creating an InteractiveMode instance (for future API use) */
|
|
222
242
|
export interface InteractiveModeOptions {
|
|
223
243
|
/** Providers that were migrated during startup */
|
|
@@ -479,6 +499,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
479
499
|
);
|
|
480
500
|
|
|
481
501
|
const startupQuiet = settings.get("startup.quiet");
|
|
502
|
+
const welcomeLogoMode = resolveWelcomeLogoMode(settings.get("startup.welcomeBannerMode"));
|
|
482
503
|
this.#welcomeComponent = undefined;
|
|
483
504
|
|
|
484
505
|
for (const warning of this.session.configWarnings) {
|
|
@@ -494,6 +515,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
494
515
|
providerName,
|
|
495
516
|
recentSessions,
|
|
496
517
|
this.#getWelcomeLspServers(),
|
|
518
|
+
welcomeLogoMode,
|
|
497
519
|
);
|
|
498
520
|
|
|
499
521
|
// Setup UI layout
|
|
@@ -2062,23 +2084,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2062
2084
|
#handleLspStartupEvent(event: LspStartupEvent): void {
|
|
2063
2085
|
this.#updateWelcomeLspServers();
|
|
2064
2086
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
const failedServers = event.servers.filter(server => server.status === "error");
|
|
2071
|
-
|
|
2072
|
-
if (failedServers.length === 1) {
|
|
2073
|
-
const failedServer = failedServers[0];
|
|
2074
|
-
const detail = failedServer.error ? `: ${failedServer.error}` : "";
|
|
2075
|
-
this.showWarning(`LSP startup failed for ${failedServer.name}${detail}. It will retry lazily on write.`);
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
if (failedServers.length > 1) {
|
|
2080
|
-
const failedNames = failedServers.map(server => server.name).join(", ");
|
|
2081
|
-
this.showWarning(`LSP startup failed for ${failedNames}. It will retry lazily on write.`);
|
|
2087
|
+
const warningMessage = getLspStartupWarningMessage(event);
|
|
2088
|
+
if (warningMessage) {
|
|
2089
|
+
this.showWarning(warningMessage);
|
|
2082
2090
|
}
|
|
2083
2091
|
}
|
|
2084
2092
|
|
|
@@ -28,6 +28,7 @@ interface PromptActionAutocompleteOptions {
|
|
|
28
28
|
keybindings: KeybindingsManager;
|
|
29
29
|
copyCurrentLine: () => void;
|
|
30
30
|
copyPrompt: () => void;
|
|
31
|
+
pasteImage: () => void;
|
|
31
32
|
undo: (prefix: string) => void;
|
|
32
33
|
moveCursorToMessageEnd: () => void;
|
|
33
34
|
moveCursorToMessageStart: () => void;
|
|
@@ -190,7 +191,9 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
190
191
|
const query = promptActionPrefix.slice(1).toLowerCase();
|
|
191
192
|
const items = this.#actions
|
|
192
193
|
.map(action => {
|
|
193
|
-
const searchable = [action.label, action.description, ...action.keywords]
|
|
194
|
+
const searchable = [action.id, action.label, action.description, ...action.keywords]
|
|
195
|
+
.join(" ")
|
|
196
|
+
.toLowerCase();
|
|
194
197
|
if (!fuzzyMatch(query, searchable)) return null;
|
|
195
198
|
return {
|
|
196
199
|
value: action.label,
|
|
@@ -368,6 +371,13 @@ export function createPromptActionAutocompleteProvider(
|
|
|
368
371
|
keywords: ["copy", "prompt", "clipboard", "message"],
|
|
369
372
|
execute: options.copyPrompt,
|
|
370
373
|
},
|
|
374
|
+
{
|
|
375
|
+
id: "paste-image",
|
|
376
|
+
label: "Paste image from clipboard",
|
|
377
|
+
description: formatKeyHints(options.keybindings.getKeys("app.clipboard.pasteImage")),
|
|
378
|
+
keywords: ["paste", "image", "clipboard", "screenshot", "attach", "vision"],
|
|
379
|
+
execute: options.pasteImage,
|
|
380
|
+
},
|
|
371
381
|
{
|
|
372
382
|
id: "undo",
|
|
373
383
|
label: "Undo",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import * as path from "node:path";
|
|
15
14
|
import { $pickenv, logger, readLines, Snowflake } from "@sayknow-cli/utils";
|
|
16
15
|
import type {
|
|
17
16
|
ExtensionUIContext,
|
|
@@ -20,6 +19,7 @@ import type {
|
|
|
20
19
|
} from "../../extensibility/extensions";
|
|
21
20
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
22
21
|
import type { AgentSession } from "../../session/agent-session";
|
|
22
|
+
import { workflowGatePath } from "../../skc-runtime/session-layout";
|
|
23
23
|
import { initializeExtensions } from "../runtime-init";
|
|
24
24
|
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
25
25
|
import { AgentWireFrameSequencer, toAgentWireEventFrame } from "../shared/agent-wire/event-envelope";
|
|
@@ -336,7 +336,7 @@ export async function runRpcMode(
|
|
|
336
336
|
// Unattended control plane (#318/#319/#323/G011): routes negotiate_unattended +
|
|
337
337
|
// workflow_gate_response and lets skill runtimes emit gates over RPC.
|
|
338
338
|
const gateStore = new FileGateStore(
|
|
339
|
-
|
|
339
|
+
workflowGatePath(session.sessionManager.getCwd(), session.sessionId, session.sessionId),
|
|
340
340
|
);
|
|
341
341
|
const unattendedControlPlane = new UnattendedSessionControlPlane({
|
|
342
342
|
runId: session.sessionId,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
+
import { sessionAuditDir } from "../../../skc-runtime/session-layout";
|
|
15
16
|
import type { RpcBudgetExceeded, RpcWorkflowGateKind, RpcWorkflowStage } from "../../rpc/rpc-types";
|
|
16
17
|
import { answerHashOf } from "./workflow-gate-schema";
|
|
17
18
|
|
|
@@ -69,9 +70,9 @@ function defaultId(): string {
|
|
|
69
70
|
return `ae_${Date.now().toString(36)}_${idCounter.toString(36)}`;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
export function defaultAuditPath(runId: string, root = process.cwd()): string {
|
|
73
|
+
export function defaultAuditPath(runId: string, root = process.cwd(), skcSessionId = runId): string {
|
|
73
74
|
const safe = runId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
74
|
-
return path.join(root,
|
|
75
|
+
return path.join(sessionAuditDir(root, skcSessionId), "unattended", `${safe}.jsonl`);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/** Append-only audit log writer + reader for one unattended run. */
|
|
@@ -5,7 +5,7 @@ thinking-level: medium
|
|
|
5
5
|
hide: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Generate AGENTS.md by launching multiple
|
|
8
|
+
Generate AGENTS.md by launching multiple canonical role agents in parallel (via `task` tool, usually `planner` or `architect`) scanning different areas (core src, tests, configs/build, scripts/docs), then synthesize findings into a single file.
|
|
9
9
|
|
|
10
10
|
<structure>
|
|
11
11
|
- **Project Overview**: Brief description of project purpose
|
|
@@ -82,7 +82,7 @@ The plan MUST be scannable yet detailed enough to execute.
|
|
|
82
82
|
|
|
83
83
|
<procedure>
|
|
84
84
|
### Phase 1: Understand
|
|
85
|
-
You MUST focus on the request and associated code. You SHOULD launch parallel
|
|
85
|
+
You MUST focus on the request and associated code. You SHOULD launch parallel canonical role agents (`planner` or `architect`) when scope spans multiple areas.
|
|
86
86
|
|
|
87
87
|
### Phase 2: Design
|
|
88
88
|
You MUST draft an approach based on exploration. You MUST consider trade-offs briefly, then choose.
|
|
@@ -38,5 +38,5 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
38
38
|
<critical>
|
|
39
39
|
- Avoid repo-root scans — narrow `paths` first
|
|
40
40
|
- Parse issues are query failure, not evidence of absence: repair the pattern or tighten `paths` before concluding "no matches"
|
|
41
|
-
- For broad/open-ended
|
|
41
|
+
- For broad/open-ended inspection across subsystems, delegate a bounded fact-finding task to an appropriate canonical role agent (`planner` or `architect`) first
|
|
42
42
|
</critical>
|