@try-works/dsh-recursive-mode 0.3.0 → 0.3.1

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/lib/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export * from './policy.ts';
23
23
  export * from './snapshot.ts';
24
24
  export * from './live-route.ts';
25
25
  export * from './teams-loop.ts';
26
+ export * from './skills.ts';
26
27
  /**
27
28
  * Bundle plugin entry. The Loader activates this row once `tools` is available
28
29
  * (`inject` below); the RecursiveRuntime service is constructed directly so it
package/lib/index.js CHANGED
@@ -6712,12 +6712,12 @@ function createRecursiveAuditTeamTool(teams) {
6712
6712
  * references/ (never inlined TS string literals) and resolved relative to this
6713
6713
  * module (package install location), never process.cwd().
6714
6714
  */
6715
- const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
6715
+ const MODULE_DIR$1 = dirname(fileURLToPath(import.meta.url));
6716
6716
  /** Package root: <package>/src/.. — references/ sits next to src/. */
6717
- const PACKAGE_ROOT = join(MODULE_DIR, "..");
6717
+ const PACKAGE_ROOT$1 = join(MODULE_DIR$1, "..");
6718
6718
  /** references/ root (shipped package files). */
6719
6719
  function referencesRoot() {
6720
- return join(PACKAGE_ROOT, "references");
6720
+ return join(PACKAGE_ROOT$1, "references");
6721
6721
  }
6722
6722
  const M = {
6723
6723
  recursiveStart: "<!-- RECURSIVE-MODE-CANONICAL:START -->",
@@ -7626,6 +7626,91 @@ function mountRecursiveRoutesOnce(packageName, makeRoutes, webServer) {
7626
7626
  };
7627
7627
  }
7628
7628
  //#endregion
