@kitman-sh/cli 0.7.2 → 0.7.3

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.
@@ -100,7 +100,7 @@ import {
100
100
  toCodecJson,
101
101
  Json,
102
102
  LoopCapabilities
103
- } from "./chunk-704ws2q7.js";
103
+ } from "./chunk-8g2twwm8.js";
104
104
 
105
105
  // packages/sdk/src/loop/define.ts
106
106
  var LoopOutcome = Literals(["feature", "bug", "refactor", "investigation", "docs", "migration"]);
@@ -18,13 +18,13 @@ import {
18
18
  ProgramWorkUnit,
19
19
  LoopManifest,
20
20
  RunnerRpcError
21
- } from "./chunk-704ws2q7.js";
21
+ } from "./chunk-8g2twwm8.js";
22
22
  import {
23
23
  ManifestInvalid,
24
24
  make,
25
25
  Suspended,
26
26
  LoopCheckError
27
- } from "./chunk-ztmwmk8t.js";
27
+ } from "./chunk-0296rete.js";
28
28
 
29
29
  // packages/sdk/src/program/engine.ts
30
30
  class JournalWriteFailed extends TaggedError("JournalWriteFailed") {
@@ -31,7 +31,7 @@ import {
31
31
  Int,
32
32
  TaggedError2,
33
33
  Json
34
- } from "./chunk-704ws2q7.js";
34
+ } from "./chunk-8g2twwm8.js";
35
35
  import {
36
36
  LoopOutcome,
37
37
  defineLoop,
@@ -67,7 +67,7 @@ import {
67
67
  mergeFindings,
68
68
  settleRound,
69
69
  rallyStop
70
- } from "./chunk-ztmwmk8t.js";
70
+ } from "./chunk-0296rete.js";
71
71
 
72
72
  // node_modules/.bun/effect@4.0.4/node_modules/effect/dist/workflow/DurableClock.js
73
73
  var TypeId = "~effect/workflow/DurableClock";
@@ -121,7 +121,7 @@ import {
121
121
  isStreamSchema,
122
122
  getStreamSchemas,
123
123
  exitSchema
124
- } from "./chunk-704ws2q7.js";
124
+ } from "./chunk-8g2twwm8.js";
125
125
 
126
126
  // node_modules/.bun/effect@4.0.4/node_modules/effect/dist/socket/Socket.js
127
127
  var TypeId = "~effect/socket/Socket";
@@ -11351,7 +11351,7 @@ var Proto6 = /* @__PURE__ */ Object.defineProperties(/* @__PURE__ */ Object.crea
11351
11351
  },
11352
11352
  [symbol2]: {
11353
11353
  value(that) {
11354
- return Equivalence4(this, that);
11354
+ return Equivalence3(this, that);
11355
11355
  }
11356
11356
  },
11357
11357
  [symbol]: {
@@ -11367,7 +11367,7 @@ var Proto6 = /* @__PURE__ */ Object.defineProperties(/* @__PURE__ */ Object.crea
11367
11367
  }
11368
11368
  });
11369
11369
  var make24 = (input) => Object.assign(Object.create(Proto6), input);
11370
- var Equivalence4 = /* @__PURE__ */ makeEquivalence2(/* @__PURE__ */ strictEqual());
11370
+ var Equivalence3 = /* @__PURE__ */ makeEquivalence2(/* @__PURE__ */ strictEqual());
11371
11371
  var empty3 = /* @__PURE__ */ Object.create(Proto6);
