@phamvuhoang/otto-core 0.25.0 → 0.27.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/dist/cli-help.d.ts +4 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +5 -0
- package/dist/cli-help.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +16 -0
- package/dist/inspect.js.map +1 -1
- package/dist/loop.d.ts +5 -0
- package/dist/loop.d.ts.map +1 -1
- package/dist/loop.js +48 -1
- package/dist/loop.js.map +1 -1
- package/dist/report-explain.d.ts.map +1 -1
- package/dist/report-explain.js +4 -0
- package/dist/report-explain.js.map +1 -1
- package/dist/run-bin.d.ts.map +1 -1
- package/dist/run-bin.js +10 -0
- package/dist/run-bin.js.map +1 -1
- package/dist/run-report.d.ts +6 -0
- package/dist/run-report.d.ts.map +1 -1
- package/dist/run-report.js.map +1 -1
- package/dist/runner.d.ts +4 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/skill-activation.d.ts +47 -0
- package/dist/skill-activation.d.ts.map +1 -0
- package/dist/skill-activation.js +57 -0
- package/dist/skill-activation.js.map +1 -0
- package/dist/skill-routing.d.ts +83 -0
- package/dist/skill-routing.d.ts.map +1 -0
- package/dist/skill-routing.js +245 -0
- package/dist/skill-routing.js.map +1 -0
- package/dist/skill-validation.d.ts +155 -0
- package/dist/skill-validation.d.ts.map +1 -0
- package/dist/skill-validation.js +376 -0
- package/dist/skill-validation.js.map +1 -0
- package/dist/skills-cli.d.ts +14 -0
- package/dist/skills-cli.d.ts.map +1 -1
- package/dist/skills-cli.js +123 -4
- package/dist/skills-cli.js.map +1 -1
- package/dist/skills.d.ts +40 -4
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +43 -1
- package/dist/skills.js.map +1 -1
- package/dist/stage-exec.d.ts +4 -0
- package/dist/stage-exec.d.ts.map +1 -1
- package/dist/stage-exec.js +6 -0
- package/dist/stage-exec.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { parseFrontmatter } from "./external-skills.js";
|
|
3
|
+
import { toSkillName } from "./skills.js";
|
|
4
|
+
/** sha256 of a skill's instruction body, hex. The hash domain matches the */
|
|
5
|
+
/** imported-package checksum in `external-skills.ts` so drift compares cleanly. */
|
|
6
|
+
export function skillChecksum(instructions) {
|
|
7
|
+
return createHash("sha256").update(instructions).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Map one capability tag to the stage it scopes to, or null when it does not
|
|
11
|
+
* imply a specific stage (a general capability → afk-safe, not stage-scoped).
|
|
12
|
+
* Mirrors the P18 stage capability tags in the roadmap.
|
|
13
|
+
*/
|
|
14
|
+
const CAPABILITY_STAGE = new Map([
|
|
15
|
+
["planning", "plan"],
|
|
16
|
+
["roadmap-planning", "plan"],
|
|
17
|
+
["prd", "plan"],
|
|
18
|
+
["prioritization", "plan"],
|
|
19
|
+
["problem-framing", "plan"],
|
|
20
|
+
["discovery", "plan"],
|
|
21
|
+
["tdd", "implement"],
|
|
22
|
+
["coding", "implement"],
|
|
23
|
+
["refactor", "implement"],
|
|
24
|
+
["code-review", "review"],
|
|
25
|
+
["review", "review"],
|
|
26
|
+
["security", "review"],
|
|
27
|
+
["structural", "review"],
|
|
28
|
+
["reporting", "report"],
|
|
29
|
+
["report", "report"],
|
|
30
|
+
["journal", "journal"],
|
|
31
|
+
["context-engineering", "tool-output"],
|
|
32
|
+
["compression", "tool-output"],
|
|
33
|
+
]);
|
|
34
|
+
/** The stages a skill's capabilities imply (sorted, de-duplicated). */
|
|
35
|
+
function impliedStages(capabilities) {
|
|
36
|
+
const stages = new Set();
|
|
37
|
+
for (const cap of capabilities) {
|
|
38
|
+
const stage = CAPABILITY_STAGE.get(cap.toLowerCase());
|
|
39
|
+
if (stage)
|
|
40
|
+
stages.add(stage);
|
|
41
|
+
}
|
|
42
|
+
return [...stages].sort();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Derive a {@link SkillCompatibility} class from a validation report (issue
|
|
46
|
+
* #134), as a priority ladder: an error-severity finding → `blocked` (cannot be
|
|
47
|
+
* applied anywhere); an interactive hard stop → `interactive-only` (needs a human,
|
|
48
|
+
* so AFK-unsafe even when otherwise valid); capabilities that imply specific
|
|
49
|
+
* stages → `stage-scoped` (with the stage list); otherwise `afk-safe`. Pure.
|
|
50
|
+
*/
|
|
51
|
+
export function classifyCompatibility(report) {
|
|
52
|
+
if (report.findings.some((f) => f.severity === "error")) {
|
|
53
|
+
return { compatibility: "blocked", stages: [] };
|
|
54
|
+
}
|
|
55
|
+
if (report.findings.some((f) => f.rule === "interactive-hard-stop")) {
|
|
56
|
+
return { compatibility: "interactive-only", stages: [] };
|
|
57
|
+
}
|
|
58
|
+
const stages = impliedStages(report.capabilities);
|
|
59
|
+
if (stages.length > 0) {
|
|
60
|
+
return { compatibility: "stage-scoped", stages };
|
|
61
|
+
}
|
|
62
|
+
return { compatibility: "afk-safe", stages: [] };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Static manifest/schema lint. Checks the structural invariants a usable skill
|
|
66
|
+
* package must hold: a filesystem-safe name, a non-empty instruction body, at
|
|
67
|
+
* least one capability (the retrieval key), and a set version. Returns one
|
|
68
|
+
* finding per broken rule; a well-formed manifest yields `[]`.
|
|
69
|
+
*/
|
|
70
|
+
export function lintManifest(skill) {
|
|
71
|
+
const findings = [];
|
|
72
|
+
if (toSkillName(skill.name) !== skill.name) {
|
|
73
|
+
findings.push({
|
|
74
|
+
kind: "manifest",
|
|
75
|
+
severity: "error",
|
|
76
|
+
rule: "name-slug",
|
|
77
|
+
message: `name "${skill.name}" is not a filesystem-safe slug`,
|
|
78
|
+
remediation: `rename the package to "${toSkillName(skill.name)}"`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (skill.instructions.trim().length === 0) {
|
|
82
|
+
findings.push({
|
|
83
|
+
kind: "manifest",
|
|
84
|
+
severity: "error",
|
|
85
|
+
rule: "empty-instructions",
|
|
86
|
+
message: "the skill has no instruction body",
|
|
87
|
+
remediation: "add the procedure to instructions.md",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (skill.capabilities.length === 0) {
|
|
91
|
+
findings.push({
|
|
92
|
+
kind: "manifest",
|
|
93
|
+
severity: "warn",
|
|
94
|
+
rule: "no-capabilities",
|
|
95
|
+
message: "no capabilities declared — retrieval cannot key on this skill",
|
|
96
|
+
remediation: "declare capability tags (e.g. planning, tdd, code-review)",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (skill.version === "0.0.0" || skill.version.trim().length === 0) {
|
|
100
|
+
findings.push({
|
|
101
|
+
kind: "manifest",
|
|
102
|
+
severity: "warn",
|
|
103
|
+
rule: "unversioned",
|
|
104
|
+
message: `version "${skill.version}" looks unset`,
|
|
105
|
+
remediation: "set a real version so drift and revalidation can be tracked",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return findings;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extract the skill's effective capabilities: the manifest `capabilities` plus
|
|
112
|
+
* any declared in a leading frontmatter block in the instruction body (skill
|
|
113
|
+
* packs often carry `capabilities:` there). De-duplicated, manifest order first.
|
|
114
|
+
*/
|
|
115
|
+
export function extractCapabilities(skill) {
|
|
116
|
+
const out = [...skill.capabilities];
|
|
117
|
+
const { fields } = parseFrontmatter(skill.instructions);
|
|
118
|
+
const raw = fields.capabilities;
|
|
119
|
+
if (raw) {
|
|
120
|
+
for (const cap of raw
|
|
121
|
+
.replace(/^\[|\]$/g, "")
|
|
122
|
+
.split(",")
|
|
123
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
124
|
+
.filter((s) => s.length > 0)) {
|
|
125
|
+
if (!out.includes(cap))
|
|
126
|
+
out.push(cap);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* License/provenance check. For an imported skill (has `provenance`): the
|
|
133
|
+
* upstream ref must be pinned and a license declared, and when an explicit
|
|
134
|
+
* `--source` is given it must match the recorded source. For a repo-authored
|
|
135
|
+
* skill (no provenance) there is nothing external to check — but asserting a
|
|
136
|
+
* `--source` against it is an error, since it was never imported.
|
|
137
|
+
*/
|
|
138
|
+
export function checkProvenance(skill, source) {
|
|
139
|
+
const findings = [];
|
|
140
|
+
const p = skill.provenance;
|
|
141
|
+
if (!p) {
|
|
142
|
+
if (source !== undefined) {
|
|
143
|
+
findings.push({
|
|
144
|
+
kind: "provenance",
|
|
145
|
+
severity: "error",
|
|
146
|
+
rule: "not-imported",
|
|
147
|
+
message: `--source "${source}" was given but "${skill.name}" is repo-authored (no provenance)`,
|
|
148
|
+
remediation: "drop --source, or import the skill via otto-skills sync",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return findings;
|
|
152
|
+
}
|
|
153
|
+
if (source !== undefined && p.source !== source) {
|
|
154
|
+
findings.push({
|
|
155
|
+
kind: "provenance",
|
|
156
|
+
severity: "error",
|
|
157
|
+
rule: "source-mismatch",
|
|
158
|
+
message: `--source "${source}" does not match recorded source "${p.source}"`,
|
|
159
|
+
remediation: `validate with --source ${p.source}, or re-import from ${source}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (!p.upstreamRef) {
|
|
163
|
+
findings.push({
|
|
164
|
+
kind: "provenance",
|
|
165
|
+
severity: "warn",
|
|
166
|
+
rule: "unpinned-ref",
|
|
167
|
+
message: `imported "${skill.name}" has no pinned upstream ref`,
|
|
168
|
+
remediation: "pin the source: otto-skills sources add <name> <url> --ref <sha-or-tag>",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!p.license) {
|
|
172
|
+
findings.push({
|
|
173
|
+
kind: "provenance",
|
|
174
|
+
severity: "warn",
|
|
175
|
+
rule: "missing-license",
|
|
176
|
+
message: `imported "${skill.name}" declares no license`,
|
|
177
|
+
remediation: "add a license: field to the upstream SKILL.md and re-sync",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return findings;
|
|
181
|
+
}
|
|
182
|
+
const RISK_PATTERNS = [
|
|
183
|
+
// Unsafe shell advice — destructive or self-elevating commands.
|
|
184
|
+
{
|
|
185
|
+
rule: "unsafe-shell",
|
|
186
|
+
severity: "error",
|
|
187
|
+
re: /\brm\s+-[a-z]*[rf][a-z]*\s+(?:-[a-z]+\s+)*(?:\/|~|\$\w|\*)/i,
|
|
188
|
+
message: (h) => `destructive delete in shell advice: "${h.trim()}"`,
|
|
189
|
+
remediation: "scope deletes to a named path; never rm -rf / ~ or $VAR",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
rule: "unsafe-shell",
|
|
193
|
+
severity: "error",
|
|
194
|
+
re: /\b(?:curl|wget)\b[^\n|]*\|\s*(?:sudo\s+)?(?:ba|z|da|)sh\b/i,
|
|
195
|
+
message: (h) => `pipes a download straight into a shell: "${h.trim()}"`,
|
|
196
|
+
remediation: "download, inspect, then run — never pipe a fetch into a shell",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
rule: "unsafe-shell",
|
|
200
|
+
severity: "error",
|
|
201
|
+
re: /\bsudo\s+\S+/i,
|
|
202
|
+
message: (h) => `requires elevated privileges: "${h.trim()}"`,
|
|
203
|
+
remediation: "AFK runs unprivileged; remove sudo and operate in the workspace",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
rule: "unsafe-shell",
|
|
207
|
+
severity: "error",
|
|
208
|
+
re: /\bchmod\s+(?:-[a-z]+\s+)*777\b/i,
|
|
209
|
+
message: (h) => `world-writable permissions: "${h.trim()}"`,
|
|
210
|
+
remediation: "grant least-privilege permissions instead of 777",
|
|
211
|
+
},
|
|
212
|
+
// Secret handling — printing/echoing/logging credentials.
|
|
213
|
+
{
|
|
214
|
+
rule: "secret-handling",
|
|
215
|
+
severity: "error",
|
|
216
|
+
re: /\b(?:echo|print|printf|cat|log|paste|export)\b[^\n]{0,40}\b(?:[A-Z0-9_]*(?:SECRET|PASSWORD|API[_-]?KEY|TOKEN|CREDENTIAL)[A-Z0-9_]*)\b/i,
|
|
217
|
+
message: (h) => `exposes a secret: "${h.trim()}"`,
|
|
218
|
+
remediation: "never echo/log secrets; read them from the environment only",
|
|
219
|
+
},
|
|
220
|
+
// Network use — fetch commands that AFK policy may forbid.
|
|
221
|
+
{
|
|
222
|
+
rule: "network-use",
|
|
223
|
+
severity: "warn",
|
|
224
|
+
re: /\b(?:curl|wget|nc|netcat|ssh|scp|telnet)\s+\S+/i,
|
|
225
|
+
message: (h) => `performs network I/O: "${h.trim()}"`,
|
|
226
|
+
remediation: "AFK network is policy-scoped; declare needed domains in .otto/policy.json or avoid the call",
|
|
227
|
+
},
|
|
228
|
+
// Interactive hard stops — block AFK, mark the skill interactive-only.
|
|
229
|
+
{
|
|
230
|
+
rule: "interactive-hard-stop",
|
|
231
|
+
severity: "warn",
|
|
232
|
+
re: /\b(?:ask|prompt|wait for|confirm with|check with|get approval from)\b[^\n]{0,20}\b(?:the\s+)?(?:user|human|operator|reviewer)\b|\bdo not proceed until\b|\bSTOP and (?:ask|wait)\b|\bwait for (?:confirmation|approval|input)\b/i,
|
|
233
|
+
message: (h) => `requires human interaction: "${h.trim()}"`,
|
|
234
|
+
remediation: "provide an autonomous default for AFK, or classify the skill interactive-only",
|
|
235
|
+
},
|
|
236
|
+
// Unsupported tool assumptions — GUI/IDE/browser the AFK runtime lacks.
|
|
237
|
+
{
|
|
238
|
+
rule: "unsupported-tool",
|
|
239
|
+
severity: "warn",
|
|
240
|
+
re: /\b(?:open your browser|in your IDE|open .{0,20}in (?:vs ?code|the editor)|click (?:the|on)|drag and drop|take a screenshot)\b/i,
|
|
241
|
+
message: (h) => `assumes an interactive tool the AFK runtime lacks: "${h.trim()}"`,
|
|
242
|
+
remediation: "replace GUI/IDE steps with CLI/file operations",
|
|
243
|
+
},
|
|
244
|
+
// Conflicting hierarchy — attempts to overrule repo policy / prior context.
|
|
245
|
+
{
|
|
246
|
+
rule: "conflicting-hierarchy",
|
|
247
|
+
severity: "error",
|
|
248
|
+
re: /\b(?:ignore|disregard|override|bypass|forget)\b[^\n]{0,30}\b(?:previous|prior|above|all)?\s*(?:instructions?|system prompt|repo policy|safety|policy|agents\.md|rules?)\b/i,
|
|
249
|
+
message: (h) => `tries to overrule the instruction hierarchy: "${h.trim()}"`,
|
|
250
|
+
remediation: "skills are advisory; remove overrule language — repo policy and stage contracts win",
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
/**
|
|
254
|
+
* Scan a skill's instruction body for the six P17 risk categories (issue #133):
|
|
255
|
+
* unsafe shell advice, secret handling, network use, interactive hard stops,
|
|
256
|
+
* unsupported tool assumptions, and conflicting hierarchy (a skill trying to
|
|
257
|
+
* overrule repo policy). Each finding names the exact matched phrase and a
|
|
258
|
+
* remediation. Pure, deterministic, conservative — benign guidance yields `[]`.
|
|
259
|
+
*/
|
|
260
|
+
export function scanInstructionRisks(instructions) {
|
|
261
|
+
const findings = [];
|
|
262
|
+
for (const p of RISK_PATTERNS) {
|
|
263
|
+
const m = p.re.exec(instructions);
|
|
264
|
+
if (m) {
|
|
265
|
+
findings.push({
|
|
266
|
+
kind: "risk",
|
|
267
|
+
severity: p.severity,
|
|
268
|
+
rule: p.rule,
|
|
269
|
+
message: p.message(m[0]),
|
|
270
|
+
remediation: p.remediation,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return findings;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The standard drills from the roadmap (issue #135): a Superpowers planning/TDD
|
|
278
|
+
* drill (the methodology must be usable, not interactive-only/blocked), a PM
|
|
279
|
+
* roadmap/PRD drill (must scope to the plan stage), and a review drill (a review
|
|
280
|
+
* skill must not try to overrule repo policy).
|
|
281
|
+
*/
|
|
282
|
+
export const STANDARD_DRILLS = [
|
|
283
|
+
{
|
|
284
|
+
name: "planning-tdd-usable",
|
|
285
|
+
description: "a planning/TDD skill must be applicable, not interactive-only or blocked",
|
|
286
|
+
appliesTo: ["planning", "tdd"],
|
|
287
|
+
expectClassIn: ["afk-safe", "stage-scoped"],
|
|
288
|
+
forbidRules: [],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "pm-roadmap-prd-stage-scoped",
|
|
292
|
+
description: "a PM roadmap/PRD skill must scope to a stage (plan), not be blocked",
|
|
293
|
+
appliesTo: ["roadmap-planning", "prd"],
|
|
294
|
+
expectClassIn: ["stage-scoped", "afk-safe"],
|
|
295
|
+
forbidRules: [],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "review-respects-policy",
|
|
299
|
+
description: "a review skill must not try to overrule repo policy",
|
|
300
|
+
appliesTo: ["code-review", "review"],
|
|
301
|
+
expectClassIn: ["stage-scoped", "afk-safe"],
|
|
302
|
+
forbidRules: ["conflicting-hierarchy"],
|
|
303
|
+
},
|
|
304
|
+
];
|
|
305
|
+
/**
|
|
306
|
+
* Run behavior drills against a validation report (issue #135). A drill applies
|
|
307
|
+
* when the skill declares one of its `appliesTo` capabilities; an applied drill
|
|
308
|
+
* passes when the report's class is in `expectClassIn` and none of its
|
|
309
|
+
* `forbidRules` fired. A non-applicable drill is reported `applied: false,
|
|
310
|
+
* passed: true` so it never blocks an unrelated skill. Pure, deterministic.
|
|
311
|
+
*/
|
|
312
|
+
export function runDrills(report, drills = STANDARD_DRILLS) {
|
|
313
|
+
const caps = new Set(report.capabilities.map((c) => c.toLowerCase()));
|
|
314
|
+
return drills.map((drill) => {
|
|
315
|
+
const applied = drill.appliesTo.some((c) => caps.has(c.toLowerCase()));
|
|
316
|
+
if (!applied) {
|
|
317
|
+
return {
|
|
318
|
+
drill: drill.name,
|
|
319
|
+
applied: false,
|
|
320
|
+
passed: true,
|
|
321
|
+
detail: "not in scope",
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const classOk = drill.expectClassIn.includes(report.compatibility);
|
|
325
|
+
const firedForbidden = drill.forbidRules.filter((r) => report.findings.some((f) => f.rule === r));
|
|
326
|
+
const passed = classOk && firedForbidden.length === 0;
|
|
327
|
+
const detail = passed
|
|
328
|
+
? `class ${report.compatibility} accepted`
|
|
329
|
+
: !classOk
|
|
330
|
+
? `class ${report.compatibility} not in {${drill.expectClassIn.join(", ")}}`
|
|
331
|
+
: `forbidden finding(s): ${firedForbidden.join(", ")}`;
|
|
332
|
+
return { drill: drill.name, applied: true, passed, detail };
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Whether a statically-validated skill must be revalidated before reuse (issue
|
|
337
|
+
* #135). True when the skill carries a recorded `instructionsChecksum` (it was
|
|
338
|
+
* validated) but its current body no longer hashes to it — the skill drifted
|
|
339
|
+
* (e.g. an upstream re-sync changed it) since the gate last ran. A skill that was
|
|
340
|
+
* never statically validated has nothing to revalidate, so this is false. Pure.
|
|
341
|
+
*/
|
|
342
|
+
export function needsRevalidation(skill) {
|
|
343
|
+
const recorded = skill.validation.instructionsChecksum;
|
|
344
|
+
if (!recorded)
|
|
345
|
+
return false;
|
|
346
|
+
return skillChecksum(skill.instructions) !== recorded;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Run the full static gate over one skill and aggregate the result: manifest
|
|
350
|
+
* lint, capability extraction, provenance check (#132), instruction-risk scan
|
|
351
|
+
* (#133), derived compatibility class (#134), and behavior drills (#135). `ok` is
|
|
352
|
+
* false when any `error`-severity finding fired OR an applicable drill failed.
|
|
353
|
+
* Pure — the caller decides whether to persist or print the report.
|
|
354
|
+
*/
|
|
355
|
+
export function validateSkill(skill, opts = {}) {
|
|
356
|
+
const findings = [
|
|
357
|
+
...lintManifest(skill),
|
|
358
|
+
...checkProvenance(skill, opts.source),
|
|
359
|
+
...scanInstructionRisks(skill.instructions),
|
|
360
|
+
];
|
|
361
|
+
const base = {
|
|
362
|
+
skill: skill.name,
|
|
363
|
+
capabilities: extractCapabilities(skill),
|
|
364
|
+
findings,
|
|
365
|
+
ok: !findings.some((f) => f.severity === "error"),
|
|
366
|
+
compatibility: "afk-safe",
|
|
367
|
+
stages: [],
|
|
368
|
+
checksum: skillChecksum(skill.instructions),
|
|
369
|
+
drills: [],
|
|
370
|
+
};
|
|
371
|
+
const { compatibility, stages } = classifyCompatibility(base);
|
|
372
|
+
const drills = runDrills({ ...base, compatibility, stages });
|
|
373
|
+
const ok = base.ok && drills.every((d) => d.passed);
|
|
374
|
+
return { ...base, compatibility, stages, drills, ok };
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=skill-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-validation.js","sourceRoot":"","sources":["../src/skill-validation.ts"],"names":[],"mappings":"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"}
|
package/dist/skills-cli.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ExternalAuditFinding, type ExternalSkillSource, type SyncPlan } from "./external-skills.js";
|
|
2
|
+
import { type SkillValidationReport } from "./skill-validation.js";
|
|
3
|
+
import { type SkillRouteResult } from "./skill-routing.js";
|
|
2
4
|
import { type Skill, type SkillCandidate, type SkillMatch } from "./skills.js";
|
|
3
5
|
/**
|
|
4
6
|
* Injectable host surface for {@link runSkills} so the bin stays unit-testable
|
|
@@ -26,12 +28,24 @@ export declare function formatSkillsAudit(skills: Skill[], now?: Date): string;
|
|
|
26
28
|
* "inspect why a skill was selected" metric). Pure.
|
|
27
29
|
*/
|
|
28
30
|
export declare function formatWhy(matches: SkillMatch[]): string;
|
|
31
|
+
/**
|
|
32
|
+
* Render the stage-routing explanation (issue #140 P18): which validated skills
|
|
33
|
+
* would be injected into `stageName` and why, plus the char-budget accounting —
|
|
34
|
+
* so a user can reproduce the runtime selection from the CLI alone. Pure.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatWhyStage(stageName: string, result: SkillRouteResult): string;
|
|
29
37
|
/** Render the candidate skills suggested from repeated successful runs. Pure. */
|
|
30
38
|
export declare function formatCandidates(candidates: SkillCandidate[]): string;
|
|
31
39
|
/** Render the configured external sources, sorted. Pure. */
|
|
32
40
|
export declare function formatSources(sources: ExternalSkillSource[]): string;
|
|
33
41
|
/** Render a sync plan; `dryRun` flips the header between preview and applied. Pure. */
|
|
34
42
|
export declare function formatSyncPlan(plan: SyncPlan, dryRun: boolean): string;
|
|
43
|
+
/**
|
|
44
|
+
* Render a skill validation report (issue #113 P17): the extracted capabilities,
|
|
45
|
+
* each finding with its severity/rule/message/remediation, and a pass/fail line.
|
|
46
|
+
* Pure — the bin decides the exit code from `report.ok`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatValidationReport(report: SkillValidationReport): string;
|
|
35
49
|
/** Render the external-registry audit findings. Pure. */
|
|
36
50
|
export declare function formatExternalAudit(findings: ExternalAuditFinding[]): string;
|
|
37
51
|
/**
|
package/dist/skills-cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-cli.d.ts","sourceRoot":"","sources":["../src/skills-cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAUL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAExB,KAAK,QAAQ,EACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,
|
|
1
|
+
{"version":3,"file":"skills-cli.d.ts","sourceRoot":"","sources":["../src/skills-cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAUL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAExB,KAAK,QAAQ,EACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAUL,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B,CAAC;AAuBF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,GAAE,IAAiB,GACrB,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,GAAE,IAAiB,GACrB,MAAM,CA6BR;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAUvD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB,MAAM,CAkBR;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,CAWrE;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAWpE;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAqBtE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CA8B5E;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAK5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE,UAAwB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA8HjB"}
|
package/dist/skills-cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { addSource, applySync, auditExternal, importedChecksum, planSync, readLock, readSources, removeSource, writeSources, } from "./external-skills.js";
|
|
3
3
|
import { listRunIds, readManifest } from "./run-report.js";
|
|
4
|
-
import {
|
|
4
|
+
import { needsRevalidation, validateSkill, } from "./skill-validation.js";
|
|
5
|
+
import { routeSkillsForStage } from "./skill-routing.js";
|
|
6
|
+
import { findSkillCandidates, readSkill, readSkills, recordStaticValidation, selectSkills, skillsDir, skillStatus, writeSkill, } from "./skills.js";
|
|
5
7
|
const defaultDeps = {
|
|
6
8
|
env: process.env,
|
|
7
9
|
cwd: process.cwd(),
|
|
@@ -10,6 +12,8 @@ const defaultDeps = {
|
|
|
10
12
|
};
|
|
11
13
|
const USAGE = "Usage: otto-skills <list|audit|candidates>\n" +
|
|
12
14
|
" otto-skills why <changed-path>...\n" +
|
|
15
|
+
" otto-skills why --stage <stage> [--changed <path>...]\n" +
|
|
16
|
+
" otto-skills validate <skill> [--source <name>]\n" +
|
|
13
17
|
" otto-skills sources <list|add|remove> ...\n" +
|
|
14
18
|
" otto-skills sync [--dry-run]\n" +
|
|
15
19
|
" otto-skills audit --external";
|
|
@@ -54,6 +58,16 @@ export function formatSkillsAudit(skills, now = new Date()) {
|
|
|
54
58
|
for (const { s, st } of notUsable) {
|
|
55
59
|
lines.push(` - ${s.name} [${st}] (run its tests, then record a validating run)`);
|
|
56
60
|
}
|
|
61
|
+
// Body-drift detection (issue #135): a statically-validated skill whose body
|
|
62
|
+
// changed since the gate ran must be revalidated before it is reused.
|
|
63
|
+
const drifted = skills.filter(needsRevalidation);
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(`Needs revalidation — body drifted (${drifted.length}):`);
|
|
66
|
+
if (drifted.length === 0)
|
|
67
|
+
lines.push(" (none)");
|
|
68
|
+
for (const s of drifted) {
|
|
69
|
+
lines.push(` - ${s.name} (re-run: otto-skills validate ${s.name})`);
|
|
70
|
+
}
|
|
57
71
|
return lines.join("\n");
|
|
58
72
|
}
|
|
59
73
|
/**
|
|
@@ -72,6 +86,29 @@ export function formatWhy(matches) {
|
|
|
72
86
|
}
|
|
73
87
|
return lines.join("\n");
|
|
74
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Render the stage-routing explanation (issue #140 P18): which validated skills
|
|
91
|
+
* would be injected into `stageName` and why, plus the char-budget accounting —
|
|
92
|
+
* so a user can reproduce the runtime selection from the CLI alone. Pure.
|
|
93
|
+
*/
|
|
94
|
+
export function formatWhyStage(stageName, result) {
|
|
95
|
+
const lines = [
|
|
96
|
+
`Stage '${stageName}' (family: ${result.family ?? "unknown"}) — skill routing:`,
|
|
97
|
+
];
|
|
98
|
+
if (result.verdicts.length === 0) {
|
|
99
|
+
lines.push(" No skills installed.");
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
for (const v of result.verdicts) {
|
|
103
|
+
const tag = v.selected ? "selected" : v.eligible ? "eligible" : "skip";
|
|
104
|
+
lines.push(`- ${v.name} [${tag}] score ${v.score}`);
|
|
105
|
+
for (const r of v.reasons)
|
|
106
|
+
lines.push(` · ${r}`);
|
|
107
|
+
}
|
|
108
|
+
lines.push("");
|
|
109
|
+
lines.push(`budget: ${result.usedChars}/${result.budgetChars} chars used by ${result.selected.length} selected skill(s)`);
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
75
112
|
/** Render the candidate skills suggested from repeated successful runs. Pure. */
|
|
76
113
|
export function formatCandidates(candidates) {
|
|
77
114
|
if (candidates.length === 0) {
|
|
@@ -116,6 +153,40 @@ export function formatSyncPlan(plan, dryRun) {
|
|
|
116
153
|
}
|
|
117
154
|
return lines.join("\n");
|
|
118
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Render a skill validation report (issue #113 P17): the extracted capabilities,
|
|
158
|
+
* each finding with its severity/rule/message/remediation, and a pass/fail line.
|
|
159
|
+
* Pure — the bin decides the exit code from `report.ok`.
|
|
160
|
+
*/
|
|
161
|
+
export function formatValidationReport(report) {
|
|
162
|
+
const scope = report.compatibility === "stage-scoped" && report.stages.length
|
|
163
|
+
? ` (${report.stages.join(", ")})`
|
|
164
|
+
: "";
|
|
165
|
+
const lines = [
|
|
166
|
+
`Validating skill '${report.skill}'`,
|
|
167
|
+
` capabilities: ${report.capabilities.length ? report.capabilities.join(", ") : "(none)"}`,
|
|
168
|
+
` compatibility: ${report.compatibility}${scope}`,
|
|
169
|
+
];
|
|
170
|
+
if (report.findings.length === 0) {
|
|
171
|
+
lines.push(" no findings");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
for (const f of report.findings) {
|
|
175
|
+
lines.push(` [${f.severity}] ${f.rule}: ${f.message}`);
|
|
176
|
+
lines.push(` → ${f.remediation}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const applied = report.drills.filter((d) => d.applied);
|
|
180
|
+
if (applied.length > 0) {
|
|
181
|
+
lines.push(" drills:");
|
|
182
|
+
for (const d of applied) {
|
|
183
|
+
lines.push(` ${d.passed ? "pass" : "FAIL"} ${d.drill}: ${d.detail}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push(report.ok ? "PASS (no blocking errors)" : "FAIL (blocking errors present)");
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
119
190
|
/** Render the external-registry audit findings. Pure. */
|
|
120
191
|
export function formatExternalAudit(findings) {
|
|
121
192
|
if (findings.length === 0)
|
|
@@ -143,7 +214,15 @@ export async function runSkills(argv, deps = defaultDeps) {
|
|
|
143
214
|
deps.out(USAGE);
|
|
144
215
|
return 0;
|
|
145
216
|
}
|
|
146
|
-
const known = [
|
|
217
|
+
const known = [
|
|
218
|
+
"list",
|
|
219
|
+
"audit",
|
|
220
|
+
"why",
|
|
221
|
+
"validate",
|
|
222
|
+
"candidates",
|
|
223
|
+
"sources",
|
|
224
|
+
"sync",
|
|
225
|
+
];
|
|
147
226
|
if (arg !== undefined && !known.includes(arg)) {
|
|
148
227
|
deps.err(`Unknown subcommand '${arg}'.\n${USAGE}`);
|
|
149
228
|
return 1;
|
|
@@ -162,14 +241,54 @@ export async function runSkills(argv, deps = defaultDeps) {
|
|
|
162
241
|
}
|
|
163
242
|
const skills = readSkills(workspaceDir);
|
|
164
243
|
if (arg === "why") {
|
|
165
|
-
const
|
|
244
|
+
const rest = argv.slice(1);
|
|
245
|
+
const stage = flagValue(rest, "--stage");
|
|
246
|
+
if (stage) {
|
|
247
|
+
// P18: route validated skills for a concrete stage (with optional --changed
|
|
248
|
+
// paths for scope scoring), explaining the runtime selection.
|
|
249
|
+
const changedIdx = rest.indexOf("--changed");
|
|
250
|
+
const changed = changedIdx >= 0
|
|
251
|
+
? rest.slice(changedIdx + 1).filter((a) => !a.startsWith("--"))
|
|
252
|
+
: [];
|
|
253
|
+
const result = routeSkillsForStage(skills, {
|
|
254
|
+
stageName: stage,
|
|
255
|
+
changedPaths: changed,
|
|
256
|
+
});
|
|
257
|
+
deps.out(formatWhyStage(stage, result));
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
const paths = rest;
|
|
166
261
|
if (paths.length === 0) {
|
|
167
|
-
deps.err(`why needs at least one changed path.\n${USAGE}`);
|
|
262
|
+
deps.err(`why needs at least one changed path (or --stage <stage>).\n${USAGE}`);
|
|
168
263
|
return 1;
|
|
169
264
|
}
|
|
170
265
|
deps.out(formatWhy(selectSkills(skills, { changedPaths: paths })));
|
|
171
266
|
return 0;
|
|
172
267
|
}
|
|
268
|
+
if (arg === "validate") {
|
|
269
|
+
const rest = argv.slice(1);
|
|
270
|
+
const name = rest.find((a) => !a.startsWith("--"));
|
|
271
|
+
if (!name) {
|
|
272
|
+
deps.err(`validate needs a skill name.\n${USAGE}`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
const skill = readSkill(workspaceDir, name);
|
|
276
|
+
if (!skill) {
|
|
277
|
+
deps.err(`No skill named '${name}' under ${skillsDir(workspaceDir)}.`);
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
const source = flagValue(rest, "--source");
|
|
281
|
+
const report = validateSkill(skill, source ? { source } : {});
|
|
282
|
+
deps.out(formatValidationReport(report));
|
|
283
|
+
// Persist the static-gate outcome (issue #134). Recording a class does NOT
|
|
284
|
+
// make the skill eligible — selection stays a separate concern (P18).
|
|
285
|
+
writeSkill(workspaceDir, recordStaticValidation(skill, {
|
|
286
|
+
compatibility: report.compatibility,
|
|
287
|
+
stages: report.stages,
|
|
288
|
+
checksum: report.checksum,
|
|
289
|
+
}));
|
|
290
|
+
return report.ok ? 0 : 1;
|
|
291
|
+
}
|
|
173
292
|
if (arg === "candidates") {
|
|
174
293
|
const runs = listRunIds(workspaceDir)
|
|
175
294
|
.map((id) => readManifest(workspaceDir, id))
|