7629
+ //#region src/skills.ts
7630
+ /**
7631
+ * Packaged `recursive-mode` skill (dsh plugin standard).
7632
+ *
7633
+ * Ships the workflow's operating contract as a bundled skill through
7634
+ * `ctx.skills.registerProvider(...)` — the same shape as the shipped
7635
+ * `dsh-skill-badge` provider: a `bundled` candidate at `BUNDLED_SKILL_RANK`
7636
+ * (600), body read from the package's shipped `skills/recursive-mode/SKILL.md`
7637
+ * (never an inlined TS string literal), with a directory resource base so the
7638
+ * skill can resolve its own assets. Skills are optional instructions, not
7639
+ * session events: this emits nothing and never appends a recursive/* event.
7640
+ *
7641
+ * Optionality: `skills` is a host-plane registry; when the composition has
7642
+ * none, this is a no-op (returns undefined) rather than failing boot.
7643
+ */
7644
+ /** Package root: <package>/src/.. — skills/ sits next to src/. */
7645
+ const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
7646
+ const PACKAGE_ROOT = join(MODULE_DIR, "..");
7647
+ /** Precedence rank for packaged/bundled skills (mirrors `BUNDLED_SKILL_RANK`). */
7648
+ const BUNDLED_SKILL_RANK = 600;
7649
+ const SKILL_NAME = "recursive-mode";
7650
+ const PROVIDER_NAME = "recursive-mode";
7651
+ const SKILL_BODY_PATH = join(PACKAGE_ROOT, "skills", SKILL_NAME, "SKILL.md");
7652
+ const SKILL_RESOURCE_BASE = join(PACKAGE_ROOT, "skills", SKILL_NAME);
7653
+ const SKILL_DESCRIPTION = "Drive the recursive-mode workflow: an audit-gated, phase-disciplined loop that carries one requirement from AS-IS through TO-BE plan, implementation, test, and manual QA to locked control-plane state and durable memory. Use whenever the user asks to implement, resume, or advance a recursive run, or when a repo carries a /.recursive/ control plane.";
7654
+ /** Invocation policy: both the model catalog and user `/name` surfaces may load it. */
7655
+ const INVOCATION = {
7656
+ modelInvocable: true,
7657
+ userInvocable: true
7658
+ };
7659
+ /** The bundled candidate, stable across every `list()` call. */
7660
+ function candidate() {
7661
+ return {
7662
+ name: SKILL_NAME,
7663
+ description: SKILL_DESCRIPTION,
7664
+ invocation: INVOCATION,
7665
+ provider: PROVIDER_NAME,
7666
+ source: "bundled",
7667
+ resourceBase: {
7668
+ kind: "directory",
7669
+ path: SKILL_RESOURCE_BASE
7670
+ },
7671
+ rank: BUNDLED_SKILL_RANK,
7672
+ locator: SKILL_BODY_PATH,
7673
+ path: SKILL_BODY_PATH
7674
+ };
7675
+ }
7676
+ /** Read the shipped operating-contract body (fail loud if the package lost it). */
7677
+ function readBody() {
7678
+ return readFileSync(SKILL_BODY_PATH, "utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7679
+ }
7680
+ /** The one bundled provider this plugin contributes. */
7681
+ function provider() {
7682
+ return {
7683
+ name: PROVIDER_NAME,
7684
+ list: () => Promise.resolve([candidate()]),
7685
+ get: async () => ({
7686
+ name: SKILL_NAME,
7687
+ description: SKILL_DESCRIPTION,
7688
+ invocation: INVOCATION,
7689
+ provider: PROVIDER_NAME,
7690
+ source: "bundled",
7691
+ resourceBase: {
7692
+ kind: "directory",
7693
+ path: SKILL_RESOURCE_BASE
7694
+ },
7695
+ content: readBody(),
7696
+ path: SKILL_BODY_PATH
7697
+ })
7698
+ };
7699
+ }
7700
+ /**
7701
+ * Register the packaged `recursive-mode` skill into the host `skills` registry.
7702
+ *
7703
+ * Reads `ctx.get('skills')` optionally (the registry is host-plane; a
7704
+ * composition without it is valid). On success returns the exact disposer that
7705
+ * unregisters the provider; on absence returns undefined (a no-op, never a boot
7706
+ * failure).
7707
+ */
7708
+ function registerRecursiveSkill(ctx) {
7709
+ const skills = ctx.get("skills");
7710
+ if (skills === void 0 || skills === null) return void 0;
7711
+ return skills.registerProvider(() => provider());
7712
+ }
7713
+ //#endregion
7629
7714
  //#region src/index.ts
7630
7715
  const name = "@try-works/dsh-recursive-mode";
7631
7716
  /**
@@ -7656,7 +7741,9 @@ function apply(ctx, config) {
7656
7741
  const repairedRoots = /* @__PURE__ */ new Set();
7657
7742
  const reminderGate = new ReminderOnceGate();
7658
7743
  const agentTeams = ctx.get("agentTeams");
7744
+ const skillDisposer = registerRecursiveSkill(ctx);
7659
7745
  const disposers = [
7746
+ ...skillDisposer ? [skillDisposer] : [],
7660
7747
  ctx.tools.register(createRecursiveStatusTool(recursive)),
7661
7748
  ctx.tools.register(createRecursiveInitTool(recursive)),
7662
7749
  ctx.tools.register(createRecursiveLockTool(recursive)),
@@ -7775,4 +7862,4 @@ function apply(ctx, config) {
7775
7862
  });
7776
7863
  }
7777
7864
  //#endregion
