@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,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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"}
|