@soleri/core 2.7.0 → 2.9.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 (137) hide show
  1. package/dist/extensions/index.d.ts +3 -0
  2. package/dist/extensions/index.d.ts.map +1 -0
  3. package/dist/extensions/index.js +2 -0
  4. package/dist/extensions/index.js.map +1 -0
  5. package/dist/extensions/middleware.d.ts +13 -0
  6. package/dist/extensions/middleware.d.ts.map +1 -0
  7. package/dist/extensions/middleware.js +47 -0
  8. package/dist/extensions/middleware.js.map +1 -0
  9. package/dist/extensions/types.d.ts +64 -0
  10. package/dist/extensions/types.d.ts.map +1 -0
  11. package/dist/extensions/types.js +2 -0
  12. package/dist/extensions/types.js.map +1 -0
  13. package/dist/index.d.ts +8 -16
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -16
  16. package/dist/index.js.map +1 -1
  17. package/dist/planning/gap-analysis.d.ts +2 -1
  18. package/dist/planning/gap-analysis.d.ts.map +1 -1
  19. package/dist/planning/gap-analysis.js +70 -1
  20. package/dist/planning/gap-analysis.js.map +1 -1
  21. package/dist/planning/gap-types.d.ts +8 -3
  22. package/dist/planning/gap-types.d.ts.map +1 -1
  23. package/dist/planning/gap-types.js +9 -1
  24. package/dist/planning/gap-types.js.map +1 -1
  25. package/dist/planning/planner.d.ts.map +1 -1
  26. package/dist/planning/planner.js +17 -5
  27. package/dist/planning/planner.js.map +1 -1
  28. package/dist/runtime/core-ops.d.ts +1 -1
  29. package/dist/runtime/core-ops.js +1 -1
  30. package/dist/runtime/facades/admin-facade.d.ts +8 -0
  31. package/dist/runtime/facades/admin-facade.d.ts.map +1 -0
  32. package/dist/runtime/facades/admin-facade.js +90 -0
  33. package/dist/runtime/facades/admin-facade.js.map +1 -0
  34. package/dist/runtime/facades/brain-facade.d.ts +8 -0
  35. package/dist/runtime/facades/brain-facade.d.ts.map +1 -0
  36. package/dist/runtime/facades/brain-facade.js +294 -0
  37. package/dist/runtime/facades/brain-facade.js.map +1 -0
  38. package/dist/runtime/facades/cognee-facade.d.ts +8 -0
  39. package/dist/runtime/facades/cognee-facade.d.ts.map +1 -0
  40. package/dist/runtime/facades/cognee-facade.js +154 -0
  41. package/dist/runtime/facades/cognee-facade.js.map +1 -0
  42. package/dist/runtime/facades/control-facade.d.ts +8 -0
  43. package/dist/runtime/facades/control-facade.d.ts.map +1 -0
  44. package/dist/runtime/facades/control-facade.js +244 -0
  45. package/dist/runtime/facades/control-facade.js.map +1 -0
  46. package/dist/runtime/facades/curator-facade.d.ts +8 -0
  47. package/dist/runtime/facades/curator-facade.d.ts.map +1 -0
  48. package/dist/runtime/facades/curator-facade.js +117 -0
  49. package/dist/runtime/facades/curator-facade.js.map +1 -0
  50. package/dist/runtime/facades/index.d.ts +10 -0
  51. package/dist/runtime/facades/index.d.ts.map +1 -0
  52. package/dist/runtime/facades/index.js +71 -0
  53. package/dist/runtime/facades/index.js.map +1 -0
  54. package/dist/runtime/facades/loop-facade.d.ts +8 -0
  55. package/dist/runtime/facades/loop-facade.d.ts.map +1 -0
  56. package/dist/runtime/facades/loop-facade.js +9 -0
  57. package/dist/runtime/facades/loop-facade.js.map +1 -0
  58. package/dist/runtime/facades/memory-facade.d.ts +8 -0
  59. package/dist/runtime/facades/memory-facade.d.ts.map +1 -0
  60. package/dist/runtime/facades/memory-facade.js +108 -0
  61. package/dist/runtime/facades/memory-facade.js.map +1 -0
  62. package/dist/runtime/facades/orchestrate-facade.d.ts +8 -0
  63. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -0
  64. package/dist/runtime/facades/orchestrate-facade.js +58 -0
  65. package/dist/runtime/facades/orchestrate-facade.js.map +1 -0
  66. package/dist/runtime/facades/plan-facade.d.ts +8 -0
  67. package/dist/runtime/facades/plan-facade.d.ts.map +1 -0
  68. package/dist/runtime/facades/plan-facade.js +110 -0
  69. package/dist/runtime/facades/plan-facade.js.map +1 -0
  70. package/dist/runtime/facades/vault-facade.d.ts +8 -0
  71. package/dist/runtime/facades/vault-facade.d.ts.map +1 -0
  72. package/dist/runtime/facades/vault-facade.js +194 -0
  73. package/dist/runtime/facades/vault-facade.js.map +1 -0
  74. package/dist/runtime/grading-ops.d.ts +1 -1
  75. package/dist/runtime/grading-ops.js +2 -2
  76. package/dist/runtime/grading-ops.js.map +1 -1
  77. package/dist/runtime/vault-extra-ops.d.ts +2 -2
  78. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  79. package/dist/runtime/vault-extra-ops.js +37 -2
  80. package/dist/runtime/vault-extra-ops.js.map +1 -1
  81. package/dist/streams/index.d.ts +4 -0
  82. package/dist/streams/index.d.ts.map +1 -0
  83. package/dist/streams/index.js +3 -0
  84. package/dist/streams/index.js.map +1 -0
  85. package/dist/streams/normalize.d.ts +14 -0
  86. package/dist/streams/normalize.d.ts.map +1 -0
  87. package/dist/streams/normalize.js +43 -0
  88. package/dist/streams/normalize.js.map +1 -0
  89. package/dist/streams/replayable-stream.d.ts +19 -0
  90. package/dist/streams/replayable-stream.d.ts.map +1 -0
  91. package/dist/streams/replayable-stream.js +90 -0
  92. package/dist/streams/replayable-stream.js.map +1 -0
  93. package/dist/vault/content-hash.d.ts +16 -0
  94. package/dist/vault/content-hash.d.ts.map +1 -0
  95. package/dist/vault/content-hash.js +21 -0
  96. package/dist/vault/content-hash.js.map +1 -0
  97. package/dist/vault/vault.d.ts +9 -0
  98. package/dist/vault/vault.d.ts.map +1 -1
  99. package/dist/vault/vault.js +49 -3
  100. package/dist/vault/vault.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/__tests__/content-hash.test.ts +60 -0
  103. package/src/__tests__/core-ops.test.ts +10 -7
  104. package/src/__tests__/extensions.test.ts +233 -0
  105. package/src/__tests__/grading-ops.test.ts +2 -2
  106. package/src/__tests__/memory-cross-project-ops.test.ts +2 -2
  107. package/src/__tests__/normalize.test.ts +75 -0
  108. package/src/__tests__/playbook.test.ts +4 -4
  109. package/src/__tests__/replayable-stream.test.ts +66 -0
  110. package/src/__tests__/vault-extra-ops.test.ts +1 -1
  111. package/src/__tests__/vault.test.ts +72 -0
  112. package/src/extensions/index.ts +2 -0
  113. package/src/extensions/middleware.ts +53 -0
  114. package/src/extensions/types.ts +64 -0
  115. package/src/index.ts +14 -17
  116. package/src/planning/gap-analysis.ts +95 -1
  117. package/src/planning/gap-types.ts +12 -2
  118. package/src/planning/planner.ts +17 -5
  119. package/src/runtime/facades/admin-facade.ts +101 -0
  120. package/src/runtime/facades/brain-facade.ts +331 -0
  121. package/src/runtime/facades/cognee-facade.ts +162 -0
  122. package/src/runtime/facades/control-facade.ts +279 -0
  123. package/src/runtime/facades/curator-facade.ts +132 -0
  124. package/src/runtime/facades/index.ts +74 -0
  125. package/src/runtime/facades/loop-facade.ts +12 -0
  126. package/src/runtime/facades/memory-facade.ts +114 -0
  127. package/src/runtime/facades/orchestrate-facade.ts +68 -0
  128. package/src/runtime/facades/plan-facade.ts +119 -0
  129. package/src/runtime/facades/vault-facade.ts +223 -0
  130. package/src/runtime/grading-ops.ts +2 -2
  131. package/src/runtime/vault-extra-ops.ts +38 -2
  132. package/src/streams/index.ts +3 -0
  133. package/src/streams/normalize.ts +56 -0
  134. package/src/streams/replayable-stream.ts +92 -0
  135. package/src/vault/content-hash.ts +31 -0
  136. package/src/vault/vault.ts +73 -3
  137. package/src/runtime/core-ops.ts +0 -1443