11372
11372
  var fromInput = (input) => {
11373
11373
  if (input === undefined) {
@@ -11469,7 +11469,7 @@ var Proto7 = {
11469
11469
  };
11470
11470
  },
11471
11471
  [symbol2](that) {
11472
- return Equivalence5(this, that);
11472
+ return Equivalence4(this, that);
11473
11473
  },
11474
11474
  [symbol]() {
11475
11475
  return array(this.params.flat());
@@ -11517,7 +11517,7 @@ var fromInputNested = (input) => {
11517
11517
  }
11518
11518
  return out;
11519
11519
  };
11520
- var Equivalence5 = /* @__PURE__ */ make((a, b) => arrayEquivalence(a.params, b.params));
11520
+ var Equivalence4 = /* @__PURE__ */ make((a, b) => arrayEquivalence(a.params, b.params));
11521
11521
  var arrayEquivalence = /* @__PURE__ */ makeEquivalence3(/* @__PURE__ */ makeEquivalence([/* @__PURE__ */ strictEqual(), /* @__PURE__ */ strictEqual()]));
11522
11522
  var empty6 = /* @__PURE__ */ make25([]);
11523
11523
  var setAll2 = /* @__PURE__ */ dual(2, (self, input) => {
@@ -19841,10 +19841,11 @@ var RunnerReplicaDrainState = Union([
19841
19841
  Literal("draining"),
19842
19842
  Literal("drained")
19843
19843
  ]);
19844
+ var RunnerProjectAccess = Union([Literal("all"), Literal("selected")]);
19844
19845
  var RunnerOwnership = Union([
19845
19846
  Struct({
19846
19847
  kind: Literal("workspace"),
19847
- projectAccess: Union([Literal("all"), Literal("selected")])
19848
+ projectAccess: RunnerProjectAccess
19848
19849
  }),
19849
19850
  Struct({
19850
19851
  kind: Literal("project"),
@@ -19852,7 +19853,8 @@ var RunnerOwnership = Union([
19852
19853
  }),
19853
19854
  Struct({
19854
19855
  kind: Literal("personal"),
19855
- ownerUserId: Id
19856
+ ownerUserId: Id,
19857
+ projectAccess: RunnerProjectAccess.pipe(withDecodingDefaultKey(succeed5("all")))
19856
19858
  })
19857
19859
  ]);
19858
19860
  var RunnerSecretProviderType = Union([Literal("env"), Literal("file"), Literal("exec")]);
@@ -20086,6 +20088,12 @@ var RunnerProjectGrantCreate = Struct({
20086
20088
  var RunnerRescope = Struct({
20087
20089
  ownership: RunnerOwnership
20088
20090
  });
20091
+ var RunnerUpdate = Struct({
20092
+ name: String2.check(isMinLength(1), isMaxLength(120))
20093
+ });
20094
+ var RunnerRemoveResult = Struct({
20095
+ removed: Boolean2
20096
+ });
20089
20097
  var RunnerDefaultHarnessInput = Struct({
20090
20098
  harness: NullOr(Literals(DetectedHarnessIds))
20091
20099
  });
@@ -21708,6 +21716,7 @@ function parseCliConfig(value) {
21708
21716
  const ids = (field) => Array.isArray(field) && field.every(isString2) ? field : undefined;
21709
21717
  const onboarding = isJsonObject(value.onboarding) ? value.onboarding : undefined;
21710
21718
  const mcpSkippedProjectIds = ids(onboarding?.mcpSkippedProjectIds);
21719
+ const approvedSkillSources = ids(value.approvedSkillSources);
21711
21720
  return {
21712
21721
  ...text("apiUrl"),
21713
21722
  ...text("activeWorkspaceId"),
@@ -21715,6 +21724,7 @@ function parseCliConfig(value) {
21715
21724
  ...text("mcpCredentialName"),
21716
21725
  ...UserAgentPreferencesValidator(value.userAgentPreferences) ? { userAgentPreferences: value.userAgentPreferences } : undefined,
21717
21726
  ...AgentSyncProfileValidator(value.agentSyncProfile) ? { agentSyncProfile: value.agentSyncProfile } : undefined,
21727
+ ...approvedSkillSources === undefined ? undefined : { approvedSkillSources },
21718
21728
  ...onboarding === undefined ? undefined : {
21719
21729
  onboarding: {
21720
21730
  ...mcpSkippedProjectIds === undefined ? undefined : { mcpSkippedProjectIds }
@@ -23193,6 +23203,9 @@ class KitmanEffectClient {
23193
23203
  const suffix = page === undefined ? "" : `?page=${page}`;
23194
23204
  return this.request("listCodeHostRepositories", `/workspaces/${segment(workspaceId)}/code-host-connections/${segment(connectionId)}/repositories${suffix}`, { headers: this.headers() });
23195
23205
  }
23206
+ listCodeHostGroups(workspaceId, connectionId) {
23207
+ return this.request("listCodeHostGroups", `/workspaces/${segment(workspaceId)}/code-host-connections/${segment(connectionId)}/groups`, { headers: this.headers() });
23208
+ }
23196
23209
  bindCodeHostRepositories(workspaceId, connectionId, input) {
23197
23210
  return this.request("bindCodeHostRepositories", `/workspaces/${segment(workspaceId)}/code-host-connections/${segment(connectionId)}/repo-bindings`, { method: "POST", headers: this.headers(true), body: JSON.stringify(input) });
23198
23211
  }
@@ -24164,6 +24177,16 @@ class KitmanEffectClient {
24164
24177
  body: JSON.stringify(input)
24165
24178
  });
24166
24179
  }
24180
+ updateRunner(runnerId, input) {
24181
+ return this.request("updateRunner", `/runners/${segment(runnerId)}`, {
24182
+ method: "PATCH",
24183
+ headers: this.headers(true),
24184
+ body: JSON.stringify(input)
24185
+ });
24186
+ }
24187
+ removeRunner(runnerId) {
24188
+ return this.request("removeRunner", `/runners/${segment(runnerId)}`, { method: "DELETE", headers: this.headers() });
24189
+ }
24167
24190
  setRunnerDefaultHarness(runnerId, input) {
24168
24191
  return this.request("setRunnerDefaultHarness", `/runners/${segment(runnerId)}/default-harness`, {
24169
24192
  method: "PUT",
@@ -10,7 +10,7 @@ import {
10
10
  RunnerHeaders,
11
11
  RunnerRpcProtocolVersion,
12
12
  RunnerRpcs
13
- } from "./chunk-704ws2q7.js";
13
+ } from "./chunk-8g2twwm8.js";
14
14
  import {
15
15
  WebSocketConstructor,
16
16
  layerWebSocketConstructorGlobal,
@@ -19,7 +19,7 @@ import {
19
19
  make5,
20
20
  withHeaders,
21
21
  layerProtocolSocket
22
- } from "./chunk-dwgta18y.js";
22
+ } from "./chunk-84t0s0s2.js";
23
23
 
24
24
  // packages/live/src/rpc/runner-client.ts
25
25
  var proxiedWebSocket = (proxy) => succeed4(WebSocketConstructor)((target) => {
@@ -161,7 +161,7 @@ import {
161
161
  herdrRunnerCapabilitiesForRepoBinding2,
162
162
  herdrRunnerCapabilities2,
163
163
  assertHerdrRunnerCapability2
164
- } from "./chunk-704ws2q7.js";
164
+ } from "./chunk-8g2twwm8.js";
165
165
  import {
166
166
  make4,
167
167
  readerString,
@@ -172,17 +172,17 @@ import {
172
172
  SocketUpgradeError,
173
173
  SocketCloseError,
174
174
  SocketError
175
- } from "./chunk-dwgta18y.js";
176
- import"./chunk-ztmwmk8t.js";
175
+ } from "./chunk-84t0s0s2.js";
176
+ import"./chunk-0296rete.js";
177
177
  import {
178
178
  programLease,
179
179
  programHostsEnv,
180
180
  undeclaredHostsEnv
181
- } from "./chunk-6kdj8psf.js";
182
- import"./chunk-ae7gx971.js";
181
+ } from "./chunk-1n61xbaq.js";
182
+ import"./chunk-c5nwtn11.js";
183
183
  import {
184
184
  nextInt
185
- } from "./chunk-6f4anyec.js";
185
+ } from "./chunk-x6j2j12j.js";
186
186
 
187
187
  // packages/core/src/runner-trust.ts
188
188
  import { createHash } from "crypto";
@@ -195,6 +195,129 @@ function sha256(value) {
195
195
  return createHash("sha256").update(value, "utf8").digest("hex");
196
196
  }
197
197
 
198
+ // packages/core/src/engineering-resources.ts
199
+ var marketplace = {
200
+ source: "kitman-engineering-loop",
201
+ publisher: "Kitman Platform",
202
+ visibility: "public",
203
+ trusted: true,
204
+ reviewStatus: "approved",
205
+ installable: true,
206
+ rollout: { channel: "default", installDefault: true, preservesExistingInstallations: true }
207
+ };
208
+ var kit = (input) => ({
209
+ ...input,
210
+ metadata: { marketplace }
211
+ });
212
+ var MattPocockProcessKit = kit({
213
+ name: "Matt Pocock Process",
214
+ slug: "matt-pocock-process",
215
+ kind: "process",
216
+ description: "Adversarial, type-safe implementation and review practice for Kitman Engineering.",
217
+ contentMarkdown: [
218
+ "# Matt Pocock Process",
219
+ "",
220
+ "Use fresh-context review, TDD, Effect-native boundaries, and explicit type contracts.",
221
+ "",
222
+ "## Complete skill collection",
223
+ "",
224
+ "diagnosing-bugs, tdd, effect-ts, codebase-design, api-and-interface-design, improve-codebase-architecture, doubt-driven-development, documentation-and-adrs, postgres-drizzle, security-and-hardening, source-driven-development, test-driven-development, research."
225
+ ].join(`
226
+ `),
227
+ agentEnvironment: {
228
+ skills: [
229
+ {
230
+ name: "research",
231
+ policy: "required",
232
+ source: {
233
+ kind: "git",
234
+ url: "https://github.com/mattpocock/skills.git",
235
+ ref: "49dd158d1076134a641b33efb035946536778336",
236
+ path: "skills/engineering/research"
237
+ }
238
+ }
239
+ ]
240
+ },
241
+ agentInstructions: "Prefer the smallest correct change, preserve evidence, and stop on ambiguous public-interface decisions.",
242
+ doNotDo: "Do not execute user-controlled code in the API or Workflow worker."
243
+ });
244
+ var AddyOsmaniProcessKit = kit({
245
+ name: "Addy Osmani Process",
246
+ slug: "addy-osmani-process",
247
+ kind: "process",
248
+ description: "Incremental, accessible, observable, and production-focused delivery practice for Kitman Engineering.",
249
+ contentMarkdown: [
250
+ "# Addy Osmani Process",
251
+ "",
252
+ "Use incremental implementation, browser evidence, accessibility checks, CI discipline, and simplification.",
253
+ "",
254
+ "## Complete skill collection",
255
+ "",
256
+ "incremental-implementation, frontend-ui-engineering, browser-testing-with-devtools, web-design-guidelines, ci-cd-and-automation, code-review-and-quality, code-simplification, observability-and-instrumentation, performance-optimization, shipping-and-launch, git-workflow-and-versioning."
257
+ ].join(`
258
+ `),
259
+ agentInstructions: "Prefer verifiable slices, accessible interfaces, and evidence that survives handoff.",
260
+ doNotDo: "Do not hide failures behind broad fallbacks or claim production evidence from local fixtures."
261
+ });
262
+ var PonytailProcessKit = kit({
263
+ name: "Ponytail",
264
+ slug: "ponytail",
265
+ kind: "process",
266
+ description: "Removes unnecessary code and complexity before implementation and review complete.",
267
+ contentMarkdown: `# Ponytail
268
+
269
+ Prefer deletion, existing code, and the smallest correct change. Avoid speculative abstractions.`,
270
+ agentEnvironment: {
271
+ plugins: [
272
+ {
273
+ name: "ponytail",
274
+ policy: "required",
275
+ source: {
276
+ kind: "package",
277
+ packageName: "@dietrichgebert/ponytail",
278
+ version: "4.9.0"
279
+ }
280
+ }
281
+ ]
282
+ },
283
+ agentInstructions: "Use the required Ponytail skill before completing implementation or review.",
284
+ doNotDo: "Do not add an abstraction, dependency, or configuration without a present requirement."
285
+ });
286
+ var EngineeringTechnologyKitCatalog = [
287
+ kit({
288
+ name: "Backend",
289
+ slug: "backend",
290
+ kind: "technology",
291
+ description: "Effect, Elysia, PostgreSQL, Drizzle, Better Auth, and backend practices.",
292
+ contentMarkdown: `# Backend
293
+
294
+ Effect, Elysia, PostgreSQL, Drizzle, Better Auth, API contracts, security, and observability.`
295
+ }),
296
+ kit({
297
+ name: "Frontend",
298
+ slug: "frontend",
299
+ kind: "technology",
300
+ description: "Nuxt, Vue, Nuxt UI, TanStack Query, VueUse, accessibility, and browser testing.",
301
+ contentMarkdown: `# Frontend
302
+
303
+ Nuxt, Vue, Nuxt UI, TanStack Query, VueUse, accessibility, responsive UI, and browser testing.`
304
+ }),
305
+ kit({
306
+ name: "DevOps",
307
+ slug: "devops",
308
+ kind: "technology",
309
+ description: "Kubernetes, Kustomize, GitLab CI, and multi-stage container builds.",
310
+ contentMarkdown: `# DevOps
311
+
312
+ Kubernetes, Kustomize, GitLab CI, multi-stage container builds, and operational verification.`
313
+ })
314
+ ];
315
+ var EngineeringProcessKitCatalog = [
316
+ MattPocockProcessKit,
317
+ AddyOsmaniProcessKit,
318
+ PonytailProcessKit
319
+ ];
320
+
198
321
  // packages/agent-runtime/src/agent-environment.ts
199
322
  class AgentEnvironmentResolutionError extends TaggedError("AgentEnvironmentResolutionError") {
200
323
  get message() {
@@ -1596,6 +1719,9 @@ function repositoryUrl(repoBinding) {
1596
1719
  return repoBinding.repositoryUrl ?? `https://${repoBinding.host}/${repoBinding.owner}/${repoBinding.name}.git`;
1597
1720
  }
1598
1721
  // packages/agent-runtime/src/skill-materializer.ts
1722
+ var gitSourceKey = (source) => `${source.url}#${source.ref}:${source.path ?? ""}`;
1723
+ var reviewedGitSkillSources = new Set([...EngineeringProcessKitCatalog, ...EngineeringTechnologyKitCatalog].flatMap((kit) => kit.agentEnvironment?.skills ?? []).flatMap((skill) => skill.source.kind === "git" ? [gitSourceKey(skill.source)] : []));
1724
+
1599
1725
  class AgentEnvironmentMaterializationError extends TaggedError("AgentEnvironmentMaterializationError") {
1600
1726
  get message() {
1601
1727
  return `AgentEnvironment materialization failed: ${this.issues.map((issue) => issue.message).join("; ")}`;
@@ -1616,35 +1742,50 @@ var previewAgentEnvironmentMaterialization = fnUntraced(function* (input) {
1616
1742
  for (const skill of input.environment.skills) {
1617
1743
  const skillKey = skillIdentity(skill.item);
1618
1744
  const source = skill.item.source;
1619
- const skip = (message) => {
1620
- const issue = { message, skillName: skill.item.name, skillKey };
1745
+ const fromKit = skill.source.label === undefined ? "" : ` (from Kit ${skill.source.label})`;
1746
+ const problem = source.kind === "kitman" ? source.contentMarkdown === undefined ? `Kitman skill ${skill.item.name} has no contentMarkdown to materialize.` : undefined : input.noInstall === true ? `Skipping materialization for ${skill.item.name} because sync was run with noInstall.` : source.kind !== "git" ? `Skill ${skill.item.name}${fromKit} comes from ${source.kind}, which Kitman cannot install yet; only Kitman and git skills are installed. Remove it from the Kit or make it recommended.` : !source.url.startsWith("https://") ? `Git skill ${skill.item.name} must come from an https URL, not ${source.url}.` : !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(source.ref) ? `Git skill ${skill.item.name} must be pinned to a commit, not ${source.ref}, so Kitman installs only the reviewed version.` : !reviewedGitSkillSources.has(gitSourceKey(source)) && !(input.approvedSkillSources ?? []).includes(gitSourceKey(source)) ? `Git skill ${skill.item.name}${fromKit} at ${gitSourceKey(source)} is not one Kitman reviewed; approve it by adding that source to approvedSkillSources in your Kitman user config, or remove it from the Kit.` : undefined;
1747
+ if (problem !== undefined) {
1748
+ const issue = { message: problem, skillName: skill.item.name, skillKey };
1621
1749
  if (skill.item.policy === "required")
1622
- return new AgentEnvironmentMaterializationError({ issues: [issue] });
1750
+ return yield* new AgentEnvironmentMaterializationError({ issues: [issue] });
1623
1751
  warnings.push(issue);
1624
- return;
1625
- };
1626
- const refusal = source.kind !== "kitman" ? skip(input.noInstall === true ? `Skipping materialization for ${skill.item.name} because sync was run with noInstall.` : `Materializing ${skill.item.name} from ${source.kind} is not supported; only Kitman skills are materialized.`) : source.contentMarkdown === undefined ? skip(`Kitman skill ${skill.item.name} has no contentMarkdown to materialize.`) : undefined;
1627
- if (refusal !== undefined)
1628
- return yield* refusal;
1629
- if (source.kind !== "kitman" || source.contentMarkdown === undefined)
1630
1752
  continue;
1631
- const skillPath = path.join(sourceRoot, "kitman", hashSegment(source.skillId ?? source.name ?? skill.item.name), skillSlug2(skill.item.name));
1632
- operations.push({
1633
- kind: "write_skill",
1634
- skillName: skill.item.name,
1635
- skillKey,
1636
- path: path.join(skillPath, "SKILL.md"),
1637
- content: skillMarkdown(skill.item.name, source.contentMarkdown)
1638
- }, {
1639
- kind: "symlink_skill",
1640
- skillName: skill.item.name,
1641
- skillKey,
1642
- sourcePath: skillPath,
1643
- targetPath: path.join(targetRoot, `${skillSlug2(skill.item.name)}-${hashSegment(skillKey)}`),
1644
- sourceRoot,
1645
- targetRoot
1646
- });
1647
- materializedSkills.push({ name: skill.item.name, key: skillKey, harness, source, skillPath, managed: true });
1753
+ }
1754
+ const expose = (skillPath) => {
1755
+ operations.push({
1756
+ kind: "symlink_skill",
1757
+ skillName: skill.item.name,
1758
+ skillKey,
1759
+ sourcePath: skillPath,
1760
+ targetPath: path.join(targetRoot, `${skillSlug2(skill.item.name)}-${hashSegment(skillKey)}`),
1761
+ sourceRoot,
1762
+ targetRoot
1763
+ });
1764
+ materializedSkills.push({ name: skill.item.name, key: skillKey, harness, source, skillPath, managed: true });
1765
+ };
1766
+ if (source.kind === "git") {
1767
+ const skillPath = path.join(sourceRoot, "git", hashSegment(skillKey), skillSlug2(skill.item.name));
1768
+ operations.push({
1769
+ kind: "fetch_git_skill",
1770
+ skillName: skill.item.name,
1771
+ skillKey,
1772
+ url: source.url,
1773
+ ref: source.ref,
1774
+ ...source.path === undefined ? undefined : { repositoryPath: source.path },
1775
+ path: skillPath
1776
+ });
1777
+ expose(skillPath);
1778
+ } else if (source.kind === "kitman" && source.contentMarkdown !== undefined) {
1779
+ const skillPath = path.join(sourceRoot, "kitman", hashSegment(source.skillId ?? source.name ?? skill.item.name), skillSlug2(skill.item.name));
1780
+ operations.push({
1781
+ kind: "write_skill",
1782
+ skillName: skill.item.name,
1783
+ skillKey,
1784
+ path: path.join(skillPath, "SKILL.md"),
1785
+ content: skillMarkdown(skill.item.name, source.contentMarkdown)
1786
+ });
1787
+ expose(skillPath);
1788
+ }
1648
1789
  }
1649
1790
  return { harness, operations, materializedSkills, warnings };
1650
1791
  });
@@ -1652,28 +1793,74 @@ var applyAgentEnvironmentMaterialization = fnUntraced(function* (plan) {
1652
1793
  for (const operation of plan.operations) {
1653
1794
  if (operation.kind === "write_skill")
1654
1795
  yield* writeSkill(plan, operation);
1796
+ else if (operation.kind === "fetch_git_skill")
1797
+ yield* fetchGitSkill(plan, operation);
1655
1798
  else
1656
1799
  yield* exposeSkill(operation);
1657
1800
  }
1658
1801
  return plan;
1659
1802
  });
1660
1803
  var refuse = (message) => fail4(new SkillMaterializationRefused({ message }));
1661
- var writeSkill = (plan, operation) => gen(function* () {
1662
- const fs = yield* FileSystem;
1804
+ var managedSkillPath = (plan, skillKey, file) => gen(function* () {
1663
1805
  const path = yield* Path;
1664
- const boundary = plan.operations.find((candidate) => candidate.kind === "symlink_skill" && candidate.skillKey === operation.skillKey);
1806
+ const boundary = plan.operations.find((candidate) => candidate.kind === "symlink_skill" && candidate.skillKey === skillKey);
1665
1807
  if (boundary === undefined)
1666
1808
  return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
1667
1809
  const root = path.resolve(boundary.sourceRoot);
1668
- const target = path.resolve(operation.path);
1810
+ const target = path.resolve(file);
1669
1811
  if (!isPathWithin(path, root, target) || target === root)
1670
1812
  return yield* refuse("SKILL_WRITE_OUTSIDE_ROOT");
1671
1813
  yield* assertNoSymlinkComponents(path.dirname(target), "SKILL_WRITE_OUTSIDE_ROOT");
1814
+ return target;
1815
+ });
1816
+ var writeSkill = (plan, operation) => gen(function* () {
1817
+ const fs = yield* FileSystem;
1818
+ const path = yield* Path;
1819
+ const target = yield* managedSkillPath(plan, operation.skillKey, operation.path);
1672
1820
  if (yield* isSymbolicLink(target))
1673
1821
  return yield* refuse("SKILL_WRITE_SYMLINK");
1674
1822
  yield* fs.makeDirectory(path.dirname(target), { recursive: true });
1675
1823
  yield* fs.writeFileString(target, operation.content);
1676
1824
  });
1825
+ var fetchGitSkill = (plan, operation) => gen(function* () {
1826
+ const fs = yield* FileSystem;
1827
+ const path = yield* Path;
1828
+ const target = yield* managedSkillPath(plan, operation.skillKey, operation.path);
1829
+ const scratch = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-git-skill-" });
1830
+ const repository = path.join(scratch, "repository");
1831
+ const tree = path.join(scratch, "tree");
1832
+ const git = (...args) => runCommand2("git", ["--git-dir", repository, ...args]);
1833
+ yield* fs.makeDirectory(tree);
1834
+ yield* runCommand2("git", ["init", "--quiet", "--bare", repository]);
1835
+ yield* git("fetch", "--quiet", "--depth", "1", "--", operation.url, operation.ref);
1836
+ yield* git("--work-tree", tree, "checkout", "FETCH_HEAD", "--", ".");
1837
+ const skillDirectory = path.resolve(tree, operation.repositoryPath ?? ".");
1838
+ if (!isPathWithin(path, tree, skillDirectory))
1839
+ return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
1840
+ if (yield* containsSymbolicLink(tree, skillDirectory))
1841
+ return yield* refuse(`Git skill ${operation.skillName} links to a file outside itself in ${operation.url}@${operation.ref}; Kitman does not install linked files.`);
1842
+ if (!(yield* fs.exists(path.join(skillDirectory, "SKILL.md"))))
1843
+ return yield* refuse(`Git skill ${operation.skillName} has no SKILL.md at ${operation.repositoryPath ?? "the repository root"} in ${operation.url}@${operation.ref}.`);
1844
+ const staged = `${target}.partial`;
1845
+ yield* fs.remove(staged, { recursive: true, force: true });
1846
+ yield* fs.makeDirectory(path.dirname(target), { recursive: true });
1847
+ yield* fs.copy(skillDirectory, staged);
1848
+ yield* fs.remove(target, { recursive: true, force: true });
1849
+ yield* fs.rename(staged, target);
1850
+ }).pipe(scoped);
1851
+ var containsSymbolicLink = (root, directory) => gen(function* () {
1852
+ const fs = yield* FileSystem;
1853
+ const path = yield* Path;
1854
+ for (let current = directory;current !== root; current = path.dirname(current))
1855
+ if (yield* isSymbolicLink(current))
1856
+ return true;
1857
+ if (!(yield* fs.exists(directory)))
1858
+ return false;
1859
+ for (const entry of yield* fs.readDirectory(directory, { recursive: true }))
1860
+ if (yield* isSymbolicLink(path.join(directory, entry)))
1861
+ return true;
1862
+ return false;
1863
+ });
1677
1864
  var exposeSkill = (operation) => gen(function* () {
1678
1865
  const fs = yield* FileSystem;
1679
1866
  const path = yield* Path;
@@ -4368,7 +4555,8 @@ var previewAgentEnvironmentPlan = fnUntraced(function* (input) {
4368
4555
  ...input.rootDir === undefined ? undefined : { rootDir: input.rootDir },
4369
4556
  ...input.scope === undefined ? undefined : { scope: input.scope },
4370
4557
  ...input.cacheRoot === undefined ? undefined : { cacheRoot: input.cacheRoot },
4371
- ...input.noInstall === undefined ? undefined : { noInstall: input.noInstall }
4558
+ ...input.noInstall === undefined ? undefined : { noInstall: input.noInstall },
4559
+ ...input.approvedSkillSources === undefined ? undefined : { approvedSkillSources: input.approvedSkillSources }
4372
4560
  });
4373
4561
  const harnessConfig = yield* previewAgentEnvironmentConfigSync({
4374
4562
  environment: input.environment,