7778
- export { DEFAULT_ENFORCEMENT, OPTIONAL_PHASES, PHASES, PHASE_SEQUENCE, RECURSIVE_API_PREFIX, RUN_ARTIFACT_SEQUENCE, RUN_STATES, RecursiveRuntime, apply, auditToPass, buildDelegationPrompt, buildReviewBundle, capabilityProbe, childScratchPath, coerceAskToDecision, contentSha256, coupleGateBlockToGoal, createChildBrief, createHandoff, createRecursiveCloseoutTool, createRecursiveInitTool, createRecursiveLintTool, createRecursiveLockTool, createRecursivePhaseTool, createRecursiveScratchTool, createRecursiveStatusTool, createRecursiveWorktreeTool, defaultReviewToolFilter, delegate, delegateContinuable, delegationDecisionBasis, delegationError, detectTamper, discoverRuns, drainContinuableChildren, drainContinuableDescendants, escapeRegExp, evaluateDelegationResult, evaluateToolGuard, foldRun, foldRunCard, getAllStaleReceipts, getArtifactState, getGateStatus, getLatestRunDirectory, getLockStatus, getMdFieldValue, getNextLegalPhase, getPrerequisiteBlockers, getPrerequisites, getStaleDownstreamPhases, getTodoStats, getWorkflowProfile, inject, interruptContinuable, invalidateReceipt, isCoreArtifact, isTaskClaimedBy, loadRouterPolicy, lockHashFromContent, makeRecursiveRoutes, mountRecursiveRoutesOnce, name, normalizeForLockHash, phaseIndex, probeCapabilities, readReceipt, readRepairFromStructured, readVerdictFromStructured, receiptPath, renderRecursivePolicy, renderTaskHistory, replyPath, resolveEnforcementConfig, resolveRole, resolveRunDir, reviewBundleDir, reviewOutputSchema, routerPolicyPath, snapshotWorkspace, trimMdValue, validateChain, validateReferences, validateTransition, writeActionRecord, writeReceipt };
7865
+ export { DEFAULT_ENFORCEMENT, OPTIONAL_PHASES, PHASES, PHASE_SEQUENCE, RECURSIVE_API_PREFIX, RUN_ARTIFACT_SEQUENCE, RUN_STATES, RecursiveRuntime, apply, auditToPass, buildDelegationPrompt, buildReviewBundle, capabilityProbe, childScratchPath, coerceAskToDecision, contentSha256, coupleGateBlockToGoal, createChildBrief, createHandoff, createRecursiveCloseoutTool, createRecursiveInitTool, createRecursiveLintTool, createRecursiveLockTool, createRecursivePhaseTool, createRecursiveScratchTool, createRecursiveStatusTool, createRecursiveWorktreeTool, defaultReviewToolFilter, delegate, delegateContinuable, delegationDecisionBasis, delegationError, detectTamper, discoverRuns, drainContinuableChildren, drainContinuableDescendants, escapeRegExp, evaluateDelegationResult, evaluateToolGuard, foldRun, foldRunCard, getAllStaleReceipts, getArtifactState, getGateStatus, getLatestRunDirectory, getLockStatus, getMdFieldValue, getNextLegalPhase, getPrerequisiteBlockers, getPrerequisites, getStaleDownstreamPhases, getTodoStats, getWorkflowProfile, inject, interruptContinuable, invalidateReceipt, isCoreArtifact, isTaskClaimedBy, loadRouterPolicy, lockHashFromContent, makeRecursiveRoutes, mountRecursiveRoutesOnce, name, normalizeForLockHash, phaseIndex, probeCapabilities, readReceipt, readRepairFromStructured, readVerdictFromStructured, receiptPath, registerRecursiveSkill, renderRecursivePolicy, renderTaskHistory, replyPath, resolveEnforcementConfig, resolveRole, resolveRunDir, reviewBundleDir, reviewOutputSchema, routerPolicyPath, snapshotWorkspace, trimMdValue, validateChain, validateReferences, validateTransition, writeActionRecord, writeReceipt };
@@ -0,0 +1,70 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /** One skill contribution, mirrored from the `dsh-skill` registry contract. */
3
+ export interface SkillInvocationPolicyLike {
4
+ readonly modelInvocable: boolean;
5
+ readonly userInvocable: boolean;
6
+ }
7
+ /** Provider-owned base for relative resource resolution. */
8
+ export type SkillResourceBaseLike = {
9
+ readonly kind: 'directory';
10
+ readonly path: string;
11
+ } | {
12
+ readonly kind: 'url';
13
+ readonly url: string;
14
+ } | {
15
+ readonly kind: 'opaque';
16
+ readonly description: string;
17
+ };
18
+ /** Invocation-neutral summary fields shared by candidates and definitions. */
19
+ export interface SkillSummaryLike {
20
+ readonly name: string;
21
+ readonly description: string;
22
+ readonly whenToUse?: string;
23
+ readonly invocation: SkillInvocationPolicyLike;
24
+ readonly source: string;
25
+ readonly provider: string;
26
+ readonly resourceBase?: SkillResourceBaseLike;
27
+ }
28
+ /** Provider catalog entry: a summary plus rank and an opaque locator. */
29
+ export interface SkillCandidateLike extends SkillSummaryLike {
30
+ readonly rank: number;
31
+ readonly locator: unknown;
32
+ readonly path?: string;
33
+ }
34
+ /** Complete loaded skill: a summary plus the markdown instruction body. */
35
+ export interface SkillDefinitionLike extends SkillSummaryLike {
36
+ readonly content: string;
37
+ readonly path?: string;
38
+ }
39
+ /** Lookup options passed to provider `list`/`get`. */
40
+ export interface SkillLookupOptionsLike {
41
+ readonly cwd?: string | undefined;
42
+ readonly signal?: AbortSignal | undefined;
43
+ }
44
+ /** Registration-scoped control borrowed by one provider. */
45
+ export interface SkillProviderControlLike {
46
+ readonly signal: AbortSignal;
47
+ readonly invalidate: () => void;
48
+ }
49
+ /** One source of skills, mirrored from the `dsh-skill` SkillProvider contract. */
50
+ export interface SkillProviderLike {
51
+ readonly name: string;
52
+ readonly list: (options: SkillLookupOptionsLike) => Promise<readonly SkillCandidateLike[] | {
53
+ readonly candidates: readonly SkillCandidateLike[];
54
+ readonly complete: boolean;
55
+ }>;
56
+ readonly get: (candidate: SkillCandidateLike, options: SkillLookupOptionsLike) => Promise<SkillDefinitionLike | undefined>;
57
+ }
58
+ /** Minimal host-realm contract for ctx.skills (the seam we call). */
59
+ export interface SkillsRuntimeLike {
60
+ registerProvider(create: (control: SkillProviderControlLike) => SkillProviderLike): () => void;
61
+ }
62
+ /**
63
+ * Register the packaged `recursive-mode` skill into the host `skills` registry.
64
+ *
65
+ * Reads `ctx.get('skills')` optionally (the registry is host-plane; a
66
+ * composition without it is valid). On success returns the exact disposer that
67
+ * unregisters the provider; on absence returns undefined (a no-op, never a boot
68
+ * failure).
69
+ */
70
+ export declare function registerRecursiveSkill(ctx: Context): (() => void) | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@try-works/dsh-recursive-mode",
3
3
  "description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + recursive_status tool",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -24,7 +24,8 @@