@@ -0,0 +1,53 @@
1
+ import type { FacadeConfig } from '../facades/types.js';
2
+ import type { OpMiddleware } from './types.js';
3
+
4
+ /**
5
+ * Wrap all ops in the given facades with middleware.
6
+ *
7
+ * Middleware chain follows the onion model:
8
+ * - before hooks: first middleware → last middleware → handler
9
+ * - after hooks: last middleware → first middleware (reverse)
10
+ *
11
+ * This mutates the facade ops in-place (replaces handlers).
12
+ */
13
+ export function wrapWithMiddleware(facades: FacadeConfig[], middleware: OpMiddleware[]): void {
14
+ if (middleware.length === 0) return;
15
+
16
+ for (const facade of facades) {
17
+ for (const op of facade.ops) {
18
+ const originalHandler = op.handler;
19
+
20
+ op.handler = async (params: Record<string, unknown>) => {
21
+ // Run before hooks (first → last)
22
+ let currentParams = params;
23
+ for (const mw of middleware) {
24
+ if (mw.before) {
25
+ currentParams = await mw.before({
26
+ facade: facade.name,
27
+ op: op.name,
28
+ params: currentParams,
29
+ });
30
+ }
31
+ }
32
+
33
+ // Run original handler
34
+ let result = await originalHandler(currentParams);
35
+
36
+ // Run after hooks (last → first)
37
+ for (let i = middleware.length - 1; i >= 0; i--) {
38
+ const mw = middleware[i];
39
+ if (mw.after) {
40
+ result = await mw.after({
41
+ facade: facade.name,
42
+ op: op.name,
43
+ params: currentParams,
44
+ result,
45
+ });
46
+ }
47
+ }
48
+
49
+ return result;
50
+ };
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,64 @@
1
+ import type { OpDefinition, FacadeConfig } from '../facades/types.js';
2
+ import type { AgentRuntime } from '../runtime/types.js';
3
+
4
+ /**
5
+ * Middleware that wraps op execution with before/after hooks.
6
+ *
7
+ * - `before` runs before the op handler. Return modified params or throw to reject.
8
+ * - `after` runs after the op handler. Return modified result or throw.
9
+ *
10
+ * Multiple middleware are chained: before hooks run first→last,
11
+ * after hooks run last→first (onion model).
12
+ */
13
+ export interface OpMiddleware {
14
+ /** Middleware name (for logging/debugging) */
15
+ name: string;
16
+ /** Runs before op handler. Return modified params or throw to reject. */
17
+ before?: (ctx: MiddlewareContext) => Promise<Record<string, unknown>>;
18
+ /** Runs after op handler. Return modified result or throw. */
19
+ after?: (ctx: MiddlewareContext & { result: unknown }) => Promise<unknown>;
20
+ }
21
+
22
+ export interface MiddlewareContext {
23
+ facade: string;
24
+ op: string;
25
+ params: Record<string, unknown>;
26
+ }
27
+
28
+ /**
29
+ * User-defined extensions for a Soleri agent.
30
+ *
31
+ * Extensions live in `src/extensions/` and are auto-discovered by the entry
32
+ * point at startup. Core ops from `@soleri/core` are never modified — extensions
33
+ * are additive (new ops, new facades) or decorative (middleware).
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // src/extensions/index.ts
38
+ * import type { AgentExtensions } from '@soleri/core';
39
+ * import type { AgentRuntime } from '@soleri/core';
40
+ *
41
+ * export default function loadExtensions(runtime: AgentRuntime): AgentExtensions {
42
+ * return {
43
+ * ops: [myCustomOp(runtime)],
44
+ * facades: [myCustomFacade(runtime)],
45
+ * middleware: [auditLogger],
46
+ * };
47
+ * }
48
+ * ```
49
+ */
50
+ export interface AgentExtensions {
51
+ /** Extra ops merged into the core facade */
52
+ ops?: OpDefinition[];
53
+ /** New facades registered as separate MCP tools */
54
+ facades?: FacadeConfig[];
55
+ /** Middleware applied to all ops across all facades */
56
+ middleware?: OpMiddleware[];
57
+ /** Lifecycle hooks */
58
+ hooks?: {
59
+ /** Called after runtime init, before MCP server starts */
60
+ onStartup?: (runtime: AgentRuntime) => Promise<void>;
61
+ /** Called on SIGTERM/SIGINT before process exits */
62
+ onShutdown?: (runtime: AgentRuntime) => Promise<void>;
63
+ };
64
+ }
package/src/index.ts CHANGED
@@ -29,8 +29,6 @@ export type {
29
29
  MergedPlaybook,
30
30
  PlaybookMatchResult,
31
31
  } from './playbooks/index.js';
32
- export { createPlaybookOps } from './runtime/playbook-ops.js';
33
-
34
32
  // ─── Text Utilities ─────────────────────────────────────────────────
35
33
  export {
36
34
  tokenize,
@@ -239,6 +237,10 @@ export {
239
237
  export { KeyPool, loadKeyPoolConfig } from './llm/key-pool.js';
240
238
  export type { KeyPoolFiles } from './llm/key-pool.js';
241
239
 
240
+ // ─── Extensions ──────────────────────────────────────────────────────
241
+ export type { AgentExtensions, OpMiddleware, MiddlewareContext } from './extensions/index.js';
242
+ export { wrapWithMiddleware } from './extensions/index.js';
243
+
242
244
  // ─── Facades ─────────────────────────────────────────────────────────
243
245
  export { registerFacade, registerAllFacades } from './facades/facade-factory.js';
244
246
  export { facadeInputSchema } from './facades/types.js';
@@ -285,22 +287,8 @@ export type { LogLevel, LogEntry, LogContext, LoggerConfig } from './logging/typ
285
287
 
286
288
  // ─── Runtime Factory ────────────────────────────────────────────────
287
289
  export { createAgentRuntime } from './runtime/runtime.js';
288
- export { createCoreOps } from './runtime/core-ops.js';
290
+ export { createSemanticFacades } from './runtime/facades/index.js';
289
291
  export { createDomainFacade, createDomainFacades } from './runtime/domain-ops.js';
290
- export { createPlanningExtraOps } from './runtime/planning-extra-ops.js';
291
- export { createMemoryExtraOps } from './runtime/memory-extra-ops.js';
292
- export { createVaultExtraOps } from './runtime/vault-extra-ops.js';
293
- export { createAdminOps } from './runtime/admin-ops.js';
294
- export { createAdminExtraOps } from './runtime/admin-extra-ops.js';
295
- export { createLoopOps } from './runtime/loop-ops.js';
296
- export { createOrchestrateOps } from './runtime/orchestrate-ops.js';
297
- export { createGradingOps } from './runtime/grading-ops.js';
298
- export { createCaptureOps } from './runtime/capture-ops.js';
299
- export { createCuratorExtraOps } from './runtime/curator-extra-ops.js';
300
- export { createProjectOps } from './runtime/project-ops.js';
301
- export { createMemoryCrossProjectOps } from './runtime/memory-cross-project-ops.js';
302
- export { createCogneeSyncOps } from './runtime/cognee-sync-ops.js';
303
- export { createIntakeOps } from './runtime/intake-ops.js';
304
292
  export type { AgentRuntimeConfig, AgentRuntime } from './runtime/types.js';
305
293
 
306
294
  // ─── Errors ────────────────────────────────────────────────────────────
@@ -337,6 +325,15 @@ export type {
337
325
  FtsSearchOptions,
338
326
  } from './persistence/index.js';
339
327
 
328
+ // ─── Streams ──────────────────────────────────────────────────────────
329
+ export { ReplayableStream } from './streams/index.js';
330
+ export { normalize, collect } from './streams/index.js';
331
+ export type { NestableInput } from './streams/index.js';
332
+
333
+ // ─── Content Hashing ──────────────────────────────────────────────────
334
+ export { computeContentHash } from './vault/content-hash.js';
335
+ export type { HashableEntry } from './vault/content-hash.js';
336
+
340
337
  // ─── Prompts ───────────────────────────────────────────────────────────
341
338
  export { TemplateManager, parseVariables, resolveIncludes } from './prompts/index.js';
342
339
  export type { PromptTemplate, TemplateVariable, RenderOptions } from './prompts/index.js';
@@ -3,13 +3,14 @@
3
3
  * Ported from Salvador MCP's plan-gap-content.ts / plan-gap-technical.ts /
4
4
  * plan-gap-domain.ts / plan-gap-antipattern.ts.
5
5
  *
6
- * 6 built-in passes (always run):
6
+ * 7 built-in passes (always run):
7
7
  * 1. Structure — required fields present and sufficiently long
8
8
  * 2. Completeness — measurable objectives, decision rationale, scope exclusions
9
9
  * 3. Feasibility — overly broad scope, missing dependency awareness
10
10
  * 4. Risk — breaking changes without mitigation, missing verification
11
11
  * 5. Clarity — ambiguous language, vague criteria
12
12
  * 6. Semantic Quality — generic objectives, shallow rationale, non-concrete approach
13
+ * 7. Knowledge Depth — BONUS: vault pattern refs, acceptance criteria, domain indicators
13
14
  *
14
15
  * Opt-in pass factories (registered via customPasses):
15
16
  * - createToolFeasibilityPass — validates tool_chain entries and ordering
@@ -517,6 +518,98 @@ function analyzeSemanticQuality(plan: Plan): PlanGap[] {
517
518
  return gaps;
518
519
  }
519
520
 
521
+ // ─── Pass 7: Knowledge Depth (Substance Bonuses) ────────────────
522
+
523
+ /**
524
+ * Patterns that indicate vault/knowledge-informed content in task descriptions.
525
+ * Each match earns a bonus point — rewarding plans that reference specific
526
+ * patterns, anti-patterns, or domain knowledge rather than generic guidance.
527
+ */
528
+ const KNOWLEDGE_INDICATORS = [
529
+ /vault\s*pattern/i,
530
+ /vault\s*patterns/i,
531
+ /anti-pattern/i,
532
+ /wcag\s*[\d.]+/i,
533
+ /aria-[a-z]+/i,
534
+ /\d+(\.\d+)?:\d+\s*(contrast|ratio)/i,
535
+ /\d+px\s*(touch|target|minimum|min)/i,
536
+ /acceptance\s*criteria/i,
537
+ ];
538
+
539
+ /** Checks if task descriptions reference specific named patterns (e.g. "zod-form-validation"). */
540
+ const NAMED_PATTERN_REGEX = /[a-z]+-[a-z]+-[a-z]+/;
541
+
542
+ function analyzeKnowledgeDepth(plan: Plan): PlanGap[] {
543
+ const gaps: PlanGap[] = [];
544
+ const allTaskText = taskText(plan);
545
+
546
+ // Bonus: tasks reference vault patterns by name
547
+ let namedPatternCount = 0;
548
+ for (const task of plan.tasks) {
549
+ const desc = task.description || '';
550
+ const matches = desc.match(/[a-z]+-[a-z]+(-[a-z]+)*/g) || [];
551
+ // Filter to likely pattern IDs (hyphenated, 2+ segments, not common words)
552
+ const patternRefs = matches.filter(
553
+ (m) => m.length > 8 && NAMED_PATTERN_REGEX.test(m) && !['front-end', 'back-end', 'real-time', 'client-side', 'server-side'].includes(m),
554
+ );
555
+ namedPatternCount += patternRefs.length;
556
+ }
557
+
558
+ if (namedPatternCount >= 5) {
559
+ gaps.push(
560
+ gap('bonus', 'knowledge-depth', `${namedPatternCount} vault pattern references across tasks — strong knowledge-informed plan.`, '', 'tasks', 'vault_pattern_refs_high'),
561
+ );
562
+ gaps.push(
563
+ gap('bonus', 'knowledge-depth', 'Vault pattern density indicates expert-level domain knowledge.', '', 'tasks', 'vault_pattern_density'),
564
+ );
565
+ } else if (namedPatternCount >= 2) {
566
+ gaps.push(
567
+ gap('bonus', 'knowledge-depth', `${namedPatternCount} vault pattern references across tasks.`, '', 'tasks', 'vault_pattern_refs_medium'),
568
+ );
569
+ }
570
+
571
+ // Bonus: tasks have specific acceptance criteria
572
+ let tasksWithCriteria = 0;
573
+ let totalCriteria = 0;
574
+ for (const task of plan.tasks) {
575
+ if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
576
+ tasksWithCriteria++;
577
+ totalCriteria += task.acceptanceCriteria.length;
578
+ }
579
+ }
580
+
581
+ if (plan.tasks.length > 0 && tasksWithCriteria / plan.tasks.length >= 0.8) {
582
+ gaps.push(
583
+ gap('bonus', 'knowledge-depth', `${tasksWithCriteria}/${plan.tasks.length} tasks have acceptance criteria (${totalCriteria} total).`, '', 'tasks', 'high_acceptance_criteria'),
584
+ );
585
+ }
586
+
587
+ // Bonus: knowledge indicator patterns in task text
588
+ let indicatorHits = 0;
589
+ for (const pattern of KNOWLEDGE_INDICATORS) {
590
+ if (pattern.test(allTaskText)) indicatorHits++;
591
+ }
592
+
593
+ if (indicatorHits >= 4) {
594
+ gaps.push(
595
+ gap('bonus', 'knowledge-depth', `${indicatorHits} domain-specific knowledge indicators found (WCAG, ARIA, contrast ratios, touch targets, etc.).`, '', 'tasks', 'domain_knowledge_indicators'),
596
+ );
597
+ }
598
+
599
+ // Bonus: rich task descriptions (avg > 80 chars per task)
600
+ if (plan.tasks.length > 0) {
601
+ const avgDescLength =
602
+ plan.tasks.reduce((sum, t) => sum + (t.description?.length ?? 0), 0) / plan.tasks.length;
603
+ if (avgDescLength >= 80) {
604
+ gaps.push(
605
+ gap('bonus', 'knowledge-depth', `Task descriptions average ${Math.round(avgDescLength)} chars — detailed and specific.`, '', 'tasks', 'rich_task_descriptions'),
606
+ );
607
+ }
608
+ }
609
+
610
+ return gaps;
611
+ }
612
+
520
613
  // ─── Types ───────────────────────────────────────────────────────
521
614
 
522
615
  /** A custom gap analysis pass that agents can register. */
@@ -762,6 +855,7 @@ export function runGapAnalysis(plan: Plan, options?: GapAnalysisOptions): PlanGa
762
855
  ...analyzeRisk(plan),
763
856
  ...analyzeClarity(plan),
764
857
  ...analyzeSemanticQuality(plan),
858
+ ...analyzeKnowledgeDepth(plan),
765
859
  ];
766
860
 
767
861
  // Run custom passes (domain-specific checks like tool-feasibility, UI context, etc.)
@@ -5,7 +5,7 @@
5
5
 
6
6
  // ─── Severity & Category ─────────────────────────────────────────
7
7
 
8
- export type GapSeverity = 'critical' | 'major' | 'minor' | 'info';
8
+ export type GapSeverity = 'critical' | 'major' | 'minor' | 'info' | 'bonus';
9
9
 
10
10
  export type GapCategory =
11
11
  | 'structure'
@@ -14,6 +14,7 @@ export type GapCategory =
14
14
  | 'risk'
15
15
  | 'clarity'
16
16
  | 'semantic-quality'
17
+ | 'knowledge-depth'
17
18
  | 'tool-feasibility'
18
19
  | 'flow-alignment'
19
20
  | 'anti-pattern';
@@ -32,12 +33,13 @@ export interface PlanGap {
32
33
 
33
34
  // ─── Scoring Constants ───────────────────────────────────────────
34
35
 
35
- /** Points deducted per gap severity. */
36
+ /** Points deducted per gap severity. Negative = bonus (adds points). */
36
37
  export const SEVERITY_WEIGHTS: Record<GapSeverity, number> = {
37
38
  critical: 30,
38
39
  major: 15,
39
40
  minor: 2,
40
41
  info: 0,
42
+ bonus: -3,
41
43
  };
42
44
 
43
45
  /**
@@ -48,6 +50,14 @@ export const CATEGORY_PENALTY_CAPS: Record<string, number> = {
48
50
  clarity: 10,
49
51
  };
50
52
 
53
+ /**
54
+ * Maximum bonus per category.
55
+ * Substance bonuses offset structural penalties but are capped to prevent gaming.
56
+ */
57
+ export const CATEGORY_BONUS_CAPS: Record<string, number> = {
58
+ 'knowledge-depth': 15,
59
+ };
60
+
51
61
  // ─── Validation Thresholds ───────────────────────────────────────
52
62
 
53
63
  export const MIN_OBJECTIVE_LENGTH = 10;
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { dirname } from 'node:path';
4
4
  import type { PlanGap } from './gap-types.js';
5
- import { SEVERITY_WEIGHTS, CATEGORY_PENALTY_CAPS } from './gap-types.js';
5
+ import { SEVERITY_WEIGHTS, CATEGORY_PENALTY_CAPS, CATEGORY_BONUS_CAPS } from './gap-types.js';
6
6
  import { runGapAnalysis } from './gap-analysis.js';
7
7
  import type { GapAnalysisOptions } from './gap-analysis.js';
8
8
 
@@ -207,7 +207,8 @@ export interface PlanCheck {
207
207
  * - Score = max(0, 100 - totalDeductions)
208
208
  */
209
209
  export function calculateScore(gaps: PlanGap[], iteration: number = 1): number {
210
- const categoryTotals = new Map<string, number>();
210
+ const categoryDeductions = new Map<string, number>();
211
+ const categoryBonuses = new Map<string, number>();
211
212
 
212
213
  for (const gap of gaps) {
213
214
  let weight: number = SEVERITY_WEIGHTS[gap.severity];
@@ -220,16 +221,27 @@ export function calculateScore(gaps: PlanGap[], iteration: number = 1): number {
220
221
  }
221
222
 
222
223
  const category = gap.category;
223
- categoryTotals.set(category, (categoryTotals.get(category) ?? 0) + weight);
224
+ if (weight < 0) {
225
+ // Bonus — accumulate as positive value for capping, apply as negative deduction
226
+ categoryBonuses.set(category, (categoryBonuses.get(category) ?? 0) + Math.abs(weight));
227
+ } else {
228
+ categoryDeductions.set(category, (categoryDeductions.get(category) ?? 0) + weight);
229
+ }
224
230
  }
225
231
 
226
232
  let deductions = 0;
227
- for (const [category, total] of categoryTotals) {
233
+ for (const [category, total] of categoryDeductions) {
228
234
  const cap = CATEGORY_PENALTY_CAPS[category];
229
235
  deductions += cap !== undefined ? Math.min(total, cap) : total;
230
236
  }
231
237
 
232
- return Math.max(0, 100 - deductions);
238
+ let bonuses = 0;
239
+ for (const [category, total] of categoryBonuses) {
240
+ const cap = CATEGORY_BONUS_CAPS[category];
241
+ bonuses += cap !== undefined ? Math.min(total, cap) : total;
242
+ }
243
+
244
+ return Math.max(0, Math.min(100, 100 - deductions + bonuses));
233
245
  }
234
246
 
235
247
  /**
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Admin facade — infrastructure ops.
3
+ * health, config, telemetry, tokens, LLM, prompts.
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import type { OpDefinition } from '../../facades/types.js';
8
+ import type { AgentRuntime } from '../types.js';
9
+ import { createAdminOps } from '../admin-ops.js';
10
+ import { createAdminExtraOps } from '../admin-extra-ops.js';
11
+
12
+ export function createAdminFacadeOps(runtime: AgentRuntime): OpDefinition[] {
13
+ const { llmClient, keyPool } = runtime;
14
+
15
+ return [
16
+ // ─── LLM (inline from core-ops.ts) ──────────────────────────
17
+ {
18
+ name: 'llm_rotate',
19
+ description:
20
+ 'Force rotate the active API key for a provider. Useful when rate-limited or key is failing.',
21
+ auth: 'write',
22
+ schema: z.object({
23
+ provider: z.enum(['openai', 'anthropic']),
24
+ }),
25
+ handler: async (params) => {
26
+ const provider = params.provider as 'openai' | 'anthropic';
27
+ const pool = keyPool[provider];
28
+ if (!pool.hasKeys) return { rotated: false, error: `No ${provider} keys configured` };
29
+ const newKey = pool.rotateOnError();
30
+ return {
31
+ rotated: newKey !== null,
32
+ activeKeyIndex: pool.activeKeyIndex,
33
+ poolSize: pool.poolSize,
34
+ exhausted: pool.exhausted,
35
+ };
36
+ },
37
+ },
38
+ {
39
+ name: 'llm_call',
40
+ description: 'Make an LLM completion call. Uses model routing config and key pool rotation.',
41
+ auth: 'write',
42
+ schema: z.object({
43
+ systemPrompt: z.string().describe('System prompt for the LLM.'),
44
+ userPrompt: z.string().describe('User prompt / task input.'),
45
+ model: z
46
+ .string()
47
+ .optional()
48
+ .describe('Model name. Routed via model-routing.json if omitted.'),
49
+ temperature: z.number().optional().describe('Sampling temperature (0-2). Default 0.3.'),
50
+ maxTokens: z.number().optional().describe('Max output tokens. Default 500.'),
51
+ caller: z.string().optional().describe('Caller name for routing. Default "core-ops".'),
52
+ task: z.string().optional().describe('Task name for routing.'),
53
+ }),
54
+ handler: async (params) => {
55
+ return llmClient.complete({
56
+ provider: 'openai',
57
+ model: (params.model as string) ?? '',
58
+ systemPrompt: params.systemPrompt as string,
59
+ userPrompt: params.userPrompt as string,
60
+ temperature: params.temperature as number | undefined,
61
+ maxTokens: params.maxTokens as number | undefined,
62
+ caller: (params.caller as string) ?? 'core-ops',
63
+ task: params.task as string | undefined,
64
+ });
65
+ },
66
+ },
67
+
68
+ // ─── Prompt Templates (inline from core-ops.ts) ─────────────
69
+ {
70
+ name: 'render_prompt',
71
+ description:
72
+ 'Render a prompt template with variable substitution. Templates are .prompt files loaded from the templates directory.',
73
+ auth: 'read' as const,
74
+ schema: z.object({
75
+ template: z.string().describe('Template name (without .prompt extension)'),
76
+ variables: z.record(z.string()).optional().default({}),
77
+ strict: z.boolean().optional().default(true),
78
+ }),
79
+ handler: async (params) => {
80
+ const rendered = runtime.templateManager.render(
81
+ params.template as string,
82
+ (params.variables ?? {}) as Record<string, string>,
83
+ { strict: params.strict as boolean },
84
+ );
85
+ return { rendered };
86
+ },
87
+ },
88
+ {
89
+ name: 'list_templates',
90
+ description: 'List all loaded prompt templates.',
91
+ auth: 'read' as const,
92
+ handler: async () => ({
93
+ templates: runtime.templateManager.listTemplates(),
94
+ }),
95
+ },
96
+
97
+ // ─── Satellite ops ───────────────────────────────────────────
98
+ ...createAdminOps(runtime),
99
+ ...createAdminExtraOps(runtime),
100
+ ];
101
+ }