@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 +1 -0
- package/lib/index.js +91 -4
- package/lib/skills.d.ts +70 -0
- package/package.json +3 -2
- package/skills/recursive-mode/SKILL.md +66 -0
- package/src/index.ts +9 -0
- package/src/skills.ts +151 -0
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 };
|
package/lib/skills.d.ts
ADDED
|
@@ -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.
|
|
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
|
+
}
|