24
24
  "src",
25
25
  "preset",
26
26
  "scripts",
27
- "references"
27
+ "references",
28
+ "skills"
28
29
  ],
29
30
  "license": "MIT",
30
31
  "dsh": {
@@ -0,0 +1,66 @@
1
+ # recursive-mode
2
+
3
+ Drive the recursive-mode workflow: an audit-gated, phase-disciplined loop that
4
+ carries one requirement from an AS-IS understanding through a TO-BE plan,
5
+ implementation, test, and manual QA, to locked control-plane state and durable
6
+ memory. Every phase follows `draft → audit → repair → re-audit → pass → lock`.
7
+
8
+ ## When to use
9
+
10
+ - The user says "implement the run", "implement run <id>", "start a recursive
11
+ run", "resume <run-id>", or otherwise asks to advance a run.
12
+ - The repo carries a `/.recursive/` control plane (RECURSIVE.md, STATE.md,
13
+ DECISIONS.md, memory/, run/).
14
+
15
+ ## Source of truth
16
+
17
+ The canonical spec is `/.recursive/RECURSIVE.md`; read it before starting or
18
+ resuming. The control plane lives under `/.recursive/`, one run under
19
+ `/.recursive/run/<run-id>/`, and durable memory under `/.recursive/memory/`.
20
+ Treat bridge docs (`AGENTS.md`, `CLAUDE.md`, `.codex/AGENTS.md`, `.agent/PLANS.md`)
21
+ as harness adapters, not a second spec — on conflict, follow `/.recursive/RECURSIVE.md`.
22
+
23
+ ## Phases
24
+
25
+ A run advances one phase at a time in order; each phase owns a Markdown artifact
26
+ under `/.recursive/run/<run-id>/`:
27
+
28
+ - Phase 0 — `00-requirements.md` (gather requirements)
29
+ - Phase 0 (Worktree) — `00-worktree.md` (record the diff basis and scope)
30
+ - Phase 1 — `01-as-is.md` (AS-IS analysis)
31
+ - Phase 2 — `02-to-be-plan.md` (TO-BE plan)
32
+ - Phase 3 — `03-implementation-summary.md` (implement, strict/pragmatic TDD)
33
+ - Phase 3.5 — `03.5-code-review.md` (delegated or self review)
34
+ - Phase 4 — `04-test-summary.md` (test evidence)
35
+ - Phase 5 — `05-manual-qa.md` (manual QA)
36
+ - Phase 6 — `06-decisions-update.md` (update `/.recursive/DECISIONS.md`)
37
+ - Phase 7 — `07-state-update.md` (update `/.recursive/STATE.md`)
38
+ - Phase 8 — `08-memory-impact.md` (update `/.recursive/memory/`)
39
+
40
+ Use `recursive_status` to read the current phase and its state. Use `recursive_init`
41
+ to scaffold a run, `recursive_lock` to lock a passed phase (it refuses premature
42
+ locks), `recursive_lint` to machine-check an artifact before acceptance, and
43
+ `recursive_closeout` to advance the receipt when the phase is done.
44
+
45
+ ## The audit loop
46
+
47
+ Audited phases must follow `draft → audit → repair → re-audit → pass → lock`.
48
+ Never set `Coverage: PASS` or `Approval: PASS` unless the artifact ends with
49
+ `Audit: PASS`. When subagents are unavailable, perform the same audit as
50
+ self-audit; do not weaken or skip it. Delegate an audit only with the full
51
+ context bundle (phase name + artifact path, upstream artifacts reread for the
52
+ audit, diff basis from `00-worktree.md`, changed files + code references,
53
+ phase-specific audit questions); if the bundle is incomplete, perform the audit
54
+ yourself and record `Audit Execution Mode: self-audit`. A `success: false` or any
55
+ nonzero delegated exit is a failed attempt — preserve diagnostics, repair owned
56
+ issues, rerun, and verify before acceptance.
57
+
58
+ ## Locking and memory
59
+
60
+ Lock a phase with `recursive_lock` — it is the supported way to write
61
+ `Status: LOCKED`, `LockedAt`, and `LockHash`, and it refuses a lock whose
62
+ prerequisites are unmet. Phase 6 owns `/.recursive/DECISIONS.md`, Phase 7 owns
63
+ `/.recursive/STATE.md`, Phase 8 owns `/.recursive/memory/**`. Read
64
+ `/.recursive/memory/MEMORY.md` before loading other memory docs, and record
65
+ durable, reusable conclusions (not raw transcripts) when a run teaches the repo
66
+ something about capability availability, fit, or quality.
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ import { renderRecursivePolicy } from './policy.ts'
20
20
  import { fsPolicyIntent } from './fs-intent.ts'
21
21
  import { snapshotWorkspace } from './snapshot.ts'
22
22
  import { mountRecursiveRoutesOnce, makeRecursiveRoutes, type RecursiveRouteHost } from './live-route.ts'
23
+ import { registerRecursiveSkill } from './skills.ts'
23
24
  import { enumerateRuns, stageBWorkflowInit } from './bootstrap.ts'
24
25
  import { getNextLegalPhase, getLockStatus } from './lock.ts'
25
26
  import { phaseLintRulesMessage, ReminderOnceGate } from './phase-rules.ts'
@@ -74,6 +75,7 @@ export * from './policy.ts'
74
75
  export * from './snapshot.ts'
75
76
  export * from './live-route.ts'
76
77
  export * from './teams-loop.ts'
78
+ export * from './skills.ts'
77
79
 
78
80
  /**
79
81
  * Bundle plugin entry. The Loader activates this row once `tools` is available
@@ -125,7 +127,14 @@ export function apply(ctx: Context, config?: { shellOnly?: boolean; repoRoot?: s
125
127
  // the seam passes the exact live Agent the tool extracts from exec.agent.
126
128
  const agentTeams = ctx.get('agentTeams') as TeamRuntimeLike | undefined
127
129
 
130
+ // Packaged skill (dsh plugin standard): register the `recursive-mode` skill
131
+ // into the host skills registry via ctx.skills.registerProvider (the
132
+ // dsh-skill-badge bundled-provider shape). Optional — a composition without
133
+ // a skills registry is valid and this no-ops (returns undefined).
134
+ const skillDisposer = registerRecursiveSkill(ctx)
135
+
128
136
  const disposers = [
137
+ ...(skillDisposer ? [skillDisposer] : []),
129
138
  ctx.tools.register(createRecursiveStatusTool(recursive)),
130
139
  ctx.tools.register(createRecursiveInitTool(recursive)),
131
140
  ctx.tools.register(createRecursiveLockTool(recursive)),
package/src/skills.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Packaged `recursive-mode` skill (dsh plugin standard).
3
+ *
4
+ * Ships the workflow's operating contract as a bundled skill through
5
+ * `ctx.skills.registerProvider(...)` — the same shape as the shipped
6
+ * `dsh-skill-badge` provider: a `bundled` candidate at `BUNDLED_SKILL_RANK`
7
+ * (600), body read from the package's shipped `skills/recursive-mode/SKILL.md`
8
+ * (never an inlined TS string literal), with a directory resource base so the
9
+ * skill can resolve its own assets. Skills are optional instructions, not
10
+ * session events: this emits nothing and never appends a recursive/* event.
11
+ *
12
+ * Optionality: `skills` is a host-plane registry; when the composition has
13
+ * none, this is a no-op (returns undefined) rather than failing boot.
14
+ */
15
+ import { readFileSync } from 'node:fs'
16
+ import { dirname, join } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+ import type { Context } from '@deepseek-ai/cordis'
19
+
20
+ /** Package root: <package>/src/.. — skills/ sits next to src/. */
21
+ const MODULE_DIR = dirname(fileURLToPath(import.meta.url))
22
+ const PACKAGE_ROOT = join(MODULE_DIR, '..')
23
+
24
+ /** Precedence rank for packaged/bundled skills (mirrors `BUNDLED_SKILL_RANK`). */
25
+ const BUNDLED_SKILL_RANK = 600
26
+ const SKILL_NAME = 'recursive-mode'
27
+ const PROVIDER_NAME = 'recursive-mode'
28
+ const SKILL_BODY_PATH = join(PACKAGE_ROOT, 'skills', SKILL_NAME, 'SKILL.md')
29
+ const SKILL_RESOURCE_BASE = join(PACKAGE_ROOT, 'skills', SKILL_NAME)
30
+ const SKILL_DESCRIPTION =
31
+ 'Drive the recursive-mode workflow: an audit-gated, phase-disciplined loop that carries one requirement from AS-IS through TO-BE plan, implementation, test, and manual QA to locked control-plane state and durable memory. Use whenever the user asks to implement, resume, or advance a recursive run, or when a repo carries a /.recursive/ control plane.'
32
+
33
+ /** Invocation policy: both the model catalog and user `/name` surfaces may load it. */
34
+ const INVOCATION = { modelInvocable: true, userInvocable: true } as const
35
+
36
+ /** One skill contribution, mirrored from the `dsh-skill` registry contract. */
37
+ export interface SkillInvocationPolicyLike {
38
+ readonly modelInvocable: boolean
39
+ readonly userInvocable: boolean
40
+ }
41
+
42
+ /** Provider-owned base for relative resource resolution. */
43
+ export type SkillResourceBaseLike =
44
+ | { readonly kind: 'directory'; readonly path: string }
45
+ | { readonly kind: 'url'; readonly url: string }
46
+ | { readonly kind: 'opaque'; readonly description: string }
47
+
48
+ /** Invocation-neutral summary fields shared by candidates and definitions. */
49
+ export interface SkillSummaryLike {
50
+ readonly name: string
51
+ readonly description: string
52
+ readonly whenToUse?: string
53
+ readonly invocation: SkillInvocationPolicyLike
54
+ readonly source: string
55
+ readonly provider: string
56
+ readonly resourceBase?: SkillResourceBaseLike
57
+ }
58
+
59
+ /** Provider catalog entry: a summary plus rank and an opaque locator. */
60
+ export interface SkillCandidateLike extends SkillSummaryLike {
61
+ readonly rank: number
62
+ readonly locator: unknown
63
+ readonly path?: string
64
+ }
65
+
66
+ /** Complete loaded skill: a summary plus the markdown instruction body. */
67
+ export interface SkillDefinitionLike extends SkillSummaryLike {
68
+ readonly content: string
69
+ readonly path?: string
70
+ }
71
+
72
+ /** Lookup options passed to provider `list`/`get`. */
73
+ export interface SkillLookupOptionsLike {
74
+ readonly cwd?: string | undefined
75
+ readonly signal?: AbortSignal | undefined
76
+ }
77
+
78
+ /** Registration-scoped control borrowed by one provider. */
79
+ export interface SkillProviderControlLike {
80
+ readonly signal: AbortSignal
81
+ readonly invalidate: () => void
82
+ }
83
+
84
+ /** One source of skills, mirrored from the `dsh-skill` SkillProvider contract. */
85
+ export interface SkillProviderLike {
86
+ readonly name: string
87
+ readonly list: (options: SkillLookupOptionsLike) => Promise<readonly SkillCandidateLike[] | { readonly candidates: readonly SkillCandidateLike[]; readonly complete: boolean }>
88
+ readonly get: (candidate: SkillCandidateLike, options: SkillLookupOptionsLike) => Promise<SkillDefinitionLike | undefined>
89
+ }
90
+
91
+ /** Minimal host-realm contract for ctx.skills (the seam we call). */
92
+ export interface SkillsRuntimeLike {
93
+ registerProvider(create: (control: SkillProviderControlLike) => SkillProviderLike): () => void
94
+ }
95
+
96
+ /** The bundled candidate, stable across every `list()` call. */
97
+ function candidate(): SkillCandidateLike {
98
+ return {
99
+ name: SKILL_NAME,
100
+ description: SKILL_DESCRIPTION,
101
+ invocation: INVOCATION,
102
+ provider: PROVIDER_NAME,
103
+ source: 'bundled',
104
+ resourceBase: { kind: 'directory', path: SKILL_RESOURCE_BASE },
105
+ rank: BUNDLED_SKILL_RANK,
106
+ locator: SKILL_BODY_PATH,
107
+ path: SKILL_BODY_PATH,
108
+ }
109
+ }
110
+
111
+ /** Read the shipped operating-contract body (fail loud if the package lost it). */
112
+ function readBody(): string {
113
+ return readFileSync(SKILL_BODY_PATH, 'utf8').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
114
+ }
115
+
116
+ /** The one bundled provider this plugin contributes. */
117
+ function provider(): SkillProviderLike {
118
+ return {
119
+ name: PROVIDER_NAME,
120
+ list: () => Promise.resolve([candidate()]),
121
+ get: async () => ({
122
+ name: SKILL_NAME,
123
+ description: SKILL_DESCRIPTION,
124
+ invocation: INVOCATION,
125
+ provider: PROVIDER_NAME,
126
+ source: 'bundled',
127
+ resourceBase: { kind: 'directory', path: SKILL_RESOURCE_BASE },
128
+ content: readBody(),
129
+ path: SKILL_BODY_PATH,
130
+ }),
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Register the packaged `recursive-mode` skill into the host `skills` registry.
136
+ *
137
+ * Reads `ctx.get('skills')` optionally (the registry is host-plane; a
138
+ * composition without it is valid). On success returns the exact disposer that
139
+ * unregisters the provider; on absence returns undefined (a no-op, never a boot
140
+ * failure).
141
+ */
142
+ export function registerRecursiveSkill(ctx: Context): (() => void) | undefined {
143
+ const skills = ctx.get('skills')
144
+ if (skills === undefined || skills === null) return undefined
145
+ // SAFETY: the single boundary cast asserts the live ctx.skills satisfies the
146
+ // structural SkillsRuntimeLike seam (registerProvider). The live registry's
147
+ // method is a superset of this seam; the provider we return is a plain owned
148
+ // object read through its own leaf fields, never serialized.
149
+ const registry = skills as SkillsRuntimeLike
150
+ return registry.registerProvider(() => provider())
151
+ }