@gajae-code/coding-agent 0.16.3 → 0.16.6
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/CHANGELOG.md +113 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/customize-doctor.d.ts +22 -0
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +31 -3
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +7 -0
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +35 -0
- package/dist/types/exec/bash-executor.d.ts +17 -1
- package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
- package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-signals.d.ts +2 -0
- package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
- package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
- package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
- package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
- package/dist/types/exec/bash-shell-worker.d.ts +1 -0
- package/dist/types/exec/isolated-shell.d.ts +19 -0
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
- package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/runner.d.ts +46 -6
- package/dist/types/extensibility/extensions/types.d.ts +16 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skill-management.d.ts +9 -4
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
- package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/bash-execution.d.ts +1 -0
- package/dist/types/modes/components/execution-shared.d.ts +1 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/broker/ensure.d.ts +32 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
- package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
- package/dist/types/sdk/client/client.d.ts +6 -0
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/session-runtime.d.ts +26 -1
- package/dist/types/sdk/lifecycle/service.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +10 -2
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/session.d.ts +5 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/setup/paseo/announce.d.ts +112 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/skill-state/canonical-skills.d.ts +4 -0
- package/dist/types/system-prompt.d.ts +10 -0
- package/dist/types/task/executor.d.ts +5 -1
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/github-cache.d.ts +2 -1
- package/dist/types/tools/index.d.ts +10 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +9 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +11 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/capability/index.ts +14 -2
- package/src/capability/types.ts +4 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/customize-doctor.ts +155 -12
- package/src/cli/root-flags.ts +5 -3
- package/src/cli.ts +22 -0
- package/src/commands/sdk.ts +74 -31
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock-gc.ts +40 -2
- package/src/config/file-lock.ts +379 -31
- package/src/config/model-preset-registry.ts +120 -19
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +73 -23
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +33 -2
- package/src/coordinator-mcp/server.ts +477 -101
- package/src/crash/sanitize.ts +1 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
- package/src/discovery/builtin.ts +52 -55
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +307 -34
- package/src/exec/bash-executor.ts +330 -50
- package/src/exec/bash-shell-guardian.ts +388 -0
- package/src/exec/bash-shell-runtime-entry.ts +5 -0
- package/src/exec/bash-shell-signals.ts +29 -0
- package/src/exec/bash-shell-supervisor-entry.ts +3 -0
- package/src/exec/bash-shell-supervisor.ts +359 -0
- package/src/exec/bash-shell-worker-entry.ts +5 -0
- package/src/exec/bash-shell-worker-protocol.ts +36 -0
- package/src/exec/bash-shell-worker.ts +116 -0
- package/src/exec/isolated-shell.ts +393 -0
- package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
- package/src/extensibility/extensions/function-hooks.ts +959 -0
- package/src/extensibility/extensions/index.ts +22 -0
- package/src/extensibility/extensions/loader.ts +53 -2
- package/src/extensibility/extensions/runner.ts +895 -264
- package/src/extensibility/extensions/types.ts +42 -0
- package/src/extensibility/extensions/wrapper.ts +56 -15
- package/src/extensibility/gjc-plugins/compiler.ts +228 -60
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/registry.ts +113 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
- package/src/extensibility/gjc-plugins/schema.ts +121 -4
- package/src/extensibility/gjc-plugins/types.ts +25 -0
- package/src/extensibility/hooks/loader.ts +133 -16
- package/src/extensibility/hooks/types.ts +28 -0
- package/src/extensibility/runtime-skill-discovery.ts +149 -190
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skill-management.ts +200 -52
- package/src/extensibility/skills.ts +73 -25
- package/src/gjc-runtime/empty-delete-gc.ts +94 -4
- package/src/gjc-runtime/gc-runtime.ts +29 -0
- package/src/gjc-runtime/linux-proc.ts +14 -0
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +143 -10
- package/src/gjc-runtime/session-state-sidecar.ts +27 -11
- package/src/gjc-runtime/state-writer.ts +30 -29
- package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
- package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -0
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +12 -12
- package/src/lsp/config.ts +10 -3
- package/src/lsp/index.ts +1 -1
- package/src/lsp/path-trust.ts +69 -14
- package/src/main.ts +78 -35
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +16 -3
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/bash-execution.ts +4 -1
- package/src/modes/components/execution-shared.ts +4 -1
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +396 -22
- package/src/modes/controllers/command-controller.ts +3 -0
- package/src/modes/controllers/event-controller.ts +151 -7
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +6 -3
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +53 -6
- package/src/modes/print-mode.ts +5 -1
- package/src/modes/prompt-action-autocomplete.ts +54 -27
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/types.ts +17 -3
- package/src/modes/utils/ui-helpers.ts +2 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/prompts/tools/subagent.md +1 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +189 -14
- package/src/runtime-mcp/pool.test.ts +218 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +124 -32
- package/src/sdk/broker/ensure.ts +248 -41
- package/src/sdk/broker/lifecycle-ledger.ts +7 -17
- package/src/sdk/broker/lifecycle.ts +56 -6
- package/src/sdk/broker/startup-failure.ts +15 -6
- package/src/sdk/bus/chat-daemon-control.ts +3 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
- package/src/sdk/bus/chat-effect-journal.ts +132 -7
- package/src/sdk/bus/conversation-store.ts +29 -0
- package/src/sdk/bus/lifecycle-commands.ts +17 -11
- package/src/sdk/bus/slack-conversation.ts +1 -0
- package/src/sdk/bus/slack-daemon.ts +478 -49
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/bus/telegram-daemon.ts +16 -2
- package/src/sdk/bus/topic-registry.ts +19 -0
- package/src/sdk/cli/master-cli.ts +1 -1
- package/src/sdk/cli/session-cli.ts +11 -8
- package/src/sdk/client/client.ts +66 -17
- package/src/sdk/host/host.ts +29 -9
- package/src/sdk/host/query/revision-store.ts +19 -13
- package/src/sdk/host/reverse-leases.ts +7 -4
- package/src/sdk/host/session-runtime.test.ts +52 -10
- package/src/sdk/host/session-runtime.ts +383 -44
- package/src/sdk/lifecycle/service.ts +4 -0
- package/src/sdk/protocol/operation-inventory.generated.json +77 -0
- package/src/sdk/router/session-router.ts +89 -38
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +228 -55
- package/src/sdk/transport/relay.ts +35 -13
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +240 -90
- package/src/session/contribution-prep.ts +214 -21
- package/src/session/fallback-chain-controller.ts +38 -0
- package/src/session/internal/managed-session-storage.ts +19 -5
- package/src/session/messages.ts +5 -0
- package/src/session/session-manager.ts +29 -5
- package/src/session-import/redact.ts +5 -1
- package/src/setup/paseo/announce.ts +456 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/skill-state/canonical-skills.ts +12 -0
- package/src/skills/skills.test.ts +41 -21
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +29 -3
- package/src/task/executor.ts +7 -1
- package/src/task/index.ts +21 -4
- package/src/tools/bash.ts +63 -19
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/gh.ts +26 -3
- package/src/tools/github-cache.ts +68 -16
- package/src/tools/index.ts +10 -0
- package/src/tools/job.ts +21 -4
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/read.ts +14 -2
- package/src/tools/render-utils.ts +1 -3
- package/src/tools/resource-gc.ts +28 -8
- package/src/tools/search.ts +116 -58
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +37 -2
- package/src/tools/subagent.ts +6 -3
- package/src/tools/tool-catalog.generated.ts +10 -3
- package/src/tools/ultragoal-ask-guard.ts +2 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -12,16 +12,32 @@
|
|
|
12
12
|
* writing a skill file into a scope, and per-skill enable/disable. The import
|
|
13
13
|
* preview/apply transaction and the UI itself are owned by #4291.
|
|
14
14
|
*/
|
|
15
|
+
import * as nodeFs from "node:fs";
|
|
15
16
|
import * as fs from "node:fs/promises";
|
|
16
17
|
import * as path from "node:path";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
getAgentDir,
|
|
20
|
+
getAgentProfileAuthority,
|
|
21
|
+
getTrustedHomeDir,
|
|
22
|
+
normalizePathForComparison,
|
|
23
|
+
parseFrontmatter,
|
|
24
|
+
} from "@gajae-code/utils";
|
|
18
25
|
import { findRepoRoot } from "../capability/fs";
|
|
19
26
|
import type { Skill as CapabilitySkill } from "../capability/skill";
|
|
20
27
|
import { resolveSkillScopeTrust } from "../config/skill-settings-defaults";
|
|
21
28
|
import { scanClaudeProjectSkills, scanClaudeUserSkills } from "../discovery/claude";
|
|
22
29
|
import { scanCodexProjectSkills, scanCodexUserSkills } from "../discovery/codex";
|
|
23
|
-
import {
|
|
24
|
-
|
|
30
|
+
import {
|
|
31
|
+
compareSkillOrder,
|
|
32
|
+
getAncestorDirs,
|
|
33
|
+
getUserSkillScanDirs,
|
|
34
|
+
resolveUserAgentDir,
|
|
35
|
+
scanSkillsFromDir,
|
|
36
|
+
} from "../discovery/helpers";
|
|
37
|
+
import {
|
|
38
|
+
CANONICAL_GJC_WORKFLOW_SKILLS,
|
|
39
|
+
isCanonicalGjcWorkflowSkillFilesystemAlias,
|
|
40
|
+
} from "../skill-state/canonical-skills";
|
|
25
41
|
export type SkillScope = "project" | "user";
|
|
26
42
|
export type ConventionSkillHost = "claude" | "codex";
|
|
27
43
|
|
|
@@ -61,6 +77,7 @@ export interface ConventionSkillImportSource {
|
|
|
61
77
|
export interface WriteNativeSkillInput {
|
|
62
78
|
cwd: string;
|
|
63
79
|
home?: string;
|
|
80
|
+
agentDir?: string;
|
|
64
81
|
scope: SkillScope;
|
|
65
82
|
name: string;
|
|
66
83
|
content: string;
|
|
@@ -93,28 +110,127 @@ export class SkillFrontmatterError extends Error {
|
|
|
93
110
|
}
|
|
94
111
|
}
|
|
95
112
|
|
|
96
|
-
|
|
113
|
+
function isProtectedSkillName(name: string): boolean {
|
|
114
|
+
return isCanonicalGjcWorkflowSkillFilesystemAlias(name);
|
|
115
|
+
}
|
|
97
116
|
|
|
98
|
-
function
|
|
99
|
-
|
|
117
|
+
function assertSafeSkillName(name: string): void {
|
|
118
|
+
if (
|
|
119
|
+
name === "." ||
|
|
120
|
+
name === ".." ||
|
|
121
|
+
/[. ]$/u.test(name) ||
|
|
122
|
+
name.includes("/") ||
|
|
123
|
+
name.includes("\\") ||
|
|
124
|
+
name.includes("\0") ||
|
|
125
|
+
path.basename(name) !== name
|
|
126
|
+
) {
|
|
127
|
+
throw new SkillFrontmatterError(`skill name must be a single path segment: ${name}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isPathWithin(root: string, candidate: string): boolean {
|
|
132
|
+
const relative = path.relative(root, candidate);
|
|
133
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function ensureSafeDescendantDirectory(root: string, segments: readonly string[]): Promise<string> {
|
|
137
|
+
try {
|
|
138
|
+
const rootStat = await fs.lstat(root);
|
|
139
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
140
|
+
throw new SkillFrontmatterError(`skill authority root is not a safe directory: ${root}`);
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
144
|
+
await fs.mkdir(root, { recursive: true });
|
|
145
|
+
const rootStat = await fs.lstat(root);
|
|
146
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
147
|
+
throw new SkillFrontmatterError(`skill authority root is not a safe directory: ${root}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const realRoot = await fs.realpath(root);
|
|
151
|
+
let current = root;
|
|
152
|
+
for (const segment of segments) {
|
|
153
|
+
current = path.join(current, segment);
|
|
154
|
+
try {
|
|
155
|
+
const stat = await fs.lstat(current);
|
|
156
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
157
|
+
throw new SkillFrontmatterError(`skill destination is not a safe directory: ${current}`);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
161
|
+
try {
|
|
162
|
+
await fs.mkdir(current);
|
|
163
|
+
} catch (mkdirError) {
|
|
164
|
+
if ((mkdirError as NodeJS.ErrnoException).code !== "EEXIST") throw mkdirError;
|
|
165
|
+
const stat = await fs.lstat(current);
|
|
166
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
167
|
+
throw new SkillFrontmatterError(`skill destination is not a safe directory: ${current}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const realCurrent = await fs.realpath(current);
|
|
172
|
+
if (!isPathWithin(realRoot, realCurrent)) {
|
|
173
|
+
throw new SkillFrontmatterError(`skill destination escapes its scope root: ${current}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return current;
|
|
100
177
|
}
|
|
101
178
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
179
|
+
async function writeSkillFileSafely(skillDir: string, scopeRoot: string, content: string): Promise<string> {
|
|
180
|
+
const [canonicalSkillDir, canonicalScopeRoot] = await Promise.all([fs.realpath(skillDir), fs.realpath(scopeRoot)]);
|
|
181
|
+
if (!isPathWithin(canonicalScopeRoot, canonicalSkillDir)) {
|
|
182
|
+
throw new SkillFrontmatterError(`skill destination escapes its scope root: ${skillDir}`);
|
|
183
|
+
}
|
|
184
|
+
const filePath = path.join(skillDir, "SKILL.md");
|
|
185
|
+
let destinationExists = false;
|
|
186
|
+
try {
|
|
187
|
+
const destination = await fs.lstat(filePath);
|
|
188
|
+
if (destination.isSymbolicLink() || !destination.isFile()) {
|
|
189
|
+
throw new SkillFrontmatterError(`skill destination is not a safe file: ${filePath}`);
|
|
111
190
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
191
|
+
destinationExists = true;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
194
|
+
}
|
|
195
|
+
const noFollowFlags =
|
|
196
|
+
process.platform === "win32" ? 0 : (nodeFs.constants.O_NOFOLLOW ?? 0) | (nodeFs.constants.O_NONBLOCK ?? 0);
|
|
197
|
+
const flags = destinationExists
|
|
198
|
+
? nodeFs.constants.O_WRONLY | noFollowFlags
|
|
199
|
+
: nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT | nodeFs.constants.O_EXCL | noFollowFlags;
|
|
200
|
+
let handle: fs.FileHandle;
|
|
201
|
+
try {
|
|
202
|
+
handle = await fs.open(filePath, flags, 0o600);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw new SkillFrontmatterError(`skill destination is not a safe file: ${filePath} (${String(error)})`);
|
|
116
205
|
}
|
|
117
|
-
|
|
206
|
+
try {
|
|
207
|
+
const [opened, observed, currentSkillDir] = await Promise.all([
|
|
208
|
+
handle.stat({ bigint: true }),
|
|
209
|
+
fs.lstat(filePath, { bigint: true }),
|
|
210
|
+
fs.realpath(skillDir),
|
|
211
|
+
]);
|
|
212
|
+
if (
|
|
213
|
+
!opened.isFile() ||
|
|
214
|
+
opened.nlink !== 1n ||
|
|
215
|
+
observed.isSymbolicLink() ||
|
|
216
|
+
!observed.isFile() ||
|
|
217
|
+
opened.dev !== observed.dev ||
|
|
218
|
+
opened.ino !== observed.ino ||
|
|
219
|
+
normalizePathForComparison(currentSkillDir) !== normalizePathForComparison(canonicalSkillDir) ||
|
|
220
|
+
!isPathWithin(canonicalScopeRoot, currentSkillDir)
|
|
221
|
+
) {
|
|
222
|
+
throw new SkillFrontmatterError(`skill destination changed during write: ${filePath}`);
|
|
223
|
+
}
|
|
224
|
+
await handle.truncate(0);
|
|
225
|
+
await handle.writeFile(content);
|
|
226
|
+
} finally {
|
|
227
|
+
await handle.close();
|
|
228
|
+
}
|
|
229
|
+
return filePath;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getRuntimeHome(): string {
|
|
233
|
+
return getTrustedHomeDir();
|
|
118
234
|
}
|
|
119
235
|
|
|
120
236
|
/**
|
|
@@ -126,34 +242,42 @@ export async function getProjectSkillDirs(
|
|
|
126
242
|
home: string,
|
|
127
243
|
): Promise<{ dirs: string[]; repoRoot: string | null }> {
|
|
128
244
|
const repoRoot = await findRepoRoot(cwd);
|
|
129
|
-
const walkDirs =
|
|
130
|
-
return { dirs: walkDirs.map(dir => path.join(dir, ".gjc", "skills")), repoRoot };
|
|
245
|
+
const walkDirs = getAncestorDirs(cwd, path.resolve(repoRoot ?? cwd), home);
|
|
246
|
+
return { dirs: walkDirs.map(({ dir }) => path.join(dir, ".gjc", "skills")), repoRoot };
|
|
131
247
|
}
|
|
132
248
|
|
|
133
249
|
/** Canonical user skill directories in precedence order (same resolution as runtime discovery). */
|
|
134
|
-
export function getUserSkillDirs(home: string): string[] {
|
|
135
|
-
return
|
|
136
|
-
...new Set([
|
|
137
|
-
path.join(home, SOURCE_PATHS.native.userAgent, "skills"),
|
|
138
|
-
path.join(home, SOURCE_PATHS.native.userBase, "skills"),
|
|
139
|
-
path.join(home, ".gjc", "skills"),
|
|
140
|
-
]),
|
|
141
|
-
];
|
|
250
|
+
export function getUserSkillDirs(home: string, agentDir?: string, profileAuthority?: "default" | "custom"): string[] {
|
|
251
|
+
return getUserSkillScanDirs(home, agentDir, profileAuthority);
|
|
142
252
|
}
|
|
143
253
|
|
|
144
254
|
/**
|
|
145
255
|
* The canonical directory a write targets for a scope: the repo root (or `cwd`)
|
|
146
|
-
* `.gjc/skills` for project scope, the
|
|
147
|
-
*
|
|
256
|
+
* `.gjc/skills` for project scope, the agent directory's `skills` root for user
|
|
257
|
+
* scope — the same directory every reader scans first (`gjc config path` prints
|
|
258
|
+
* it; `--agent-dir` / `GJC_CODING_AGENT_DIR` / `setAgentDir()` move it).
|
|
148
259
|
*/
|
|
149
260
|
export async function resolveNativeSkillScopeDir(
|
|
150
261
|
cwd: string,
|
|
151
262
|
scope: SkillScope,
|
|
152
|
-
|
|
263
|
+
_home?: string,
|
|
264
|
+
agentDir?: string,
|
|
153
265
|
): Promise<string> {
|
|
154
|
-
|
|
266
|
+
const home = _home ?? getRuntimeHome();
|
|
267
|
+
if (scope === "user") {
|
|
268
|
+
const resolvedAgentDir = agentDir ?? (_home === undefined ? getAgentDir() : resolveUserAgentDir(home));
|
|
269
|
+
return path.join(path.resolve(resolvedAgentDir), "skills");
|
|
270
|
+
}
|
|
155
271
|
const repoRoot = await findRepoRoot(cwd);
|
|
156
|
-
|
|
272
|
+
const resolvedCwd = path.resolve(cwd);
|
|
273
|
+
const projectRoot = repoRoot ?? resolvedCwd;
|
|
274
|
+
if (normalizePathForComparison(projectRoot) === normalizePathForComparison(home)) {
|
|
275
|
+
if (normalizePathForComparison(resolvedCwd) === normalizePathForComparison(home)) {
|
|
276
|
+
throw new SkillFrontmatterError("project skill scope is unavailable at the user home boundary");
|
|
277
|
+
}
|
|
278
|
+
return path.join(resolvedCwd, ".gjc", "skills");
|
|
279
|
+
}
|
|
280
|
+
return path.join(projectRoot, ".gjc", "skills");
|
|
157
281
|
}
|
|
158
282
|
|
|
159
283
|
function matchesPattern(name: string, patterns: string[] | undefined): boolean {
|
|
@@ -174,14 +298,31 @@ function isDisabledByExtension(name: string, disabledExtensions: string[] | unde
|
|
|
174
298
|
export async function listNativeSkillsForManagement(options: {
|
|
175
299
|
cwd: string;
|
|
176
300
|
home?: string;
|
|
301
|
+
agentDir?: string;
|
|
302
|
+
/** Resolver-owned profile classification; unlike path comparison, this survives HOME refreshes. */
|
|
303
|
+
profileAuthority?: "default" | "custom";
|
|
177
304
|
policy?: SkillManagementPolicy;
|
|
178
305
|
}): Promise<ManagedSkillRecord[]> {
|
|
306
|
+
const homeWasInjected = options.home !== undefined;
|
|
179
307
|
const home = options.home ?? getRuntimeHome();
|
|
308
|
+
const agentDir = options.agentDir ?? (homeWasInjected ? resolveUserAgentDir(home) : getAgentDir());
|
|
309
|
+
const profileAuthority =
|
|
310
|
+
options.profileAuthority ??
|
|
311
|
+
(options.agentDir !== undefined
|
|
312
|
+
? normalizePathForComparison(options.agentDir) ===
|
|
313
|
+
normalizePathForComparison(homeWasInjected ? resolveUserAgentDir(home) : getAgentDir())
|
|
314
|
+
? homeWasInjected
|
|
315
|
+
? "default"
|
|
316
|
+
: getAgentProfileAuthority()
|
|
317
|
+
: "custom"
|
|
318
|
+
: !homeWasInjected
|
|
319
|
+
? getAgentProfileAuthority()
|
|
320
|
+
: undefined);
|
|
180
321
|
const policy = options.policy;
|
|
181
322
|
const projectTrusted = resolveSkillScopeTrust(policy ?? {}, "project");
|
|
182
323
|
const userTrusted = resolveSkillScopeTrust(policy ?? {}, "user");
|
|
183
324
|
|
|
184
|
-
const scanJobs: Array<Promise<{ dir: string; items: CapabilitySkill[] }>> = [];
|
|
325
|
+
const scanJobs: Array<Promise<{ dir: string; scope: SkillScope; items: CapabilitySkill[] }>> = [];
|
|
185
326
|
const projectDirs = await getProjectSkillDirs(options.cwd, home);
|
|
186
327
|
if (projectTrusted) {
|
|
187
328
|
for (const dir of projectDirs.dirs) {
|
|
@@ -189,17 +330,23 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
189
330
|
scanSkillsFromDir(
|
|
190
331
|
{ cwd: options.cwd, home, repoRoot: projectDirs.repoRoot },
|
|
191
332
|
{ dir, providerId: "runtime", level: "project", requireDescription: true },
|
|
192
|
-
).then(result => ({ dir, items: result.items })),
|
|
333
|
+
).then(result => ({ dir, scope: "project" as const, items: result.items })),
|
|
193
334
|
);
|
|
194
335
|
}
|
|
195
336
|
}
|
|
196
337
|
if (userTrusted) {
|
|
197
|
-
for (const dir of
|
|
338
|
+
for (const dir of getUserSkillScanDirs(home, agentDir, profileAuthority)) {
|
|
198
339
|
scanJobs.push(
|
|
199
340
|
scanSkillsFromDir(
|
|
200
341
|
{ cwd: options.cwd, home, repoRoot: home },
|
|
201
|
-
{
|
|
202
|
-
|
|
342
|
+
{
|
|
343
|
+
dir,
|
|
344
|
+
authorityRoot: path.resolve(dir) === path.resolve(agentDir, "skills") ? agentDir : undefined,
|
|
345
|
+
providerId: "runtime",
|
|
346
|
+
level: "user",
|
|
347
|
+
requireDescription: true,
|
|
348
|
+
},
|
|
349
|
+
).then(result => ({ dir, scope: "user" as const, items: result.items })),
|
|
203
350
|
);
|
|
204
351
|
}
|
|
205
352
|
}
|
|
@@ -208,8 +355,7 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
208
355
|
const seenNames = new Set<string>();
|
|
209
356
|
const seenPaths = new Set<string>();
|
|
210
357
|
|
|
211
|
-
for (const { dir, items } of await Promise.all(scanJobs)) {
|
|
212
|
-
const scope: SkillScope = path.resolve(dir).startsWith(`${path.resolve(home)}${path.sep}`) ? "user" : "project";
|
|
358
|
+
for (const { dir, scope, items } of await Promise.all(scanJobs)) {
|
|
213
359
|
const source = scope === "project" ? "project .gjc/skills" : `user ${dir}`;
|
|
214
360
|
for (const skill of items) {
|
|
215
361
|
const realPath = await safeRealpath(skill.path);
|
|
@@ -217,7 +363,7 @@ export async function listNativeSkillsForManagement(options: {
|
|
|
217
363
|
|
|
218
364
|
let disabledReason: SkillDisabledReason | undefined;
|
|
219
365
|
let enabled = true;
|
|
220
|
-
if (
|
|
366
|
+
if (isProtectedSkillName(skill.name)) {
|
|
221
367
|
enabled = false;
|
|
222
368
|
disabledReason = "protected";
|
|
223
369
|
} else if (!(scope === "project" ? projectTrusted : userTrusted)) {
|
|
@@ -272,19 +418,21 @@ export async function writeNativeSkill(input: WriteNativeSkillInput): Promise<Wr
|
|
|
272
418
|
|
|
273
419
|
const effectiveName =
|
|
274
420
|
typeof frontmatter.name === "string" && frontmatter.name.trim() ? frontmatter.name.trim() : name;
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
421
|
+
if (isProtectedSkillName(effectiveName)) throw new SkillNameProtectedError(effectiveName);
|
|
422
|
+
assertSafeSkillName(effectiveName);
|
|
423
|
+
|
|
424
|
+
const directory = await resolveNativeSkillScopeDir(input.cwd, input.scope, input.home, input.agentDir);
|
|
425
|
+
const scopeRoot = input.scope === "user" ? path.dirname(directory) : path.dirname(path.dirname(directory));
|
|
426
|
+
const relativeDirectory = path.relative(scopeRoot, directory);
|
|
427
|
+
const directorySegments = relativeDirectory.split(path.sep).filter(Boolean);
|
|
428
|
+
const skillDir = await ensureSafeDescendantDirectory(scopeRoot, [...directorySegments, effectiveName]);
|
|
429
|
+
const filePath = await writeSkillFileSafely(skillDir, scopeRoot, `${input.content.trimEnd()}\n`);
|
|
282
430
|
return { name: effectiveName, scope: input.scope, directory, path: filePath };
|
|
283
431
|
}
|
|
284
432
|
|
|
285
433
|
/** Whether a skill is enabled under the current policy (disabledExtensions/ignored/scope trust). */
|
|
286
434
|
export function isNativeSkillEnabled(name: string, policy: SkillManagementPolicy | undefined): boolean {
|
|
287
|
-
if (
|
|
435
|
+
if (isProtectedSkillName(name)) return true;
|
|
288
436
|
if (isDisabledByExtension(name, policy?.disabledExtensions)) return false;
|
|
289
437
|
if (matchesPattern(name, policy?.ignoredSkills)) return false;
|
|
290
438
|
if (policy?.includeSkills?.length && !matchesPattern(name, policy.includeSkills)) return false;
|
|
@@ -299,7 +447,7 @@ export function isNativeSkillEnabled(name: string, policy: SkillManagementPolicy
|
|
|
299
447
|
export function setNativeSkillEnabled(name: string, enabled: boolean, disabledExtensions: string[]): string[] {
|
|
300
448
|
const id = `skill:${name}`;
|
|
301
449
|
const next = new Set(disabledExtensions);
|
|
302
|
-
if (
|
|
450
|
+
if (isProtectedSkillName(name)) return [...next];
|
|
303
451
|
if (enabled) {
|
|
304
452
|
next.delete(id);
|
|
305
453
|
} else {
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getAgentDir,
|
|
4
|
+
getAgentProfileAuthority,
|
|
5
|
+
getProjectDir,
|
|
6
|
+
getTrustedHomeDir,
|
|
7
|
+
normalizePathForComparison,
|
|
8
|
+
} from "@gajae-code/utils";
|
|
3
9
|
import { findRepoRoot } from "../capability/fs";
|
|
4
10
|
import { skillCapability } from "../capability/skill";
|
|
5
11
|
import type { SourceMeta } from "../capability/types";
|
|
6
12
|
import type { SkillsSettings } from "../config/settings";
|
|
7
13
|
import { resolveSkillScopeTrust } from "../config/skill-settings-defaults";
|
|
8
14
|
import { type Skill as CapabilitySkill, getCapability } from "../discovery";
|
|
9
|
-
import {
|
|
15
|
+
import { loadMarketplaceSkills } from "../discovery/claude-plugins";
|
|
16
|
+
import { compareSkillOrder, resolveUserAgentDir, scanSkillsFromDir } from "../discovery/helpers";
|
|
10
17
|
import type { SkillPromptDetails } from "../session/messages";
|
|
11
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
getSkillFilesystemIdentity,
|
|
20
|
+
isCanonicalGjcWorkflowSkillFilesystemAlias,
|
|
21
|
+
} from "../skill-state/canonical-skills";
|
|
12
22
|
import { expandTilde } from "../tools/path-utils";
|
|
13
23
|
import type { LoadedSubskillActivation } from "./gjc-plugins";
|
|
14
24
|
import { buildSubskillInjection } from "./gjc-plugins/injection";
|
|
@@ -113,16 +123,17 @@ export interface LoadSkillsOptions extends SkillsSettings {
|
|
|
113
123
|
home?: string;
|
|
114
124
|
/** Explicit user agent directory for native user-scope skill discovery. */
|
|
115
125
|
agentDir?: string;
|
|
126
|
+
/** Resolver-owned classification for the selected agent directory. */
|
|
127
|
+
profileAuthority?: "default" | "custom";
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
/**
|
|
119
|
-
* Skill providers loaded into sessions.
|
|
120
|
-
*
|
|
131
|
+
* Skill providers loaded into sessions. Native `.gjc` skills and installed
|
|
132
|
+
* marketplace plugin skills are both live filesystem skills, so both are
|
|
133
|
+
* enumerated into sessions; Claude/Codex convention skills are explicit import
|
|
121
134
|
* sources into `.gjc` (see skill-management.ts) and are never loaded directly.
|
|
122
135
|
*/
|
|
123
|
-
const LOADABLE_SKILL_PROVIDERS = new Set(["native"]);
|
|
124
|
-
|
|
125
|
-
const BUILT_IN_SKILL_NAMES = new Set<string>(CANONICAL_GJC_WORKFLOW_SKILLS.map(name => name.toLowerCase()));
|
|
136
|
+
const LOADABLE_SKILL_PROVIDERS = new Set(["native", "claude-plugins"]);
|
|
126
137
|
|
|
127
138
|
/**
|
|
128
139
|
* Load skills from all configured locations.
|
|
@@ -142,14 +153,25 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
142
153
|
if (!enabled) {
|
|
143
154
|
return { skills: [], warnings: [] };
|
|
144
155
|
}
|
|
156
|
+
const homeWasInjected = options.home !== undefined;
|
|
145
157
|
const home = options.home ?? getTrustedHomeDir();
|
|
158
|
+
const agentDir = options.agentDir ?? (homeWasInjected ? resolveUserAgentDir(home) : getAgentDir());
|
|
159
|
+
const profileAuthority =
|
|
160
|
+
options.profileAuthority ??
|
|
161
|
+
(!homeWasInjected
|
|
162
|
+
? options.agentDir !== undefined &&
|
|
163
|
+
normalizePathForComparison(options.agentDir) !== normalizePathForComparison(getAgentDir())
|
|
164
|
+
? "custom"
|
|
165
|
+
: getAgentProfileAuthority()
|
|
166
|
+
: undefined);
|
|
146
167
|
|
|
147
168
|
const projectTrusted = resolveSkillScopeTrust(options, "project");
|
|
148
169
|
const userTrusted = resolveSkillScopeTrust(options, "user");
|
|
149
170
|
|
|
150
171
|
// Skill scope trust decides which canonical locations are loaded: project
|
|
151
172
|
// scope covers `.gjc/skills` (walk-up), user scope covers `~/.gjc/agent/skills`
|
|
152
|
-
// and the legacy user roots
|
|
173
|
+
// and the legacy user roots, and installed marketplace plugins are gated by the
|
|
174
|
+
// scope they were installed into. Claude/Codex convention skills are import
|
|
153
175
|
// sources into `.gjc`, never loaded directly.
|
|
154
176
|
function isSourceEnabled(source: SourceMeta): boolean {
|
|
155
177
|
const { provider, level } = source;
|
|
@@ -162,16 +184,34 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
162
184
|
// Use capability API to load all skills. `all` (rather than `items`) keeps
|
|
163
185
|
// shadowed duplicates so this function can apply the documented precedence
|
|
164
186
|
// itself: project scope beats user scope.
|
|
165
|
-
const
|
|
166
|
-
provider =>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
const providers =
|
|
188
|
+
getCapability<CapabilitySkill>(skillCapability.id)?.providers.filter(provider =>
|
|
189
|
+
LOADABLE_SKILL_PROVIDERS.has(provider.id),
|
|
190
|
+
) ?? [];
|
|
191
|
+
if (!providers.some(provider => provider.id === "native")) {
|
|
192
|
+
throw new Error("Native skill provider is unavailable");
|
|
193
|
+
}
|
|
194
|
+
const loadContext = {
|
|
170
195
|
cwd,
|
|
171
196
|
home,
|
|
172
|
-
userAgentDir:
|
|
197
|
+
userAgentDir: agentDir,
|
|
198
|
+
profileAuthority,
|
|
173
199
|
repoRoot: await findRepoRoot(cwd),
|
|
174
|
-
}
|
|
200
|
+
};
|
|
201
|
+
const providerResults = await Promise.all(
|
|
202
|
+
providers.map(async provider => {
|
|
203
|
+
const allowedLevels = new Set<"user" | "project">();
|
|
204
|
+
if (projectTrusted) allowedLevels.add("project");
|
|
205
|
+
if (userTrusted) allowedLevels.add("user");
|
|
206
|
+
return {
|
|
207
|
+
provider,
|
|
208
|
+
result:
|
|
209
|
+
provider.id === "claude-plugins"
|
|
210
|
+
? await loadMarketplaceSkills(loadContext, allowedLevels)
|
|
211
|
+
: await provider.load(loadContext),
|
|
212
|
+
};
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
175
215
|
|
|
176
216
|
const skillMap = new Map<string, Skill>();
|
|
177
217
|
const realPathSet = new Set<string>();
|
|
@@ -197,8 +237,11 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
197
237
|
// precedence. `all` is already in native provider order with project dirs
|
|
198
238
|
// before user dirs; a stable sort by level lifts every project item above
|
|
199
239
|
// every user item, giving: project `.gjc/skills` > user roots.
|
|
200
|
-
const filteredSkills =
|
|
201
|
-
.
|
|
240
|
+
const filteredSkills = providerResults
|
|
241
|
+
.flatMap(({ provider, result }) =>
|
|
242
|
+
result.items.map(capSkill => ({ capSkill, providerPriority: provider.priority })),
|
|
243
|
+
)
|
|
244
|
+
.filter(({ capSkill }) => {
|
|
202
245
|
if (!isSourceEnabled(capSkill._source)) return false;
|
|
203
246
|
if (disabledSkillNames.has(capSkill.name)) return false;
|
|
204
247
|
if (matchesIgnorePatterns(capSkill.name)) return false;
|
|
@@ -207,12 +250,12 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
207
250
|
})
|
|
208
251
|
.sort((a, b) => {
|
|
209
252
|
const levelOrder = { project: 0, user: 1 } as const;
|
|
210
|
-
return levelOrder[a.level] - levelOrder[b.level];
|
|
253
|
+
return levelOrder[a.capSkill.level] - levelOrder[b.capSkill.level] || b.providerPriority - a.providerPriority;
|
|
211
254
|
});
|
|
212
255
|
|
|
213
256
|
// Batch resolve all real paths in parallel
|
|
214
257
|
const realPaths = await Promise.all(
|
|
215
|
-
filteredSkills.map(async capSkill => {
|
|
258
|
+
filteredSkills.map(async ({ capSkill }) => {
|
|
216
259
|
try {
|
|
217
260
|
return await fs.realpath(capSkill.path);
|
|
218
261
|
} catch {
|
|
@@ -223,7 +266,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
223
266
|
|
|
224
267
|
// Process skills with resolved paths
|
|
225
268
|
for (let i = 0; i < filteredSkills.length; i++) {
|
|
226
|
-
const capSkill = filteredSkills[i];
|
|
269
|
+
const { capSkill } = filteredSkills[i];
|
|
227
270
|
const resolvedPath = realPaths[i];
|
|
228
271
|
|
|
229
272
|
// Skip silently if we've already loaded this exact file (via symlink)
|
|
@@ -235,14 +278,14 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
235
278
|
// workflow-routing hijack: warn, and let the session merge keep the bundled
|
|
236
279
|
// definition authoritative (sdk/session.ts). User-scope copies are not
|
|
237
280
|
// warned about: installed defaults legitimately mirror bundled skills.
|
|
238
|
-
if (capSkill.level === "project" &&
|
|
281
|
+
if (capSkill.level === "project" && isCanonicalGjcWorkflowSkillFilesystemAlias(capSkill.name)) {
|
|
239
282
|
collisionWarnings.push({
|
|
240
283
|
skillPath: capSkill.path,
|
|
241
284
|
message: `name collision: "${capSkill.name}" is a bundled GJC workflow skill; the bundled definition takes precedence in sessions and this project copy is never used`,
|
|
242
285
|
});
|
|
243
286
|
}
|
|
244
287
|
|
|
245
|
-
const skillKey = capSkill.name
|
|
288
|
+
const skillKey = getSkillFilesystemIdentity(capSkill.name);
|
|
246
289
|
const existing = skillMap.get(skillKey);
|
|
247
290
|
if (existing) {
|
|
248
291
|
collisionWarnings.push({
|
|
@@ -319,7 +362,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
319
362
|
const resolvedPath = customRealPaths[i];
|
|
320
363
|
if (realPathSet.has(resolvedPath)) continue;
|
|
321
364
|
|
|
322
|
-
const skillKey = skill.name
|
|
365
|
+
const skillKey = getSkillFilesystemIdentity(skill.name);
|
|
323
366
|
const existing = skillMap.get(skillKey);
|
|
324
367
|
if (existing) {
|
|
325
368
|
collisionWarnings.push({
|
|
@@ -338,7 +381,12 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
338
381
|
|
|
339
382
|
return {
|
|
340
383
|
skills,
|
|
341
|
-
warnings: [
|
|
384
|
+
warnings: [
|
|
385
|
+
...providerResults.flatMap(({ result }) =>
|
|
386
|
+
(result.warnings ?? []).map(message => ({ skillPath: "", message })),
|
|
387
|
+
),
|
|
388
|
+
...collisionWarnings,
|
|
389
|
+
],
|
|
342
390
|
};
|
|
343
391
|
}
|
|
344
392
|
|