@phuetz/code-buddy 1.2.0 → 1.3.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 (104) hide show
  1. package/README.md +56 -3
  2. package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
  3. package/dist/agent/base-agent.d.ts +4 -0
  4. package/dist/agent/base-agent.js +6 -0
  5. package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
  6. package/dist/agent/facades/infrastructure-facade.js +15 -6
  7. package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
  8. package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
  9. package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
  10. package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
  11. package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
  12. package/dist/agent/self-improvement/authored-tool-store.js +57 -0
  13. package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
  14. package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
  15. package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
  16. package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
  17. package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
  18. package/dist/agent/self-improvement/self-knowledge.js +24 -0
  19. package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
  20. package/dist/agent/self-improvement/skill-benchmark.js +22 -0
  21. package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
  22. package/dist/agent/self-improvement/skill-consolidator.js +137 -0
  23. package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
  24. package/dist/agent/self-improvement/skill-engine.js +87 -0
  25. package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
  26. package/dist/agent/self-improvement/skill-gate.js +62 -0
  27. package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
  28. package/dist/agent/self-improvement/skill-mutator.js +223 -0
  29. package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
  30. package/dist/agent/self-improvement/skill-proposer.js +82 -0
  31. package/dist/agent/self-improvement/skill-types.d.ts +41 -0
  32. package/dist/agent/self-improvement/skill-types.js +13 -0
  33. package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
  34. package/dist/agent/self-improvement/tool-benchmark.js +37 -0
  35. package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
  36. package/dist/agent/self-improvement/tool-engine.js +101 -0
  37. package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
  38. package/dist/agent/self-improvement/tool-gate.js +78 -0
  39. package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
  40. package/dist/agent/self-improvement/tool-proposer.js +34 -0
  41. package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
  42. package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
  43. package/dist/agent/self-improvement/tool-types.d.ts +48 -0
  44. package/dist/agent/self-improvement/tool-types.js +9 -0
  45. package/dist/agent/self-improvement/types.d.ts +3 -1
  46. package/dist/agent/tool-handler.js +3 -0
  47. package/dist/codebuddy/providers/provider-chatgpt-responses.js +6 -1
  48. package/dist/codebuddy/tools.d.ts +7 -0
  49. package/dist/codebuddy/tools.js +40 -0
  50. package/dist/commands/cli/improve-command.js +123 -0
  51. package/dist/commands/enhanced-command-handler.js +1 -1
  52. package/dist/commands/handlers/missing-handlers.d.ts +1 -1
  53. package/dist/commands/handlers/missing-handlers.js +26 -3
  54. package/dist/commands/skills-cli/index.js +123 -0
  55. package/dist/commands/slash/builtin-commands.js +1 -1
  56. package/dist/companion/percepts.js +11 -1
  57. package/dist/context/bootstrap-loader.js +6 -23
  58. package/dist/context/import-directive-parser.d.ts +4 -0
  59. package/dist/context/import-directive-parser.js +51 -6
  60. package/dist/context/instruction-excludes.d.ts +30 -1
  61. package/dist/context/instruction-excludes.js +71 -1
  62. package/dist/context/jit-context.d.ts +8 -10
  63. package/dist/context/jit-context.js +28 -106
  64. package/dist/context/project-context.d.ts +90 -0
  65. package/dist/context/project-context.js +295 -0
  66. package/dist/daemon/autonomous-loop.d.ts +31 -1
  67. package/dist/daemon/autonomous-loop.js +80 -2
  68. package/dist/harness/contract.d.ts +28 -28
  69. package/dist/identity/identity-manager.js +3 -2
  70. package/dist/index.js +17 -1
  71. package/dist/mcp/mcp-resources.js +2 -3
  72. package/dist/sensory/dreaming.d.ts +45 -0
  73. package/dist/sensory/dreaming.js +114 -0
  74. package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
  75. package/dist/sensory/heartbeat-scheduler.js +72 -0
  76. package/dist/sensory/reactions.d.ts +24 -0
  77. package/dist/sensory/reactions.js +31 -0
  78. package/dist/sensory/screen-reaction.d.ts +23 -0
  79. package/dist/sensory/screen-reaction.js +59 -0
  80. package/dist/sensory/sensory-bridge.d.ts +23 -0
  81. package/dist/sensory/sensory-bridge.js +85 -0
  82. package/dist/sensory/sensory-memory.d.ts +20 -0
  83. package/dist/sensory/sensory-memory.js +39 -0
  84. package/dist/sensory/speech-reaction.d.ts +21 -0
  85. package/dist/sensory/speech-reaction.js +83 -0
  86. package/dist/sensory/vision-reaction.d.ts +31 -0
  87. package/dist/sensory/vision-reaction.js +74 -0
  88. package/dist/server/index.js +89 -0
  89. package/dist/services/prompt-builder.d.ts +10 -0
  90. package/dist/services/prompt-builder.js +75 -9
  91. package/dist/skills/parser.js +3 -0
  92. package/dist/skills/skill-importer.d.ts +58 -0
  93. package/dist/skills/skill-importer.js +261 -0
  94. package/dist/skills/skill-sources.d.ts +20 -0
  95. package/dist/skills/skill-sources.js +102 -0
  96. package/dist/skills/types.d.ts +6 -0
  97. package/dist/tools/register-tool-handler.d.ts +25 -0
  98. package/dist/tools/register-tool-handler.js +100 -0
  99. package/dist/tools/registry.d.ts +6 -0
  100. package/dist/tools/registry.js +8 -0
  101. package/dist/utils/init-project.d.ts +7 -0
  102. package/dist/utils/init-project.js +37 -0
  103. package/dist/utils/settings-manager.d.ts +12 -0
  104. package/package.json +1 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Skill gate — validates an authored skill proposal. Ordered, blocking, fail-closed:
