@dot-skill/protocol 0.4.2 → 0.5.0

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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # `@dot-skill/protocol`
2
+
3
+ Types, schemas, and completeness rules for the [Open `.skill` Protocol](https://github.com/dot-skill/dot-skill).
4
+
5
+ Defines **SkillContract**, **SkillSource**, section shapes, assessment helpers, and the JSON Schema used for transferable authoring. This package is the semantic source of truth; it does not pack or run skills.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @dot-skill/protocol
11
+ ```
12
+
13
+ ## What you get
14
+
15
+ - `SkillContract` / `SkillSource` TypeScript types
16
+ - Completeness assessment and explanation APIs
17
+ - Multi-skill extract helpers (`extractSkillCandidates`, `agentCreateGuide`)
18
+ - `skill-contract.schema.json` (JSON Schema export)
19
+ - Adapter types for external capture formats (mapped into SkillSource before compile)
20
+
21
+ ## Vocabulary
22
+
23
+ Protocol terms: **section**, **SkillSource**, **SkillContract**, **extract/segment**, **compile**, **mint**, **load**.
24
+
25
+ Product-specific capture words map into this model via adapters; they are not protocol vocabulary.
26
+
27
+ ## Related
28
+
29
+ - [`@dot-skill/core`](https://www.npmjs.com/package/@dot-skill/core) — compile / pack / mint
30
+ - [`skillerr`](https://www.npmjs.com/package/skillerr) — public install (`skill` CLI)
31
+
32
+ Docs: [PROTOCOL.md](https://github.com/dot-skill/dot-skill/blob/main/docs/PROTOCOL.md) · [AUTHORING-CONTRACT.md](https://github.com/dot-skill/dot-skill/blob/main/docs/AUTHORING-CONTRACT.md)
33
+
34
+ ## License
35
+
36
+ MIT
@@ -0,0 +1,14 @@
1
+ import type { ContractAssessment, ContractField } from "./contract.js";
2
+ import type { SkillCompileProfile } from "./types.js";
3
+ /** Assess a full or partial contract without compiling it. */
4
+ export declare function assessSkillContract(value: unknown, profile?: SkillCompileProfile): ContractAssessment;
5
+ /** Machine-readable authoring template. Placeholder values intentionally fail assessment. */
6
+ export declare function scaffoldSkillContract(): Record<string, unknown>;
7
+ export declare function explainContractAssessment(assessment: ContractAssessment): {
8
+ complete: boolean;
9
+ fixes: Array<{
10
+ field: ContractField;
11
+ message: string;
12
+ fix: string;
13
+ }>;
14
+ };
@@ -0,0 +1,260 @@
1
+ const DECLARATIONS = [
2
+ "triggers",
3
+ "inputs",
4
+ "preconditions",
5
+ "steps",
6
+ "branches",
7
+ "human_decisions",
8
+ "capabilities",
9
+ "permissions",
10
+ "forbidden_actions",
11
+ "outputs",
12
+ "recovery",
13
+ "verification",
14
+ "corrections",
15
+ ];
16
+ function declarationIssue(field, value, profile) {
17
+ if (!value || typeof value !== "object") {
18
+ return {
19
+ field,
20
+ code: "missing",
21
+ message: `${field} is absent; omission is not an explicit declaration`,
22
+ fix: `Set ${field} to {status:"specified",items:[...]} or an allowed explicit {status:"none"|"not_applicable",reason:"..."}.`,
23
+ };
24
+ }
25
+ const declaration = value;
26
+ if (declaration.status === "specified") {
27
+ if (!Array.isArray(declaration.items) || declaration.items.length === 0) {
28
+ return {
29
+ field,
30
+ code: "empty",
31
+ message: `${field} is specified but has no items`,
32
+ fix: `Add at least one structured ${field} item or explicitly declare none/not_applicable with a reason.`,
33
+ };
34
+ }
35
+ return;
36
+ }
37
+ if (declaration.status !== "none" && declaration.status !== "not_applicable") {
38
+ return {
39
+ field,
40
+ code: "invalid",
41
+ message: `${field}.status is invalid`,
42
+ fix: `Use specified, none, or not_applicable.`,
43
+ };
44
+ }
45
+ if (!("reason" in declaration) || typeof declaration.reason !== "string" || !declaration.reason.trim()) {
46
+ return {
47
+ field,
48
+ code: "invalid",
49
+ message: `${field} ${declaration.status} declaration needs a reason`,
50
+ fix: `Add a concrete reason explaining why ${field} is ${declaration.status}.`,
51
+ };
52
+ }
53
+ if (profile === "release" &&
54
+ (field === "triggers" || field === "steps" || field === "verification")) {
55
+ return {
56
+ field,
57
+ code: "profile_required",
58
+ message: `${field} must be specified for release`,
59
+ fix: `Provide at least one structured ${field} item. Release skills must be discoverable, actionable, and verifiable.`,
60
+ };
61
+ }
62
+ return;
63
+ }
64
+ /** Assess a full or partial contract without compiling it. */
65
+ export function assessSkillContract(value, profile = "release") {
66
+ const issues = [];
67
+ const contract = (value ?? {});
68
+ const addMissingText = (field, value) => {
69
+ if (typeof value !== "string" || !value.trim()) {
70
+ issues.push({
71
+ field,
72
+ code: "missing",
73
+ message: `${field} is required`,
74
+ fix: `Set ${field} to a precise, non-empty string.`,
75
+ });
76
+ }
77
+ };
78
+ if (contract.kind !== "skill_contract" || contract.contract_version !== "0.5") {
79
+ issues.push({
80
+ field: "contract",
81
+ code: "invalid",
82
+ message: "Expected kind=skill_contract and contract_version=0.5",
83
+ fix: "Start from scaffoldSkillContract() and preserve its kind/version fields.",
84
+ });
85
+ }
86
+ addMissingText("title", contract.title);
87
+ addMissingText("intent", contract.intent);
88
+ if (!["knowledge", "procedure", "integration"].includes(contract.skill_kind ?? "")) {
89
+ issues.push({
90
+ field: "skill_kind",
91
+ code: "missing",
92
+ message: "skill_kind must classify the contract",
93
+ fix: "Choose knowledge, procedure, or integration.",
94
+ });
95
+ }
96
+ if (!["private", "shareable_redacted", "public"].includes(contract.sensitivity ?? "")) {
97
+ issues.push({
98
+ field: "sensitivity",
99
+ code: "missing",
100
+ message: "sensitivity is required",
101
+ fix: "Choose private, shareable_redacted, or public.",
102
+ });
103
+ }
104
+ for (const field of DECLARATIONS) {
105
+ const issue = declarationIssue(field, contract[field], profile);
106
+ if (issue)
107
+ issues.push(issue);
108
+ }
109
+ const validateItems = (field, declaration, requiredKeys) => {
110
+ const d = declaration;
111
+ if (d?.status !== "specified" || !Array.isArray(d.items))
112
+ return;
113
+ d.items.forEach((item, index) => {
114
+ if (!item || typeof item !== "object")
115
+ return;
116
+ const missing = requiredKeys.filter((key) => !(key in item));
117
+ if (missing.length) {
118
+ issues.push({
119
+ field,
120
+ code: "invalid",
121
+ message: `${field}.items[${index}] lacks ${missing.join(", ")}`,
122
+ fix: `Add ${missing.join(", ")} to ${field}.items[${index}].`,
123
+ });
124
+ }
125
+ });
126
+ };
127
+ validateItems("triggers", contract.triggers, ["id", "description"]);
128
+ validateItems("inputs", contract.inputs, [
129
+ "name",
130
+ "description",
131
+ "schema",
132
+ "required",
133
+ "sensitivity",
134
+ "source",
135
+ "ask_when",
136
+ "approval",
137
+ ]);
138
+ validateItems("preconditions", contract.preconditions, ["id", "assertion", "check", "on_failure"]);
139
+ validateItems("steps", contract.steps, ["id", "title", "kind"]);
140
+ validateItems("branches", contract.branches, ["id", "condition", "then"]);
141
+ validateItems("human_decisions", contract.human_decisions, [
142
+ "id",
143
+ "prompt",
144
+ "required_before",
145
+ "irreversible",
146
+ "approval",
147
+ ]);
148
+ validateItems("capabilities", contract.capabilities, [
149
+ "name",
150
+ "description",
151
+ "side_effect_class",
152
+ "fallback",
153
+ "required",
154
+ ]);
155
+ validateItems("permissions", contract.permissions, [
156
+ "id",
157
+ "side_effect_class",
158
+ "description",
159
+ "consent",
160
+ ]);
161
+ validateItems("forbidden_actions", contract.forbidden_actions, [
162
+ "id",
163
+ "description",
164
+ "enforcement",
165
+ ]);
166
+ validateItems("outputs", contract.outputs, [
167
+ "name",
168
+ "description",
169
+ "schema",
170
+ "required",
171
+ ]);
172
+ validateItems("recovery", contract.recovery, ["id", "from_step", "condition", "action"]);
173
+ validateItems("verification", contract.verification, [
174
+ "id",
175
+ "assertion",
176
+ "check",
177
+ "required",
178
+ ]);
179
+ validateItems("corrections", contract.corrections, ["id", "lesson"]);
180
+ const provenance = contract.provenance;
181
+ for (const [field, value] of [
182
+ ["provenance.evidence", provenance?.evidence],
183
+ ["provenance.limitations", provenance?.limitations],
184
+ ]) {
185
+ const issue = declarationIssue(field, value, profile);
186
+ if (issue)
187
+ issues.push(issue);
188
+ }
189
+ if (!provenance?.human_review) {
190
+ issues.push({
191
+ field: "provenance.human_review",
192
+ code: "missing",
193
+ message: "human review state is absent",
194
+ fix: 'Declare {status:"not_reviewed"} or provide reviewed actor, time, scope, and optional digest.',
195
+ });
196
+ }
197
+ else if (profile === "release" && provenance.human_review.status !== "reviewed") {
198
+ issues.push({
199
+ field: "provenance.human_review",
200
+ code: "profile_required",
201
+ message: "release requires recorded human semantic review",
202
+ fix: "Have a human review the contract and record actor, timestamp, scope, and preferably the reviewed digest. A CLI flag cannot create this evidence.",
203
+ });
204
+ }
205
+ else if (provenance.human_review.status === "reviewed" &&
206
+ (!provenance.human_review.actor?.trim() ||
207
+ !provenance.human_review.at?.trim() ||
208
+ !provenance.human_review.scope?.length)) {
209
+ issues.push({
210
+ field: "provenance.human_review",
211
+ code: "approval_invalid",
212
+ message: "reviewed status lacks actor, timestamp, or scope evidence",
213
+ fix: "Record the real reviewer actor, review timestamp, and non-empty semantic scope.",
214
+ });
215
+ }
216
+ const complete = issues.length === 0;
217
+ return {
218
+ kind: "contract_assessment",
219
+ profile,
220
+ complete,
221
+ release_eligible: profile === "release" && complete,
222
+ issues,
223
+ };
224
+ }
225
+ /** Machine-readable authoring template. Placeholder values intentionally fail assessment. */
226
+ export function scaffoldSkillContract() {
227
+ const declaration = { status: "__required__: specified|none|not_applicable", items: [] };
228
+ return {
229
+ kind: "skill_contract",
230
+ contract_version: "0.5",
231
+ skill_kind: "__required__: knowledge|procedure|integration",
232
+ title: "",
233
+ intent: "",
234
+ sensitivity: "__required__: private|shareable_redacted|public",
235
+ triggers: structuredClone(declaration),
236
+ inputs: structuredClone(declaration),
237
+ preconditions: structuredClone(declaration),
238
+ steps: structuredClone(declaration),
239
+ branches: structuredClone(declaration),
240
+ human_decisions: structuredClone(declaration),
241
+ capabilities: structuredClone(declaration),
242
+ permissions: structuredClone(declaration),
243
+ forbidden_actions: structuredClone(declaration),
244
+ outputs: structuredClone(declaration),
245
+ recovery: structuredClone(declaration),
246
+ verification: structuredClone(declaration),
247
+ corrections: structuredClone(declaration),
248
+ provenance: {
249
+ evidence: structuredClone(declaration),
250
+ limitations: structuredClone(declaration),
251
+ human_review: { status: "__required__: not_reviewed|reviewed" },
252
+ },
253
+ };
254
+ }
255
+ export function explainContractAssessment(assessment) {
256
+ return {
257
+ complete: assessment.complete,
258
+ fixes: assessment.issues.map(({ field, message, fix }) => ({ field, message, fix })),
259
+ };
260
+ }
@@ -0,0 +1,190 @@
1
+ import type { AskWhen, CapabilityFallback, InputSource, JsonSchema, PackageSensitivity, SensitivityLevel, SideEffectClass } from "./types.js";
2
+ /** Protocol-native contract introduced in 0.5. Product adapters must map into this vocabulary. */
3
+ export type SkillKind = "knowledge" | "procedure" | "integration";
4
+ export type DeclarationStatus = "specified" | "none" | "not_applicable";
5
+ export type ExplicitDeclaration<T> = {
6
+ status: "specified";
7
+ items: T[];
8
+ } | {
9
+ status: "none";
10
+ reason: string;
11
+ } | {
12
+ status: "not_applicable";
13
+ reason: string;
14
+ };
15
+ export interface ContractTrigger {
16
+ id: string;
17
+ description: string;
18
+ examples?: string[];
19
+ }
20
+ export type InputApproval = "none" | "human_before_use";
21
+ export interface ContractInput {
22
+ name: string;
23
+ description: string;
24
+ schema: JsonSchema;
25
+ required: boolean;
26
+ default?: unknown;
27
+ sensitivity: SensitivityLevel;
28
+ source: InputSource;
29
+ ask_when: AskWhen;
30
+ approval: InputApproval;
31
+ }
32
+ export interface ContractPrecondition {
33
+ id: string;
34
+ assertion: string;
35
+ check: "agent" | "capability" | "human";
36
+ on_failure: string;
37
+ }
38
+ export type ContractStepKind = "instruct" | "prompt" | "tool" | "transform" | "checkpoint" | "human_decision" | "verify" | "emit";
39
+ export interface ContractStep {
40
+ id: string;
41
+ title: string;
42
+ kind: ContractStepKind;
43
+ instruction?: string;
44
+ capability?: string;
45
+ arguments?: Record<string, unknown>;
46
+ argument_bindings?: Record<string, string>;
47
+ result_as?: string;
48
+ output?: string;
49
+ from?: string;
50
+ assertions?: string[];
51
+ decision?: string;
52
+ next?: string;
53
+ on_failure?: string;
54
+ optional?: boolean;
55
+ }
56
+ export interface ContractBranch {
57
+ id: string;
58
+ after_step?: string;
59
+ condition: string;
60
+ then: string;
61
+ otherwise?: string;
62
+ }
63
+ export interface ContractHumanDecision {
64
+ id: string;
65
+ prompt: string;
66
+ choices?: string[];
67
+ required_before: string;
68
+ irreversible: boolean;
69
+ /** Declares an approval gate. It is never evidence that approval was granted. */
70
+ approval: "explicit_human";
71
+ }
72
+ export interface ContractCapability {
73
+ name: string;
74
+ description: string;
75
+ input_schema?: JsonSchema;
76
+ output_schema?: JsonSchema;
77
+ side_effect_class: SideEffectClass;
78
+ fallback: CapabilityFallback;
79
+ required: boolean;
80
+ }
81
+ export interface ContractPermission {
82
+ id: string;
83
+ side_effect_class: SideEffectClass;
84
+ description: string;
85
+ paths?: string[];
86
+ hosts?: string[];
87
+ consent: "none" | "explicit_human";
88
+ }
89
+ export interface ForbiddenAction {
90
+ id: string;
91
+ description: string;
92
+ enforcement: "runtime" | "host" | "review";
93
+ }
94
+ export interface ContractOutput {
95
+ name: string;
96
+ description: string;
97
+ schema: JsonSchema;
98
+ required: boolean;
99
+ media_type?: string;
100
+ assertions?: string[];
101
+ }
102
+ export interface RecoveryEdge {
103
+ id: string;
104
+ from_step: string;
105
+ condition: string;
106
+ action: string;
107
+ goto?: string;
108
+ terminal?: boolean;
109
+ }
110
+ export interface VerificationAssertion {
111
+ id: string;
112
+ assertion: string;
113
+ check: "runtime" | "capability" | "human";
114
+ evidence?: string[];
115
+ required: boolean;
116
+ }
117
+ export interface ContractCorrection {
118
+ id: string;
119
+ lesson: string;
120
+ applies_to?: string[];
121
+ }
122
+ export interface ContractEvidence {
123
+ id: string;
124
+ kind: "source" | "section" | "test" | "review" | "external";
125
+ ref: string;
126
+ digest?: string;
127
+ supports: string[];
128
+ }
129
+ export interface ContractProvenance {
130
+ evidence: ExplicitDeclaration<ContractEvidence>;
131
+ limitations: ExplicitDeclaration<string>;
132
+ human_review: {
133
+ status: "not_reviewed";
134
+ } | {
135
+ status: "reviewed";
136
+ actor: string;
137
+ at: string;
138
+ scope: string[];
139
+ digest?: string;
140
+ };
141
+ }
142
+ /**
143
+ * Complete transferable skill authoring contract.
144
+ *
145
+ * Every declaration is required on the wire. `none` and `not_applicable` are
146
+ * deliberate author statements; an absent field is an ambiguous omission.
147
+ */
148
+ export interface SkillContract {
149
+ kind: "skill_contract";
150
+ contract_version: "0.5";
151
+ skill_kind: SkillKind;
152
+ title: string;
153
+ intent: string;
154
+ sensitivity: PackageSensitivity;
155
+ triggers: ExplicitDeclaration<ContractTrigger>;
156
+ inputs: ExplicitDeclaration<ContractInput>;
157
+ preconditions: ExplicitDeclaration<ContractPrecondition>;
158
+ steps: ExplicitDeclaration<ContractStep>;
159
+ branches: ExplicitDeclaration<ContractBranch>;
160
+ human_decisions: ExplicitDeclaration<ContractHumanDecision>;
161
+ capabilities: ExplicitDeclaration<ContractCapability>;
162
+ permissions: ExplicitDeclaration<ContractPermission>;
163
+ forbidden_actions: ExplicitDeclaration<ForbiddenAction>;
164
+ outputs: ExplicitDeclaration<ContractOutput>;
165
+ recovery: ExplicitDeclaration<RecoveryEdge>;
166
+ verification: ExplicitDeclaration<VerificationAssertion>;
167
+ corrections: ExplicitDeclaration<ContractCorrection>;
168
+ provenance: ContractProvenance;
169
+ }
170
+ export type ContractField = "contract" | "title" | "intent" | "skill_kind" | "sensitivity" | "triggers" | "inputs" | "preconditions" | "steps" | "branches" | "human_decisions" | "capabilities" | "permissions" | "forbidden_actions" | "outputs" | "recovery" | "verification" | "corrections" | "provenance.evidence" | "provenance.limitations" | "provenance.human_review";
171
+ export interface ContractIssue {
172
+ field: ContractField;
173
+ code: "missing" | "empty" | "invalid" | "profile_required" | "approval_invalid";
174
+ message: string;
175
+ fix: string;
176
+ }
177
+ export interface ContractAssessment {
178
+ kind: "contract_assessment";
179
+ profile: "continuity" | "release";
180
+ complete: boolean;
181
+ release_eligible: boolean;
182
+ issues: ContractIssue[];
183
+ }
184
+ export interface SkillCandidate {
185
+ id: string;
186
+ title: string;
187
+ evidence_refs: string[];
188
+ assessment: ContractAssessment;
189
+ contract?: SkillContract;
190
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Multi-skill identification → scaffold extraction for agents.
3
+ *
4
+ * Segmentation is an agent/adapter responsibility. This module does not invent
5
+ * skills from free prose: the caller must supply candidate topics. It emits
6
+ * incomplete SkillContract / SkillSource scaffolds plus completeness reports
7
+ * so create flows stay coherent with SkillContract 0.5 and refuse incomplete
8
+ * release compiles.
9
+ */
10
+ import type { SkillCandidate, SkillKind } from "./contract.js";
11
+ import type { PackageSensitivity, SkillCompileProfile } from "./types.js";
12
+ import { PROTOCOL_VERSION } from "./types.js";
13
+ export type JourneyCandidateInput = {
14
+ id?: string;
15
+ title: string;
16
+ evidence_refs?: string[];
17
+ intent?: string;
18
+ skill_kind?: SkillKind;
19
+ notes?: string;
20
+ };
21
+ /** Redacted journey JSON accepted by `skill extract` / `segment`. */
22
+ export type RedactedJourneyInput = {
23
+ kind?: "redacted_journey";
24
+ summary: string;
25
+ redacted?: boolean;
26
+ sensitivity?: PackageSensitivity;
27
+ /** Preferred: explicit skill candidates the agent identified. */
28
+ candidates?: JourneyCandidateInput[];
29
+ /** Convenience alias when only titles (or title objects) are known. */
30
+ topics?: Array<string | JourneyCandidateInput>;
31
+ };
32
+ export type ExtractionScaffold = {
33
+ candidate: SkillCandidate;
34
+ /** Incomplete contract scaffold; placeholders intentionally fail assessment. */
35
+ contract_scaffold: Record<string, unknown>;
36
+ /** Incomplete SkillSource scaffold for one skill (one workspace). */
37
+ source_scaffold: Record<string, unknown>;
38
+ /** Suggested workspace directory slug (relative). */
39
+ workspace_slug: string;
40
+ /** Machine-readable missing fields / fixes for this candidate. */
41
+ missing: Array<{
42
+ field: string;
43
+ message: string;
44
+ fix: string;
45
+ }>;
46
+ next_steps: string[];
47
+ };
48
+ export type ExtractionReport = {
49
+ kind: "skill_extraction";
50
+ protocol_version: typeof PROTOCOL_VERSION;
51
+ profile: SkillCompileProfile;
52
+ journey_summary: string;
53
+ redacted: boolean;
54
+ candidate_count: number;
55
+ scaffolds: ExtractionScaffold[];
56
+ protocol: {
57
+ one_workspace_per_skill: true;
58
+ refuse_release_if_incomplete: true;
59
+ rules: string[];
60
+ create_path: string[];
61
+ };
62
+ };
63
+ export type AgentGuide = {
64
+ kind: "skill_agent_guide";
65
+ protocol_version: typeof PROTOCOL_VERSION;
66
+ purpose: string;
67
+ rules: string[];
68
+ identify_multiple_skills: string[];
69
+ create_one_skill: string[];
70
+ ingest: string[];
71
+ cli: string[];
72
+ refuse: string[];
73
+ };
74
+ /**
75
+ * Emit incomplete SkillContract/SkillSource scaffolds for agent-identified candidates.
76
+ * Does not invent topics from free prose when candidates/topics are absent.
77
+ */
78
+ export declare function extractSkillCandidates(raw: unknown, options?: {
79
+ profile?: SkillCompileProfile;
80
+ host?: string;
81
+ }): ExtractionReport;
82
+ /** Alias for adapters that prefer "segment" vocabulary. */
83
+ export declare const segmentJourney: typeof extractSkillCandidates;
84
+ /** Structured agent-facing create / multi-skill protocol (also printed by CLI). */
85
+ export declare function agentCreateGuide(): AgentGuide;
86
+ /** Human-readable text form of agentCreateGuide (for terminals / AGENT.md parity). */
87
+ export declare function formatAgentGuide(guide?: AgentGuide): string;
88
+ /** Type guard helper for partial SkillContract assessments on scaffolds. */
89
+ export declare function assessExtractionScaffold(scaffold: ExtractionScaffold, profile?: SkillCompileProfile): SkillCandidate["assessment"];