@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.
Files changed (52) hide show
  1. package/dist/cli-help.d.ts +4 -0
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +5 -0
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +5 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/inspect.d.ts.map +1 -1
  10. package/dist/inspect.js +16 -0
  11. package/dist/inspect.js.map +1 -1
  12. package/dist/loop.d.ts +5 -0
  13. package/dist/loop.d.ts.map +1 -1
  14. package/dist/loop.js +48 -1
  15. package/dist/loop.js.map +1 -1
  16. package/dist/report-explain.d.ts.map +1 -1
  17. package/dist/report-explain.js +4 -0
  18. package/dist/report-explain.js.map +1 -1
  19. package/dist/run-bin.d.ts.map +1 -1
  20. package/dist/run-bin.js +10 -0
  21. package/dist/run-bin.js.map +1 -1
  22. package/dist/run-report.d.ts +6 -0
  23. package/dist/run-report.d.ts.map +1 -1
  24. package/dist/run-report.js.map +1 -1
  25. package/dist/runner.d.ts +4 -1
  26. package/dist/runner.d.ts.map +1 -1
  27. package/dist/runner.js.map +1 -1
  28. package/dist/skill-activation.d.ts +47 -0
  29. package/dist/skill-activation.d.ts.map +1 -0
  30. package/dist/skill-activation.js +57 -0
  31. package/dist/skill-activation.js.map +1 -0
  32. package/dist/skill-routing.d.ts +83 -0
  33. package/dist/skill-routing.d.ts.map +1 -0
  34. package/dist/skill-routing.js +245 -0
  35. package/dist/skill-routing.js.map +1 -0
  36. package/dist/skill-validation.d.ts +155 -0
  37. package/dist/skill-validation.d.ts.map +1 -0
  38. package/dist/skill-validation.js +376 -0
  39. package/dist/skill-validation.js.map +1 -0
  40. package/dist/skills-cli.d.ts +14 -0
  41. package/dist/skills-cli.d.ts.map +1 -1
  42. package/dist/skills-cli.js +123 -4
  43. package/dist/skills-cli.js.map +1 -1
  44. package/dist/skills.d.ts +40 -4
  45. package/dist/skills.d.ts.map +1 -1
  46. package/dist/skills.js +43 -1
  47. package/dist/skills.js.map +1 -1
  48. package/dist/stage-exec.d.ts +4 -0
  49. package/dist/stage-exec.d.ts.map +1 -1
  50. package/dist/stage-exec.js +6 -0
  51. package/dist/stage-exec.js.map +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const STAGE_FAMILIES = [
4
+ "plan",
5
+ "implement",
6
+ "review",
7
+ "report",
8
+ "journal",
9
+ ];
10
+ const TRUTHY = new Set(["1", "true", "yes", "on"]);
11
+ /**
12
+ * Read `.otto/config.json`'s `skills` block as a raw record (the parser in
13
+ * {@link resolveSkillActivation} normalizes it). Absent/malformed file or block →
14
+ * undefined (zero behavior change). Mirrors `readJournalConfig`/`readAgentConfig`.
15
+ */
16
+ export function readSkillsConfig(workspaceDir) {
17
+ try {
18
+ const raw = JSON.parse(readFileSync(join(workspaceDir, ".otto", "config.json"), "utf8"));
19
+ return raw.skills;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ /**
26
+ * Resolve activation from flag/env/config. `--use-skills` (flag) or a truthy
27
+ * `OTTO_USE_SKILLS` (env) force-enable regardless of config; otherwise
28
+ * `config.skills.enabled === true` enables. Per-family booleans on the config
29
+ * block are carried through as overrides. Pure — the caller supplies the raw
30
+ * inputs (mirrors `resolveAgentRuntime`).
31
+ */
32
+ export function resolveSkillActivation(opts) {
33
+ const cfg = opts.config !== null &&
34
+ typeof opts.config === "object" &&
35
+ !Array.isArray(opts.config)
36
+ ? opts.config
37
+ : {};
38
+ const envOn = TRUTHY.has((opts.env ?? "").trim().toLowerCase());
39
+ const enabled = opts.flag === true || envOn || cfg.enabled === true;
40
+ const stages = {};
41
+ for (const family of STAGE_FAMILIES) {
42
+ if (typeof cfg[family] === "boolean")
43
+ stages[family] = cfg[family];
44
+ }
45
+ return { enabled, stages };
46
+ }
47
+ /**
48
+ * Whether skills are active for a given stage family: the master switch must be
49
+ * on AND the family's override (if any) must not be false. A family with no
50
+ * override follows the master switch. Pure.
51
+ */
52
+ export function stageEnabled(activation, family) {
53
+ if (!activation.enabled)
54
+ return false;
55
+ return activation.stages[family] !== false;
56
+ }
57
+ //# sourceMappingURL=skill-activation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-activation.js","sourceRoot":"","sources":["../src/skill-activation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAwBjC,MAAM,cAAc,GAA2B;IAC7C,MAAM;IACN,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,SAAS;CACV,CAAC;AAUF,MAAM,MAAM,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAExE;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CACtC,CAAC;QAC7B,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAItC;IACC,MAAM,GAAG,GACP,IAAI,CAAC,MAAM,KAAK,IAAI;QACpB,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;QACzB,CAAC,CAAE,IAAI,CAAC,MAAkC;QAC1C,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;IAEpE,MAAM,MAAM,GAA0C,EAAE,CAAC;IACzD,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;QACpC,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS;YAClC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAY,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA2B,EAC3B,MAAmB;IAEnB,IAAI,CAAC,UAAU,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,83 @@
1
+ import type { StageFamily } from "./skill-activation.js";
2
+ import type { SkillUsage } from "./run-report.js";
3
+ import { type Skill } from "./skills.js";
4
+ /**
5
+ * Stage-scoped skill retrieval (issue #137 P18). Given the skills installed under
6
+ * `.otto/skills/`, the running stage, and the iteration's changed files, decide
7
+ * which **validated, compatible, relevant** skills to inject — bounded by a hard
8
+ * char budget so skill text can never crowd out the real prompt.
9
+ *
10
+ * Eligibility keys on the P17 static gate, NOT the older run-validation:
11
+ * a skill is selectable only when it carries a recorded `compatibility` that is
12
+ * neither `blocked` nor `interactive-only` (AFK is non-interactive), its body has
13
+ * not drifted since validation (`needsRevalidation`), and its scope fits the
14
+ * stage — `afk-safe` anywhere, `stage-scoped` only on its declared stages. Among
15
+ * eligible skills, relevance is scored by scope-glob match against the changed
16
+ * files and specificity; a skill whose constraints forbid the change's risk class
17
+ * is excluded. Pure, deterministic — the loop does the injection + evidence.
18
+ */
19
+ /** Default total char budget for all injected skill text in one stage. */
20
+ export declare const DEFAULT_SKILLS_BUDGET_CHARS = 4000;
21
+ /** Default per-skill excerpt cap, so one skill cannot consume the whole budget. */
22
+ export declare const DEFAULT_PER_SKILL_CHARS = 1200;
23
+ /** Map a concrete stage name (`stages.ts`) to its family, or null if unknown. */
24
+ export declare function stageFamily(stageName: string): StageFamily | null;
25
+ /** One skill's routing verdict — for `otto-skills why --stage`. */
26
+ export type SkillRouteVerdict = {
27
+ name: string;
28
+ eligible: boolean;
29
+ selected: boolean;
30
+ score: number;
31
+ reasons: string[];
32
+ };
33
+ /** A selected skill plus its bounded excerpt and char cost. */
34
+ export type SkillRouteSelection = {
35
+ skill: Skill;
36
+ reasons: string[];
37
+ /** Bounded instruction excerpt (never the full library). */
38
+ excerpt: string;
39
+ chars: number;
40
+ };
41
+ /** The result of routing skills for one stage. */
42
+ export type SkillRouteResult = {
43
+ family: StageFamily | null;
44
+ /** Selected skills, highest-ranked first, within the char budget. */
45
+ selected: SkillRouteSelection[];
46
+ /** Every skill considered, with eligibility + reasons. */
47
+ verdicts: SkillRouteVerdict[];
48
+ budgetChars: number;
49
+ usedChars: number;
50
+ };
51
+ /** Trim a skill body to a char cap, cutting on a line boundary when possible. */
52
+ export declare function boundExcerpt(instructions: string, cap?: number): string;
53
+ /**
54
+ * Route skills for a stage: assess each, rank eligible ones (score desc, then
55
+ * name), then greedily fill the char budget with bounded excerpts. Returns the
56
+ * selection (for injection), the full verdict list (for `why --stage`), and the
57
+ * char accounting. A skill that does not fit the remaining budget is dropped with
58
+ * a reason rather than truncated mid-excerpt. Pure.
59
+ */
60
+ export declare function routeSkillsForStage(skills: Skill[], opts: {
61
+ stageName: string;
62
+ changedPaths?: string[];
63
+ budgetChars?: number;
64
+ perSkillChars?: number;
65
+ now?: Date;
66
+ }): SkillRouteResult;
67
+ /**
68
+ * Render selected skills into a bounded, attributed `<available-skills>` block
69
+ * for prompt injection (issue #138). Each skill is labelled with its source, ref,
70
+ * version, and checksum so a report can trace exactly which instruction shaped the
71
+ * run. The block carries a standing note that repo policy + stage contracts
72
+ * outrank skills and that conflicts must be reported, not silently merged. Returns
73
+ * `""` when nothing is selected, so an opted-in stage with no eligible skill is
74
+ * byte-for-byte unchanged. Pure.
75
+ */
76
+ export declare function formatSkillInjection(selected: SkillRouteSelection[]): string;
77
+ /**
78
+ * Convert selections into `SkillUsage[]` evidence (issue #139): name, version,
79
+ * source, ref, the stage that consumed them, and the retrieval reasons — so a run
80
+ * report can reproduce why each skill was selected from the bundle alone. Pure.
81
+ */
82
+ export declare function toSkillUsages(selected: SkillRouteSelection[], stage: string): SkillUsage[];
83
+ //# sourceMappingURL=skill-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-routing.d.ts","sourceRoot":"","sources":["../src/skill-routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAUjE;AAED,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,kDAAkD;AAClD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,GAAG,GAAE,MAAgC,GACpC,MAAM,CAOR;AAyGD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,KAAK,EAAE,EACf,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,gBAAgB,CA8ClB;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAU5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,KAAK,EAAE,MAAM,GACZ,UAAU,EAAE,CAiBd"}
@@ -0,0 +1,245 @@
1
+ import { classifyRisk } from "./risk.js";
2
+ import { needsRevalidation } from "./skill-validation.js";
3
+ import { globMatch } from "./skills.js";
4
+ /**
5
+ * Stage-scoped skill retrieval (issue #137 P18). Given the skills installed under
6
+ * `.otto/skills/`, the running stage, and the iteration's changed files, decide
7
+ * which **validated, compatible, relevant** skills to inject — bounded by a hard
8
+ * char budget so skill text can never crowd out the real prompt.
9
+ *
10
+ * Eligibility keys on the P17 static gate, NOT the older run-validation:
11
+ * a skill is selectable only when it carries a recorded `compatibility` that is
12
+ * neither `blocked` nor `interactive-only` (AFK is non-interactive), its body has
13
+ * not drifted since validation (`needsRevalidation`), and its scope fits the
14
+ * stage — `afk-safe` anywhere, `stage-scoped` only on its declared stages. Among
15
+ * eligible skills, relevance is scored by scope-glob match against the changed
16
+ * files and specificity; a skill whose constraints forbid the change's risk class
17
+ * is excluded. Pure, deterministic — the loop does the injection + evidence.
18
+ */
19
+ /** Default total char budget for all injected skill text in one stage. */
20
+ export const DEFAULT_SKILLS_BUDGET_CHARS = 4000;
21
+ /** Default per-skill excerpt cap, so one skill cannot consume the whole budget. */
22
+ export const DEFAULT_PER_SKILL_CHARS = 1200;
23
+ /** Map a concrete stage name (`stages.ts`) to its family, or null if unknown. */
24
+ export function stageFamily(stageName) {
25
+ const n = stageName.toLowerCase();
26
+ // "apply-review-implementer" contains both — implement wins (it writes code).
27
+ if (n.includes("implement"))
28
+ return "implement";
29
+ if (n === "verifier")
30
+ return "implement";
31
+ if (n === "plan")
32
+ return "plan";
33
+ if (n.includes("review"))
34
+ return "review";
35
+ if (n.includes("report"))
36
+ return "report";
37
+ if (n.includes("journal"))
38
+ return "journal";
39
+ return null;
40
+ }
41
+ /** Trim a skill body to a char cap, cutting on a line boundary when possible. */
42
+ export function boundExcerpt(instructions, cap = DEFAULT_PER_SKILL_CHARS) {
43
+ const body = instructions.trim();
44
+ if (body.length <= cap)
45
+ return body;
46
+ const slice = body.slice(0, cap);
47
+ const lastNl = slice.lastIndexOf("\n");
48
+ const cut = lastNl > cap * 0.5 ? slice.slice(0, lastNl) : slice;
49
+ return cut.trimEnd() + "\n…";
50
+ }
51
+ /** Decide eligibility for `skill` on `family`, accumulating human reasons. */
52
+ function assess(skill, family, changedPaths, now) {
53
+ const reasons = [];
54
+ const v = skill.validation;
55
+ if (!v.compatibility) {
56
+ return {
57
+ eligible: false,
58
+ score: 0,
59
+ reasons: [
60
+ "not validated (no compatibility recorded — run otto-skills validate)",
61
+ ],
62
+ };
63
+ }
64
+ if (v.compatibility === "blocked") {
65
+ return {
66
+ eligible: false,
67
+ score: 0,
68
+ reasons: ["blocked by the validation gate"],
69
+ };
70
+ }
71
+ if (v.compatibility === "interactive-only") {
72
+ return {
73
+ eligible: false,
74
+ score: 0,
75
+ reasons: ["interactive-only — needs a human, unsafe for AFK stages"],
76
+ };
77
+ }
78
+ if (needsRevalidation(skill)) {
79
+ return {
80
+ eligible: false,
81
+ score: 0,
82
+ reasons: [
83
+ "body drifted since validation — needs revalidation before reuse",
84
+ ],
85
+ };
86
+ }
87
+ if (family === null) {
88
+ return { eligible: false, score: 0, reasons: ["unknown stage family"] };
89
+ }
90
+ let score = 0;
91
+ if (v.compatibility === "stage-scoped") {
92
+ if (!(v.stages ?? []).includes(family)) {
93
+ return {
94
+ eligible: false,
95
+ score: 0,
96
+ reasons: [
97
+ `stage-scoped to ${(v.stages ?? []).join(", ") || "(none)"} — not ${family}`,
98
+ ],
99
+ };
100
+ }
101
+ score += 1; // a skill scoped to exactly this stage is more specific than afk-safe
102
+ reasons.push(`stage-scoped to ${family}`);
103
+ }
104
+ else {
105
+ reasons.push("afk-safe (usable on any stage)");
106
+ }
107
+ // Risk-class exclusion + scope-glob relevance against the changed files.
108
+ if (changedPaths.length > 0) {
109
+ const assessment = classifyRisk(changedPaths);
110
+ const forbidden = skill.constraints.some((c) => c.toLowerCase().includes(assessment.class));
111
+ if (forbidden) {
112
+ return {
113
+ eligible: false,
114
+ score: 0,
115
+ reasons: [
116
+ `excluded by constraint for risk class "${assessment.class}"`,
117
+ ],
118
+ };
119
+ }
120
+ if (skill.scope.length === 0) {
121
+ score += 1;
122
+ reasons.push("repo-wide (no scope restriction)");
123
+ }
124
+ else {
125
+ const hit = changedPaths.filter((p) => skill.scope.some((g) => globMatch(g, p)));
126
+ if (hit.length > 0) {
127
+ score += 2;
128
+ reasons.push(`scope matches changed file(s): ${hit.join(", ")}`);
129
+ }
130
+ else {
131
+ reasons.push("scope does not match the changed files");
132
+ }
133
+ }
134
+ }
135
+ return { eligible: true, score, reasons };
136
+ }
137
+ /**
138
+ * Route skills for a stage: assess each, rank eligible ones (score desc, then
139
+ * name), then greedily fill the char budget with bounded excerpts. Returns the
140
+ * selection (for injection), the full verdict list (for `why --stage`), and the
141
+ * char accounting. A skill that does not fit the remaining budget is dropped with
142
+ * a reason rather than truncated mid-excerpt. Pure.
143
+ */
144
+ export function routeSkillsForStage(skills, opts) {
145
+ const family = stageFamily(opts.stageName);
146
+ const changedPaths = opts.changedPaths ?? [];
147
+ const budgetChars = opts.budgetChars ?? DEFAULT_SKILLS_BUDGET_CHARS;
148
+ const perSkillChars = opts.perSkillChars ?? DEFAULT_PER_SKILL_CHARS;
149
+ const eligible = [];
150
+ const verdicts = [];
151
+ for (const skill of skills) {
152
+ const a = assess(skill, family, changedPaths, opts.now);
153
+ verdicts.push({
154
+ name: skill.name,
155
+ eligible: a.eligible,
156
+ selected: false,
157
+ score: a.score,
158
+ reasons: a.reasons,
159
+ });
160
+ if (a.eligible)
161
+ eligible.push({ skill, score: a.score, reasons: a.reasons });
162
+ }
163
+ eligible.sort((x, y) => y.score - x.score || x.skill.name.localeCompare(y.skill.name));
164
+ const selected = [];
165
+ let usedChars = 0;
166
+ for (const e of eligible) {
167
+ const excerpt = boundExcerpt(e.skill.instructions, perSkillChars);
168
+ const verdict = verdicts.find((v) => v.name === e.skill.name);
169
+ if (usedChars + excerpt.length > budgetChars) {
170
+ verdict.reasons.push("dropped: over the per-stage skill char budget");
171
+ continue;
172
+ }
173
+ usedChars += excerpt.length;
174
+ verdict.selected = true;
175
+ selected.push({
176
+ skill: e.skill,
177
+ reasons: e.reasons,
178
+ excerpt,
179
+ chars: excerpt.length,
180
+ });
181
+ }
182
+ return { family, selected, verdicts, budgetChars, usedChars };
183
+ }
184
+ /** A short attribution string for a skill: source/ref + version (issue #138). */
185
+ function attribution(skill) {
186
+ const p = skill.provenance;
187
+ if (p) {
188
+ const ref = p.upstreamRef ? ` @${p.upstreamRef}` : "";
189
+ const sum = (skill.validation.instructionsChecksum ?? p.checksum).slice(0, 12);
190
+ return `source: ${p.source}${ref}, v${skill.version}, sha256 ${sum}`;
191
+ }
192
+ const sum = (skill.validation.instructionsChecksum ?? "").slice(0, 12);
193
+ return `source: repo, v${skill.version}${sum ? `, sha256 ${sum}` : ""}`;
194
+ }
195
+ /** Standing precedence + conflict guidance prepended to every injected block. */
196
+ const INJECTION_NOTE = "Advisory process context. Repo AGENTS instructions, Otto stage contracts, and " +
197
+ ".otto/policy.json outrank these skills. If a skill conflicts with repo policy " +
198
+ "or a more specific instruction, follow repo policy and note the conflict — do " +
199
+ "not silently mix both.";
200
+ /**
201
+ * Render selected skills into a bounded, attributed `<available-skills>` block
202
+ * for prompt injection (issue #138). Each skill is labelled with its source, ref,
203
+ * version, and checksum so a report can trace exactly which instruction shaped the
204
+ * run. The block carries a standing note that repo policy + stage contracts
205
+ * outrank skills and that conflicts must be reported, not silently merged. Returns
206
+ * `""` when nothing is selected, so an opted-in stage with no eligible skill is
207
+ * byte-for-byte unchanged. Pure.
208
+ */
209
+ export function formatSkillInjection(selected) {
210
+ if (selected.length === 0)
211
+ return "";
212
+ const lines = [`<available-skills note="${INJECTION_NOTE}">`];
213
+ for (const s of selected) {
214
+ lines.push(`### ${s.skill.name} (${attribution(s.skill)})`);
215
+ lines.push(s.excerpt);
216
+ lines.push("");
217
+ }
218
+ lines.push("</available-skills>");
219
+ return lines.join("\n");
220
+ }
221
+ /**
222
+ * Convert selections into `SkillUsage[]` evidence (issue #139): name, version,
223
+ * source, ref, the stage that consumed them, and the retrieval reasons — so a run
224
+ * report can reproduce why each skill was selected from the bundle alone. Pure.
225
+ */
226
+ export function toSkillUsages(selected, stage) {
227
+ return selected.map((s) => {
228
+ const u = {
229
+ name: s.skill.name,
230
+ version: s.skill.version,
231
+ stage,
232
+ reasons: s.reasons,
233
+ };
234
+ if (s.skill.provenance) {
235
+ u.source = s.skill.provenance.source;
236
+ if (s.skill.provenance.upstreamRef)
237
+ u.ref = s.skill.provenance.upstreamRef;
238
+ }
239
+ else {
240
+ u.source = "repo";
241
+ }
242
+ return u;
243
+ });
244
+ }
245
+ //# sourceMappingURL=skill-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-routing.js","sourceRoot":"","sources":["../src/skill-routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ import { type Skill, type SkillCompatibility } from "./skills.js";
2
+ /**
3
+ * Skill compatibility & validation gate (issue #113 P17). Before an imported or
4
+ * repo-authored skill is eligible to influence a live run, it must pass a gate:
5
+ * static manifest lint, frontmatter/capability extraction, license/provenance
6
+ * checks (this slice, #132), an instruction-risk scan (#133), and a derived
7
+ * compatibility class persisted back to `skill.json` (#134).
8
+ *
9
+ * This module is **pure** — no fs, no model, deterministic over a {@link Skill}
10
+ * value — mirroring `skills.ts`/`safety-policy.ts`. The bin reads a skill, runs
11
+ * {@link validateSkill}, and prints the report; persistence and selection are
12
+ * separate concerns layered on later. Validation never makes a skill eligible by
13
+ * itself: "validated" means "passed the gate", not "auto-applied".
14
+ */
15
+ /** Severity of one validation finding. `error` blocks; `warn`/`info` advise. */
16
+ export type SkillCheckSeverity = "error" | "warn" | "info";
17
+ /** Which axis of the gate a finding came from. */
18
+ export type SkillCheckKind = "manifest" | "provenance" | "risk";
19
+ /**
20
+ * One validation finding. `rule` is the stable check id (for tests + remediation
21
+ * lookup); `message` says what is wrong; `remediation` says how to fix it — the
22
+ * issue's "failures explain the exact blocker + remediation path".
23
+ */
24
+ export type SkillCheckFinding = {
25
+ kind: SkillCheckKind;
26
+ severity: SkillCheckSeverity;
27
+ rule: string;
28
+ message: string;
29
+ remediation: string;
30
+ };
31
+ /** The aggregate result of validating one skill. */
32
+ export type SkillValidationReport = {
33
+ /** Skill name validated. */
34
+ skill: string;
35
+ /** Capabilities extracted from the manifest + instruction frontmatter. */
36
+ capabilities: string[];
37
+ findings: SkillCheckFinding[];
38
+ /**
39
+ * True when no `error`-severity finding fired AND no applicable behavior drill
40
+ * failed — the skill clears the gate.
41
+ */
42
+ ok: boolean;
43
+ /** Derived compatibility class (issue #134). */
44
+ compatibility: SkillCompatibility;
45
+ /** Stages a `stage-scoped` skill is valid for; empty otherwise. */
46
+ stages: string[];
47
+ /** sha256 of the instruction body validated (for drift detection, #135). */
48
+ checksum: string;
49
+ /** Behavior-drill outcomes (issue #135). */
50
+ drills: DrillResult[];
51
+ };
52
+ /** sha256 of a skill's instruction body, hex. The hash domain matches the */
53
+ /** imported-package checksum in `external-skills.ts` so drift compares cleanly. */
54
+ export declare function skillChecksum(instructions: string): string;
55
+ /**
56
+ * Derive a {@link SkillCompatibility} class from a validation report (issue
57
+ * #134), as a priority ladder: an error-severity finding → `blocked` (cannot be
58
+ * applied anywhere); an interactive hard stop → `interactive-only` (needs a human,
59
+ * so AFK-unsafe even when otherwise valid); capabilities that imply specific
60
+ * stages → `stage-scoped` (with the stage list); otherwise `afk-safe`. Pure.
61
+ */
62
+ export declare function classifyCompatibility(report: SkillValidationReport): {
63
+ compatibility: SkillCompatibility;
64
+ stages: string[];
65
+ };
66
+ /**
67
+ * Static manifest/schema lint. Checks the structural invariants a usable skill
68
+ * package must hold: a filesystem-safe name, a non-empty instruction body, at
69
+ * least one capability (the retrieval key), and a set version. Returns one
70
+ * finding per broken rule; a well-formed manifest yields `[]`.
71
+ */
72
+ export declare function lintManifest(skill: Skill): SkillCheckFinding[];
73
+ /**
74
+ * Extract the skill's effective capabilities: the manifest `capabilities` plus
75
+ * any declared in a leading frontmatter block in the instruction body (skill
76
+ * packs often carry `capabilities:` there). De-duplicated, manifest order first.
77
+ */
78
+ export declare function extractCapabilities(skill: Skill): string[];
79
+ /**
80
+ * License/provenance check. For an imported skill (has `provenance`): the
81
+ * upstream ref must be pinned and a license declared, and when an explicit
82
+ * `--source` is given it must match the recorded source. For a repo-authored
83
+ * skill (no provenance) there is nothing external to check — but asserting a
84
+ * `--source` against it is an error, since it was never imported.
85
+ */
86
+ export declare function checkProvenance(skill: Skill, source?: string): SkillCheckFinding[];
87
+ /**
88
+ * Scan a skill's instruction body for the six P17 risk categories (issue #133):
89
+ * unsafe shell advice, secret handling, network use, interactive hard stops,
90
+ * unsupported tool assumptions, and conflicting hierarchy (a skill trying to
91
+ * overrule repo policy). Each finding names the exact matched phrase and a
92
+ * remediation. Pure, deterministic, conservative — benign guidance yields `[]`.
93
+ */
94
+ export declare function scanInstructionRisks(instructions: string): SkillCheckFinding[];
95
+ /**
96
+ * A small behavior drill (issue #135): a declarative expectation about how a
97
+ * skill of a given kind must classify. Drills are the cheap, deterministic half
98
+ * of "static rules + small behavioral drills" — they assert that an imported
99
+ * methodology lands in a usable, non-contradictory class for its intended stage,
100
+ * without paying for a model run. `appliesTo` (any-match against the skill's
101
+ * capabilities) decides whether a drill is in scope for a skill.
102
+ */
103
+ export type SkillDrill = {
104
+ name: string;
105
+ description: string;
106
+ /** Capabilities that bring this drill into scope (any-match). */
107
+ appliesTo: string[];
108
+ /** Compatibility classes this drill accepts. */
109
+ expectClassIn: SkillCompatibility[];
110
+ /** Finding rules that must NOT be present for the drill to pass. */
111
+ forbidRules: string[];
112
+ };
113
+ /** One drill's outcome against a report. */
114
+ export type DrillResult = {
115
+ drill: string;
116
+ /** Whether the drill was in scope for this skill. */
117
+ applied: boolean;
118
+ /** True when not applied, or applied and satisfied. */
119
+ passed: boolean;
120
+ detail: string;
121
+ };
122
+ /**
123
+ * The standard drills from the roadmap (issue #135): a Superpowers planning/TDD
124
+ * drill (the methodology must be usable, not interactive-only/blocked), a PM
125
+ * roadmap/PRD drill (must scope to the plan stage), and a review drill (a review
126
+ * skill must not try to overrule repo policy).
127
+ */
128
+ export declare const STANDARD_DRILLS: SkillDrill[];
129
+ /**
130
+ * Run behavior drills against a validation report (issue #135). A drill applies
131
+ * when the skill declares one of its `appliesTo` capabilities; an applied drill
132
+ * passes when the report's class is in `expectClassIn` and none of its
133
+ * `forbidRules` fired. A non-applicable drill is reported `applied: false,
134
+ * passed: true` so it never blocks an unrelated skill. Pure, deterministic.
135
+ */
136
+ export declare function runDrills(report: SkillValidationReport, drills?: SkillDrill[]): DrillResult[];
137
+ /**
138
+ * Whether a statically-validated skill must be revalidated before reuse (issue
139
+ * #135). True when the skill carries a recorded `instructionsChecksum` (it was
140
+ * validated) but its current body no longer hashes to it — the skill drifted
141
+ * (e.g. an upstream re-sync changed it) since the gate last ran. A skill that was
142
+ * never statically validated has nothing to revalidate, so this is false. Pure.
143
+ */
144
+ export declare function needsRevalidation(skill: Skill): boolean;
145
+ /**
146
+ * Run the full static gate over one skill and aggregate the result: manifest
147
+ * lint, capability extraction, provenance check (#132), instruction-risk scan
148
+ * (#133), derived compatibility class (#134), and behavior drills (#135). `ok` is
149
+ * false when any `error`-severity finding fired OR an applicable drill failed.
150
+ * Pure — the caller decides whether to persist or print the report.
151
+ */
152
+ export declare function validateSkill(skill: Skill, opts?: {
153
+ source?: string;
154
+ }): SkillValidationReport;
155
+ //# sourceMappingURL=skill-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-validation.d.ts","sourceRoot":"","sources":["../src/skill-validation.ts"],"names":[],"mappings":"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"}