3
+ * G1 static scan (authored-artifact-gate, subsystem 'skill': dangerous patterns
4
+ * in any embedded code, secrets, no-src, omissions)
5
+ * G2 SKILL FIREWALL (the headline skill safety check: prompt-injection /
6
+ * exfiltration surface — a skill is INJECTED into the agent's context)
7
+ * G3 COVERAGE — the skill must surface the scenario's expected guidance.
8
+ * Installation happens only on accept+keep (auto-apply); scoring never installs.
9
+ *
10
+ * @module agent/self-improvement/skill-gate
11
+ */
12
+ import { inspectAuthoredCode } from './authored-artifact-gate.js';
13
+ import { scanAuthoredSkillContent } from './skill-mutator.js';
14
+ /** Deterministic coverage check: the skill content surfaces all expected guidance. */
15
+ export function coversScenario(content, scenario) {
16
+ const lower = content.toLowerCase();
17
+ return scenario.expectIncludes.every((s) => lower.includes(s.toLowerCase()));
18
+ }
19
+ export function validateSkillProposal(proposal, scenario, mutator, options) {
20
+ const base = { proposalId: proposal.id, scenarioId: scenario.id };
21
+ const content = proposal.spec.content ?? '';
22
+ // G1 — static scan (no execution).
23
+ const scan = inspectAuthoredCode(content, 'skill');
24
+ if (!scan.ok) {
25
+ return { ...base, accepted: false, rejectionReason: 'static-scan', reasons: scan.reasons };
26
+ }
27
+ // G2 — skill firewall (prompt-injection / exfiltration). The headline defence.
28
+ const fw = scanAuthoredSkillContent(content);
29
+ if (!fw.safe) {
30
+ return {
31
+ ...base,
32
+ accepted: false,
33
+ rejectionReason: 'firewall',
34
+ reasons: [`skill firewall flagged it (${fw.verdict})`, ...fw.reasons],
35
+ };
36
+ }
37
+ // G3 — coverage: the skill must actually surface the expected guidance.
38
+ const lower = content.toLowerCase();
39
+ const missing = scenario.expectIncludes.filter((s) => !lower.includes(s.toLowerCase()));
40
+ if (missing.length > 0) {
41
+ return {
42
+ ...base,
43
+ accepted: false,
44
+ rejectionReason: 'coverage-fail',
45
+ reasons: [`skill does not surface expected guidance: ${JSON.stringify(missing)}`],
46
+ };
47
+ }
48
+ // Accepted. Install (auto-apply) or just report (propose-only).
49
+ let appliedRef;
50
+ if (options.keepOnAccept) {
51
+ appliedRef = mutator.create(proposal.spec).name;
52
+ }
53
+ return {
54
+ ...base,
55
+ accepted: true,
56
+ reasons: options.keepOnAccept
57
+ ? ['accepted and installed (auto-apply): firewall-clean + covers the scenario']
58
+ : ['accepted (propose-only): firewall-clean + covers the scenario, not installed'],
59
+ ...(appliedRef ? { appliedRef } : {}),
60
+ };
61
+ }
62
+ //# sourceMappingURL=skill-gate.js.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Skill mutator — installs / refines / pins / archives authored SKILL.md files
3
+ * under `.codebuddy/skills/<authored-name>/` (ONE level deep so the SkillRegistry's
4
+ * 1-level walk actually loads them) with proven, reversible operations and the
5
+ * firewall scan used to gate authored skill content.
6
+ *
7
+ * Authored skills always carry YAML frontmatter (name + description) so the
8
+ * registry can parse them; the body is the model's markdown. All destructive ops
9
+ * honour a `pinned: true` frontmatter flag and operate ONLY on authored skills.
10
+ *
11
+ * @module agent/self-improvement/skill-mutator
12
+ */
13
+ import type { SkillSpec } from './skill-types.js';
14
+ export declare const AUTHORED_SKILL_PREFIX = "authored-";
15
+ export declare function toAuthoredSkillName(raw: string): string;
16
+ export declare function isAuthoredSkillName(name: string): boolean;
17
+ /** Ensure the content begins with YAML frontmatter carrying name + description. */
18
+ export declare function ensureFrontmatter(name: string, description: string, content: string): string;
19
+ export declare function readPinned(content: string): boolean;
20
+ /** Set or clear the `pinned` flag inside the frontmatter (frontmatter must exist). */
21
+ export declare function setPinned(content: string, value: boolean): string;
22
+ export interface SkillFirewallCheck {
23
+ safe: boolean;
24
+ verdict: string;
25
+ reasons: string[];
26
+ }
27
+ /** Write the skill body to a throwaway file and run the firewall scan (no install). */
28
+ export declare function scanAuthoredSkillContent(content: string): SkillFirewallCheck;
29
+ /** Safety re-gate over authored skill CONTENT (static scan + firewall). */
30
+ export declare function safetyGateSkill(content: string): {
31
+ ok: boolean;
32
+ reasons: string[];
33
+ };
34
+ export interface SkillMutatorPort {
35
+ create(spec: SkillSpec): {
36
+ name: string;
37
+ };
38
+ remove(name: string): boolean;
39
+ has(name: string): boolean;
40
+ }
41
+ export interface MutationResult {
42
+ ok: boolean;
43
+ reasons: string[];
44
+ }
45
+ /** Dual-purpose mutator: the engine's port + the curation operations. */
46
+ export declare class LiveSkillMutator implements SkillMutatorPort {
47
+ private readonly skillsRoot;
48
+ constructor(skillsRoot?: string);
49
+ /** 1 level deep so the registry's findSkillFiles (1-level) loads it. */
50
+ private dirFor;
51
+ private skillFile;
52
+ private reload;
53
+ private readContent;
54
+ has(name: string): boolean;
55
+ isPinned(name: string): boolean;
56
+ create(spec: SkillSpec): {
57
+ name: string;
58
+ };
59
+ /** Full re-author of an existing authored skill, re-gated. Refuses pinned. */
60
+ update(name: string, newContent: string, description?: string): MutationResult;
61
+ /** Find/replace within an authored skill body (exact; fail on multiple unless replaceAll). */
62
+ patch(name: string, oldStr: string, newStr: string, opts?: {
63
+ replaceAll?: boolean;
64
+ }): MutationResult;
65
+ remove(name: string): boolean;
66
+ /** Recoverable removal — move to .archive/. Refuses pinned. */
67
+ archive(name: string): boolean;
68
+ restore(name: string): boolean;
69
+ pin(name: string): boolean;
70
+ unpin(name: string): boolean;
71
+ private setPin;
72
+ /** List installed authored skills (by the authored- prefix). */
73
+ listAuthored(): string[];
74
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Skill mutator — installs / refines / pins / archives authored SKILL.md files
3
+ * under `.codebuddy/skills/<authored-name>/` (ONE level deep so the SkillRegistry's
4
+ * 1-level walk actually loads them) with proven, reversible operations and the
5
+ * firewall scan used to gate authored skill content.
6
+ *
7
+ * Authored skills always carry YAML frontmatter (name + description) so the
8
+ * registry can parse them; the body is the model's markdown. All destructive ops
9
+ * honour a `pinned: true` frontmatter flag and operate ONLY on authored skills.
10
+ *
11
+ * @module agent/self-improvement/skill-mutator
12
+ */
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import os from 'os';
16
+ import { randomUUID } from 'crypto';
17
+ import { getSkillRegistry } from '../../skills/registry.js';
18
+ import { scanSkillFirewall } from '../../security/skill-scanner.js';
19
+ import { inspectAuthoredCode } from './authored-artifact-gate.js';
20
+ export const AUTHORED_SKILL_PREFIX = 'authored-';
21
+ const ARCHIVE_DIR = '.archive';
22
+ export function toAuthoredSkillName(raw) {
23
+ const base = String(raw)
24
+ .trim()
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9-]+/g, '-')
27
+ .replace(/^-+|-+$/g, '');
28
+ return base.startsWith(AUTHORED_SKILL_PREFIX) ? base : `${AUTHORED_SKILL_PREFIX}${base || 'skill'}`;
29
+ }
30
+ export function isAuthoredSkillName(name) {
31
+ return name.startsWith(AUTHORED_SKILL_PREFIX);
32
+ }
33
+ // ── frontmatter helpers (our controlled format; avoids the registry parser's
34
+ // tier + validation requirements for a simple flag toggle) ────────────────
35
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
36
+ /** Ensure the content begins with YAML frontmatter carrying name + description. */
37
+ export function ensureFrontmatter(name, description, content) {
38
+ if (FRONTMATTER_RE.test(content))
39
+ return content;
40
+ const desc = description.replace(/"/g, "'").replace(/\r?\n/g, ' ').trim();
41
+ return `---\nname: ${name}\ndescription: "${desc}"\n---\n\n${content.trim()}\n`;
42
+ }
43
+ export function readPinned(content) {
44
+ const m = content.match(FRONTMATTER_RE);
45
+ if (!m)
46
+ return false;
47
+ return /^\s*pinned\s*:\s*true\s*$/im.test(m[1]);
48
+ }
49
+ /** Set or clear the `pinned` flag inside the frontmatter (frontmatter must exist). */
50
+ export function setPinned(content, value) {
51
+ const m = content.match(FRONTMATTER_RE);
52
+ if (!m)
53
+ return content; // no frontmatter → nothing to pin (create() always adds it)
54
+ let body = m[1];
55
+ if (/^\s*pinned\s*:.*$/im.test(body)) {
56
+ body = body.replace(/^\s*pinned\s*:.*$/im, `pinned: ${value}`);
57
+ }
58
+ else {
59
+ body = `${body}\npinned: ${value}`;
60
+ }
61
+ return content.replace(FRONTMATTER_RE, `---\n${body}\n---\n`);
62
+ }
63
+ /** Write the skill body to a throwaway file and run the firewall scan (no install). */
64
+ export function scanAuthoredSkillContent(content) {
65
+ const dir = path.join(os.tmpdir(), `cb-skillscan-${randomUUID()}`);
66
+ const file = path.join(dir, 'SKILL.md');
67
+ fs.mkdirSync(dir, { recursive: true });
68
+ fs.writeFileSync(file, content, 'utf-8');
69
+ try {
70
+ const report = scanSkillFirewall(file);
71
+ return {
72
+ safe: !report.quarantineRequired,
73
+ verdict: String(report.verdict),
74
+ reasons: report.quarantineRequired ? [report.summary] : [],
75
+ };
76
+ }
77
+ catch (err) {
78
+ return { safe: false, verdict: 'scan-error', reasons: [err instanceof Error ? err.message : String(err)] };
79
+ }
80
+ finally {
81
+ fs.rmSync(dir, { recursive: true, force: true });
82
+ }
83
+ }
84
+ /** Safety re-gate over authored skill CONTENT (static scan + firewall). */
85
+ export function safetyGateSkill(content) {
86
+ const scan = inspectAuthoredCode(content, 'skill');
87
+ if (!scan.ok)
88
+ return { ok: false, reasons: scan.reasons };
89
+ const fw = scanAuthoredSkillContent(content);
90
+ if (!fw.safe)
91
+ return { ok: false, reasons: [`firewall: ${fw.verdict}`, ...fw.reasons] };
92
+ return { ok: true, reasons: [] };
93
+ }
94
+ /** Dual-purpose mutator: the engine's port + the curation operations. */
95
+ export class LiveSkillMutator {
96
+ skillsRoot;
97
+ constructor(skillsRoot) {
98
+ this.skillsRoot = skillsRoot ?? path.join(process.cwd(), '.codebuddy', 'skills');
99
+ }
100
+ /** 1 level deep so the registry's findSkillFiles (1-level) loads it. */
101
+ dirFor(name) {
102
+ return path.join(this.skillsRoot, name);
103
+ }
104
+ skillFile(name) {
105
+ return path.join(this.dirFor(name), 'SKILL.md');
106
+ }
107
+ reload() {
108
+ void getSkillRegistry().reloadAll().catch(() => { });
109
+ }
110
+ readContent(name) {
111
+ const f = this.skillFile(name);
112
+ return fs.existsSync(f) ? fs.readFileSync(f, 'utf-8') : null;
113
+ }
114
+ has(name) {
115
+ return fs.existsSync(this.skillFile(name));
116
+ }
117
+ isPinned(name) {
118
+ const c = this.readContent(name);
119
+ return c ? readPinned(c) : false;
120
+ }
121
+ create(spec) {
122
+ const dir = this.dirFor(spec.name);
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ const content = ensureFrontmatter(spec.name, spec.description, spec.content);
125
+ fs.writeFileSync(this.skillFile(spec.name), content, 'utf-8');
126
+ this.reload();
127
+ return { name: spec.name };
128
+ }
129
+ /** Full re-author of an existing authored skill, re-gated. Refuses pinned. */
130
+ update(name, newContent, description = '') {
131
+ if (!isAuthoredSkillName(name))
132
+ return { ok: false, reasons: ['not an authored skill'] };
133
+ if (!this.has(name))
134
+ return { ok: false, reasons: ['skill does not exist'] };
135
+ if (this.isPinned(name))
136
+ return { ok: false, reasons: ['skill is pinned'] };
137
+ const withFm = ensureFrontmatter(name, description, newContent);
138
+ const gate = safetyGateSkill(withFm);
139
+ if (!gate.ok)
140
+ return gate;
141
+ fs.writeFileSync(this.skillFile(name), withFm, 'utf-8');
142
+ this.reload();
143
+ return { ok: true, reasons: [] };
144
+ }
145
+ /** Find/replace within an authored skill body (exact; fail on multiple unless replaceAll). */
146
+ patch(name, oldStr, newStr, opts = {}) {
147
+ const content = this.readContent(name);
148
+ if (content === null)
149
+ return { ok: false, reasons: ['skill does not exist'] };
150
+ if (this.isPinned(name))
151
+ return { ok: false, reasons: ['skill is pinned'] };
152
+ const count = content.split(oldStr).length - 1;
153
+ if (count === 0)
154
+ return { ok: false, reasons: ['old_string not found'] };
155
+ if (count > 1 && !opts.replaceAll) {
156
+ return { ok: false, reasons: [`old_string matches ${count} times — pass replaceAll or add context`] };
157
+ }
158
+ const patched = opts.replaceAll ? content.split(oldStr).join(newStr) : content.replace(oldStr, newStr);
159
+ return this.update(name, patched);
160
+ }
161
+ remove(name) {
162
+ if (this.isPinned(name))
163
+ return false;
164
+ const dir = this.dirFor(name);
165
+ const existed = fs.existsSync(dir);
166
+ if (existed)
167
+ fs.rmSync(dir, { recursive: true, force: true });
168
+ this.reload();
169
+ return existed;
170
+ }
171
+ /** Recoverable removal — move to .archive/. Refuses pinned. */
172
+ archive(name) {
173
+ if (this.isPinned(name))
174
+ return false;
175
+ const dir = this.dirFor(name);
176
+ if (!fs.existsSync(dir))
177
+ return false;
178
+ const archiveRoot = path.join(this.skillsRoot, ARCHIVE_DIR);
179
+ fs.mkdirSync(archiveRoot, { recursive: true });
180
+ let dest = path.join(archiveRoot, name);
181
+ if (fs.existsSync(dest))
182
+ dest = `${dest}-${randomUUID().slice(0, 8)}`;
183
+ fs.renameSync(dir, dest);
184
+ this.reload();
185
+ return true;
186
+ }
187
+ restore(name) {
188
+ const src = path.join(this.skillsRoot, ARCHIVE_DIR, name);
189
+ if (!fs.existsSync(src))
190
+ return false;
191
+ const dir = this.dirFor(name);
192
+ if (fs.existsSync(dir))
193
+ return false; // don't clobber a live skill
194
+ fs.renameSync(src, dir);
195
+ this.reload();
196
+ return true;
197
+ }
198
+ pin(name) {
199
+ return this.setPin(name, true);
200
+ }
201
+ unpin(name) {
202
+ return this.setPin(name, false);
203
+ }
204
+ setPin(name, value) {
205
+ const content = this.readContent(name);
206
+ if (content === null)
207
+ return false;
208
+ const withFm = ensureFrontmatter(name, '', content);
209
+ fs.writeFileSync(this.skillFile(name), setPinned(withFm, value), 'utf-8');
210
+ this.reload();
211
+ return true;
212
+ }
213
+ /** List installed authored skills (by the authored- prefix). */
214
+ listAuthored() {
215
+ if (!fs.existsSync(this.skillsRoot))
216
+ return [];
217
+ return fs
218
+ .readdirSync(this.skillsRoot, { withFileTypes: true })
219
+ .filter((e) => e.isDirectory() && isAuthoredSkillName(e.name) && fs.existsSync(path.join(this.skillsRoot, e.name, 'SKILL.md')))
220
+ .map((e) => e.name);
221
+ }
222
+ }
223
+ //# sourceMappingURL=skill-mutator.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Skill proposers — author a SKILL.md for a coverage scenario. A skill is pure
3
+ * guidance, so there is no held-out set to hide (unlike tools); the proposer may
4
+ * see the scenario in full. Safety is enforced downstream by the firewall gate.
5
+ *
6
+ * @module agent/self-improvement/skill-proposer
7
+ */
8
+ import type { SkillBenchmarkScenario, SkillProposal, SkillSpec } from './skill-types.js';
9
+ export interface SkillProposer {
10
+ propose(scenario: SkillBenchmarkScenario): Promise<SkillProposal | null>;
11
+ }
12
+ /** Deterministic proposer backed by a fixture map (scenarioId → spec). */
13
+ export declare class StaticSkillProposer implements SkillProposer {
14
+ private readonly specs;
15
+ constructor(specs: Map<string, SkillSpec>);
16
+ propose(scenario: SkillBenchmarkScenario): Promise<SkillProposal | null>;
17
+ }
18
+ interface MinimalClient {
19
+ chat(messages: Array<{
20
+ role: string;
21
+ content: string;
22
+ }>, tools?: unknown[]): Promise<{
23
+ choices?: Array<{
24
+ message?: {
25
+ content?: string | null;
26
+ };
27
+ }>;
28
+ }>;
29
+ }
30
+ export declare function buildSkillDraftPrompt(scenario: SkillBenchmarkScenario): string;
31
+ export interface LlmSkillProposerOptions {
32
+ client?: MinimalClient | null;
33
+ }
34
+ export declare class LlmSkillProposer implements SkillProposer {
35
+ private clientPromise;
36
+ constructor(options?: LlmSkillProposerOptions);
37
+ private getClient;
38
+ propose(scenario: SkillBenchmarkScenario): Promise<SkillProposal | null>;
39
+ }
40
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Skill proposers — author a SKILL.md for a coverage scenario. A skill is pure
3
+ * guidance, so there is no held-out set to hide (unlike tools); the proposer may
4
+ * see the scenario in full. Safety is enforced downstream by the firewall gate.
5
+ *
6
+ * @module agent/self-improvement/skill-proposer
7
+ */
8
+ import { toAuthoredSkillName } from './skill-mutator.js';
9
+ /** Deterministic proposer backed by a fixture map (scenarioId → spec). */
10
+ export class StaticSkillProposer {
11
+ specs;
12
+ constructor(specs) {
13
+ this.specs = specs;
14
+ }
15
+ async propose(scenario) {
16
+ const spec = this.specs.get(scenario.id);
17
+ if (!spec)
18
+ return null;
19
+ return { id: `skill-proposal:${scenario.id}`, targetScenarioId: scenario.id, spec };
20
+ }
21
+ }
22
+ export function buildSkillDraftPrompt(scenario) {
23
+ return [
24
+ `Write a SKILL.md that gives you reusable guidance for this situation:`,
25
+ ` ${scenario.query}`,
26
+ ``,
27
+ `It MUST clearly cover these points (use these exact terms): ${JSON.stringify(scenario.expectIncludes)}.`,
28
+ ``,
29
+ `Format: a short markdown skill — a title line, a one-line "when to use", then`,
30
+ `concrete steps/notes. Keep it focused. Do NOT include any instruction to ignore`,
31
+ `safety rules, reveal or exfiltrate secrets/credentials, or run destructive`,
32
+ `commands — such content is rejected by a safety firewall.`,
33
+ ``,
34
+ `Return ONLY the SKILL.md markdown (no surrounding prose).`,
35
+ ].join('\n');
36
+ }
37
+ export class LlmSkillProposer {
38
+ clientPromise;
39
+ constructor(options = {}) {
40
+ this.clientPromise = options.client !== undefined ? Promise.resolve(options.client) : null;
41
+ }
42
+ getClient() {
43
+ if (!this.clientPromise) {
44
+ this.clientPromise = (async () => {
45
+ try {
46
+ const { detectProviderFromEnv } = await import('../../utils/provider-detector.js');
47
+ const { CodeBuddyClient } = await import('../../codebuddy/client.js');
48
+ const detected = detectProviderFromEnv();
49
+ if (!detected)
50
+ return null;
51
+ return new CodeBuddyClient(detected.apiKey, detected.defaultModel, detected.baseURL);
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ })();
57
+ }
58
+ return this.clientPromise;
59
+ }
60
+ async propose(scenario) {
61
+ const client = await this.getClient();
62
+ if (!client)
63
+ return null;
64
+ try {
65
+ const prompt = buildSkillDraftPrompt(scenario);
66
+ const response = await client.chat([{ role: 'user', content: prompt }], []);
67
+ const content = response?.choices?.[0]?.message?.content?.trim();
68
+ if (!content)
69
+ return null;
70
+ const spec = {
71
+ name: toAuthoredSkillName(scenario.id),
72
+ description: scenario.description,
73
+ content,
74
+ };
75
+ return { id: `llm-skill:${scenario.id}`, targetScenarioId: scenario.id, spec };
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=skill-proposer.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Types for the SKILL self-improvement path. A skill is procedural guidance
3
+ * (a SKILL.md injected into context), not a deterministic function — so it is
4
+ * gated by SAFETY (the skill firewall: prompt-injection / exfiltration surface)
5
+ * plus COVERAGE (does it actually surface the expected guidance?), rather than
6
+ * the behavioural held-out gate used for executable tools. Honest by design:
7
+ * there is nothing to "run", so there is no behavioural held-out for pure
8
+ * guidance — the held-out gate stays the tool path's concern.
9
+ *
10
+ * @module agent/self-improvement/skill-types
11
+ */
12
+ export interface SkillSpec {
13
+ /** Authored skill name (slugged, namespaced authored-*). */
14
+ name: string;
15
+ description: string;
16
+ /** The full SKILL.md body. */
17
+ content: string;
18
+ }
19
+ /** Coverage scenario: the authored skill should surface `expectIncludes` for `query`. */
20
+ export interface SkillBenchmarkScenario {
21
+ id: string;
22
+ query: string;
23
+ expectIncludes: string[];
24
+ description: string;
25
+ }
26
+ export interface SkillProposal {
27
+ id: string;
28
+ targetScenarioId: string;
29
+ experienceId?: string;
30
+ spec: SkillSpec;
31
+ }
32
+ export type SkillGateRejection = 'static-scan' | 'firewall' | 'coverage-fail';
33
+ export interface SkillGateOutcome {
34
+ accepted: boolean;
35
+ proposalId: string;
36
+ scenarioId: string;
37
+ rejectionReason?: SkillGateRejection;
38
+ reasons: string[];
39
+ /** Name of the skill left installed (only when accepted AND kept). */
40
+ appliedRef?: string;
41
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Types for the SKILL self-improvement path. A skill is procedural guidance
3
+ * (a SKILL.md injected into context), not a deterministic function — so it is
4
+ * gated by SAFETY (the skill firewall: prompt-injection / exfiltration surface)
5
+ * plus COVERAGE (does it actually surface the expected guidance?), rather than
6
+ * the behavioural held-out gate used for executable tools. Honest by design:
7
+ * there is nothing to "run", so there is no behavioural held-out for pure
8
+ * guidance — the held-out gate stays the tool path's concern.
9
+ *
10
+ * @module agent/self-improvement/skill-types
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=skill-types.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Seed behavioural benchmark for self-authored TOOLS. Each scenario describes a
3
+ * small, deterministic capability with VISIBLE cases (shown to the proposer) and
4
+ * HELD-OUT cases (fresh inputs, never shown) — so a tool that hardcodes the
5
+ * visible answers is caught. Curated separately from any proposer.
6
+ *
7
+ * @module agent/self-improvement/tool-benchmark
8
+ */
9
+ import type { ToolBenchmarkScenario } from './tool-types.js';
10
+ export declare const SEED_TOOL_SCENARIOS: ToolBenchmarkScenario[];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Seed behavioural benchmark for self-authored TOOLS. Each scenario describes a
3
+ * small, deterministic capability with VISIBLE cases (shown to the proposer) and
4
+ * HELD-OUT cases (fresh inputs, never shown) — so a tool that hardcodes the
5
+ * visible answers is caught. Curated separately from any proposer.
6
+ *
7
+ * @module agent/self-improvement/tool-benchmark
8
+ */
9
+ export const SEED_TOOL_SCENARIOS = [
10
+ {
11
+ id: 'slugify',
12
+ capability: 'Slugify the string field `text`: lowercase it, replace runs of spaces with single hyphens, and print the slug.',
13
+ description: 'authored__slugify converts text to a url slug',
14
+ visibleCases: [
15
+ { input: { text: 'Hello World' }, expectIncludes: ['hello-world'] },
16
+ { input: { text: 'Foo Bar Baz' }, expectIncludes: ['foo-bar-baz'] },
17
+ ],
18
+ heldOutCases: [
19
+ { input: { text: 'The Quick Brown' }, expectIncludes: ['the-quick-brown'] },
20
+ { input: { text: 'A B C' }, expectIncludes: ['a-b-c'] },
21
+ ],
22
+ },
23
+ {
24
+ id: 'word-count',
25
+ capability: 'Count the whitespace-separated words in the string field `text` and print the integer count.',
26
+ description: 'authored__word_count counts words',
27
+ visibleCases: [
28
+ { input: { text: 'one two three' }, expectIncludes: ['3'] },
29
+ { input: { text: 'hello' }, expectIncludes: ['1'] },
30
+ ],
31
+ heldOutCases: [
32
+ { input: { text: 'a b c d e' }, expectIncludes: ['5'] },
33
+ { input: { text: 'foo bar' }, expectIncludes: ['2'] },
34
+ ],
35
+ },
36
+ ];
37
+ //# sourceMappingURL=tool-benchmark.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * ToolImprovementEngine — the tool sibling of SelfImprovementEngine. One cycle:
3
+ * pick an uncovered tool scenario → propose (redacted view) → tool gate
4
+ * (static scan → visible → held-out) → keep+archive (auto-apply) or report.
5
+ *
6
+ * Autonomy is the same fail-safe toggle: 'propose-only' (default) validates and
7
+ * reports; 'auto-apply' (CODEBUDDY_SELF_IMPROVE=true) keeps only tools that pass
8
+ * the behavioural held-out gate, and archives them (reversible — un-registerable).
9
+ *
10
+ * @module agent/self-improvement/tool-engine
11
+ */
12
+ import { EvolutionaryArchive } from './evolutionary-archive.js';
13
+ import { type Autonomy } from './engine.js';
14
+ import { type ToolMutatorPort } from './tool-skill-mutator.js';
15
+ import { type ToolProposer } from './tool-proposer.js';
16
+ import type { ToolBenchmarkScenario, ToolGateOutcome } from './tool-types.js';
17
+ export interface ToolCycleResult {
18
+ kind: 'tool_improvement_cycle';
19
+ startedAt: string;
20
+ autonomy: Autonomy;
21
+ selectedScenarioId: string | null;
22
+ proposalId: string | null;
23
+ gate: ToolGateOutcome | null;
24
+ applied: boolean;
25
+ notes: string[];
26
+ }
27
+ export interface ToolImprovementEngineOptions {
28
+ scenarios: ToolBenchmarkScenario[];
29
+ proposer: ToolProposer;
30
+ mutator?: ToolMutatorPort;
31
+ archive?: EvolutionaryArchive;
32
+ autonomy?: Autonomy;
33
+ now?: () => Date;
34
+ }
35
+ export declare class ToolImprovementEngine {
36
+ private readonly scenarios;
37
+ private readonly proposer;
38
+ private readonly mutator;
39
+ private readonly archive;
40
+ private readonly autonomy;
41
+ private readonly now;
42
+ /** Scenario ids already satisfied this run (coverage is per-scenario, not per tool name). */
43
+ private readonly covered;
44
+ constructor(options: ToolImprovementEngineOptions);
45
+ /** Run exactly one tool-improvement cycle. */
46
+ runCycle(): Promise<ToolCycleResult>;
47
+ /** Run cycles until nothing new is applied (or maxCycles). */
48
+ runLoop(maxCycles?: number): Promise<ToolCycleResult[]>;
49
+ status(): {
50
+ autonomy: Autonomy;
51
+ scenarios: number;
52
+ archive: ReturnType<EvolutionaryArchive['summary']>;
53
+ };
54
+ }