@soleri/core 9.3.1 → 9.4.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 (172) hide show
  1. package/dist/brain/intelligence.d.ts +5 -0
  2. package/dist/brain/intelligence.d.ts.map +1 -1
  3. package/dist/brain/intelligence.js +115 -26
  4. package/dist/brain/intelligence.js.map +1 -1
  5. package/dist/brain/learning-radar.d.ts +3 -3
  6. package/dist/brain/learning-radar.d.ts.map +1 -1
  7. package/dist/brain/learning-radar.js +8 -4
  8. package/dist/brain/learning-radar.js.map +1 -1
  9. package/dist/control/intent-router.d.ts +2 -2
  10. package/dist/control/intent-router.d.ts.map +1 -1
  11. package/dist/control/intent-router.js +35 -1
  12. package/dist/control/intent-router.js.map +1 -1
  13. package/dist/control/types.d.ts +10 -2
  14. package/dist/control/types.d.ts.map +1 -1
  15. package/dist/curator/curator.d.ts +4 -0
  16. package/dist/curator/curator.d.ts.map +1 -1
  17. package/dist/curator/curator.js +23 -1
  18. package/dist/curator/curator.js.map +1 -1
  19. package/dist/curator/schema.d.ts +1 -1
  20. package/dist/curator/schema.d.ts.map +1 -1
  21. package/dist/curator/schema.js +8 -0
  22. package/dist/curator/schema.js.map +1 -1
  23. package/dist/domain-packs/types.d.ts +6 -0
  24. package/dist/domain-packs/types.d.ts.map +1 -1
  25. package/dist/domain-packs/types.js +1 -0
  26. package/dist/domain-packs/types.js.map +1 -1
  27. package/dist/engine/module-manifest.js +3 -3
  28. package/dist/engine/module-manifest.js.map +1 -1
  29. package/dist/engine/register-engine.d.ts +9 -0
  30. package/dist/engine/register-engine.d.ts.map +1 -1
  31. package/dist/engine/register-engine.js +59 -1
  32. package/dist/engine/register-engine.js.map +1 -1
  33. package/dist/facades/types.d.ts +5 -1
  34. package/dist/facades/types.d.ts.map +1 -1
  35. package/dist/facades/types.js.map +1 -1
  36. package/dist/index.d.ts +4 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/operator/operator-context-store.d.ts +54 -0
  41. package/dist/operator/operator-context-store.d.ts.map +1 -0
  42. package/dist/operator/operator-context-store.js +434 -0
  43. package/dist/operator/operator-context-store.js.map +1 -0
  44. package/dist/operator/operator-context-types.d.ts +101 -0
  45. package/dist/operator/operator-context-types.d.ts.map +1 -0
  46. package/dist/operator/operator-context-types.js +27 -0
  47. package/dist/operator/operator-context-types.js.map +1 -0
  48. package/dist/packs/index.d.ts +2 -2
  49. package/dist/packs/index.d.ts.map +1 -1
  50. package/dist/packs/index.js +1 -1
  51. package/dist/packs/index.js.map +1 -1
  52. package/dist/packs/lockfile.d.ts +3 -0
  53. package/dist/packs/lockfile.d.ts.map +1 -1
  54. package/dist/packs/lockfile.js.map +1 -1
  55. package/dist/packs/types.d.ts +8 -2
  56. package/dist/packs/types.d.ts.map +1 -1
  57. package/dist/packs/types.js +6 -0
  58. package/dist/packs/types.js.map +1 -1
  59. package/dist/planning/plan-lifecycle.d.ts +12 -1
  60. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  61. package/dist/planning/plan-lifecycle.js +52 -19
  62. package/dist/planning/plan-lifecycle.js.map +1 -1
  63. package/dist/planning/planner-types.d.ts +6 -0
  64. package/dist/planning/planner-types.d.ts.map +1 -1
  65. package/dist/planning/planner.d.ts +21 -1
  66. package/dist/planning/planner.d.ts.map +1 -1
  67. package/dist/planning/planner.js +62 -3
  68. package/dist/planning/planner.js.map +1 -1
  69. package/dist/planning/task-complexity-assessor.d.ts.map +1 -1
  70. package/dist/planning/task-complexity-assessor.js.map +1 -1
  71. package/dist/plugins/types.d.ts +18 -18
  72. package/dist/runtime/admin-ops.d.ts +1 -1
  73. package/dist/runtime/admin-ops.d.ts.map +1 -1
  74. package/dist/runtime/admin-ops.js +100 -3
  75. package/dist/runtime/admin-ops.js.map +1 -1
  76. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  77. package/dist/runtime/admin-setup-ops.js +19 -9
  78. package/dist/runtime/admin-setup-ops.js.map +1 -1
  79. package/dist/runtime/capture-ops.d.ts.map +1 -1
  80. package/dist/runtime/capture-ops.js +35 -7
  81. package/dist/runtime/capture-ops.js.map +1 -1
  82. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  83. package/dist/runtime/facades/brain-facade.js +4 -2
  84. package/dist/runtime/facades/brain-facade.js.map +1 -1
  85. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  86. package/dist/runtime/facades/control-facade.js +8 -2
  87. package/dist/runtime/facades/control-facade.js.map +1 -1
  88. package/dist/runtime/facades/curator-facade.d.ts.map +1 -1
  89. package/dist/runtime/facades/curator-facade.js +13 -0
  90. package/dist/runtime/facades/curator-facade.js.map +1 -1
  91. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  92. package/dist/runtime/facades/memory-facade.js +10 -12
  93. package/dist/runtime/facades/memory-facade.js.map +1 -1
  94. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  95. package/dist/runtime/facades/orchestrate-facade.js +36 -1
  96. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  97. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  98. package/dist/runtime/facades/plan-facade.js +20 -4
  99. package/dist/runtime/facades/plan-facade.js.map +1 -1
  100. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  101. package/dist/runtime/orchestrate-ops.js +71 -4
  102. package/dist/runtime/orchestrate-ops.js.map +1 -1
  103. package/dist/runtime/plan-feedback-helper.d.ts +21 -0
  104. package/dist/runtime/plan-feedback-helper.d.ts.map +1 -0
  105. package/dist/runtime/plan-feedback-helper.js +52 -0
  106. package/dist/runtime/plan-feedback-helper.js.map +1 -0
  107. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  108. package/dist/runtime/planning-extra-ops.js +73 -34
  109. package/dist/runtime/planning-extra-ops.js.map +1 -1
  110. package/dist/runtime/session-briefing.d.ts.map +1 -1
  111. package/dist/runtime/session-briefing.js +9 -1
  112. package/dist/runtime/session-briefing.js.map +1 -1
  113. package/dist/runtime/types.d.ts +3 -0
  114. package/dist/runtime/types.d.ts.map +1 -1
  115. package/dist/skills/sync-skills.d.ts.map +1 -1
  116. package/dist/skills/sync-skills.js +13 -7
  117. package/dist/skills/sync-skills.js.map +1 -1
  118. package/package.json +1 -1
  119. package/src/brain/brain-intelligence.test.ts +30 -0
  120. package/src/brain/extraction-quality.test.ts +323 -0
  121. package/src/brain/intelligence.ts +133 -30
  122. package/src/brain/learning-radar.ts +8 -5
  123. package/src/brain/second-brain-features.test.ts +1 -1
  124. package/src/control/intent-router.test.ts +73 -3
  125. package/src/control/intent-router.ts +38 -1
  126. package/src/control/types.ts +13 -2
  127. package/src/curator/curator.test.ts +92 -0
  128. package/src/curator/curator.ts +29 -1
  129. package/src/curator/schema.ts +8 -0
  130. package/src/domain-packs/types.ts +8 -0
  131. package/src/engine/module-manifest.test.ts +8 -2
  132. package/src/engine/module-manifest.ts +3 -3
  133. package/src/engine/register-engine.test.ts +73 -1
  134. package/src/engine/register-engine.ts +61 -1
  135. package/src/facades/types.ts +5 -0
  136. package/src/index.ts +22 -0
  137. package/src/operator/operator-context-store.test.ts +698 -0
  138. package/src/operator/operator-context-store.ts +569 -0
  139. package/src/operator/operator-context-types.ts +139 -0
  140. package/src/packs/index.ts +3 -1
  141. package/src/packs/lockfile.ts +3 -0
  142. package/src/packs/types.ts +9 -0
  143. package/src/planning/plan-lifecycle.ts +80 -22
  144. package/src/planning/planner-types.ts +6 -0
  145. package/src/planning/planner.ts +74 -4
  146. package/src/planning/task-complexity-assessor.test.ts +6 -2
  147. package/src/planning/task-complexity-assessor.ts +1 -4
  148. package/src/runtime/admin-ops.test.ts +139 -6
  149. package/src/runtime/admin-ops.ts +104 -3
  150. package/src/runtime/admin-setup-ops.ts +30 -10
  151. package/src/runtime/capture-ops.test.ts +84 -0
  152. package/src/runtime/capture-ops.ts +35 -7
  153. package/src/runtime/facades/admin-facade.test.ts +1 -1
  154. package/src/runtime/facades/brain-facade.ts +6 -3
  155. package/src/runtime/facades/control-facade.ts +10 -2
  156. package/src/runtime/facades/curator-facade.ts +18 -0
  157. package/src/runtime/facades/memory-facade.test.ts +14 -12
  158. package/src/runtime/facades/memory-facade.ts +10 -12
  159. package/src/runtime/facades/orchestrate-facade.ts +33 -1
  160. package/src/runtime/facades/plan-facade.test.ts +213 -0
  161. package/src/runtime/facades/plan-facade.ts +23 -4
  162. package/src/runtime/orchestrate-ops.test.ts +202 -2
  163. package/src/runtime/orchestrate-ops.ts +85 -4
  164. package/src/runtime/plan-feedback-helper.test.ts +173 -0
  165. package/src/runtime/plan-feedback-helper.ts +63 -0
  166. package/src/runtime/planning-extra-ops.test.ts +43 -1
  167. package/src/runtime/planning-extra-ops.ts +96 -33
  168. package/src/runtime/session-briefing.test.ts +1 -0
  169. package/src/runtime/session-briefing.ts +10 -1
  170. package/src/runtime/types.ts +3 -0
  171. package/src/skills/sync-skills.ts +14 -7
  172. package/vitest.config.ts +1 -0
@@ -15,6 +15,7 @@ import type {
15
15
  OperationalMode,
16
16
  IntentClassification,
17
17
  ModeConfig,
18
+ MorphOptions,
18
19
  MorphResult,
19
20
  RoutingAccuracyReport,
20
21
  } from './types.js';
@@ -132,6 +133,27 @@ const DEFAULT_MODES: ModeConfig[] = [
132
133
  behaviorRules: ['Be helpful', 'Ask clarifying questions when needed'],
133
134
  keywords: [],
134
135
  },
136
+ {
137
+ mode: 'YOLO-MODE',
138
+ intent: 'yolo',
139
+ description: 'Autonomous execution — skip approval gates, execute directly',
140
+ behaviorRules: [
141
+ 'Skip plan approval gates — execute tasks directly',
142
+ 'Still run orchestrate_complete — knowledge capture is non-negotiable',
143
+ 'Still run vault gather-before-execute — decisions must be informed',
144
+ 'Hook pack must be installed — refuse to activate without yolo-safety hooks',
145
+ 'User can exit with "exit YOLO" or session end',
146
+ ],
147
+ keywords: [
148
+ 'yolo',
149
+ 'autonomous',
150
+ 'fire-and-forget',
151
+ 'hands-off',
152
+ 'no-approval',
153
+ 'skip-gates',
154
+ 'full-auto',
155
+ ],
156
+ },
135
157
  ];
136
158
 
137
159
  // ─── Class ──────────────────────────────────────────────────────────
@@ -267,7 +289,7 @@ export class IntentRouter {
267
289
 
268
290
  // ─── Mode Management ───────────────────────────────────────────────
269
291
 
270
- morph(mode: OperationalMode): MorphResult {
292
+ morph(mode: OperationalMode, options?: MorphOptions): MorphResult {
271
293
  // Handle "reset" as a built-in alias for GENERAL-MODE
272
294
  const resolvedMode: OperationalMode = (mode as string) === 'reset' ? 'GENERAL-MODE' : mode;
273
295
 
@@ -283,6 +305,21 @@ export class IntentRouter {
283
305
  throw new Error(`Unknown mode: ${mode}. Available: ${available}`);
284
306
  }
285
307
 
308
+ // ─── YOLO-MODE activation gate ────────────────────────────────
309
+ // YOLO-MODE requires the yolo-safety hook pack to be installed.
310
+ // The CLI/facade layer provides hookPackInstalled based on filesystem check.
311
+ if (resolvedMode === 'YOLO-MODE' && !options?.hookPackInstalled) {
312
+ return {
313
+ previousMode: this.currentMode,
314
+ currentMode: this.currentMode, // unchanged — mode switch blocked
315
+ behaviorRules: this.getBehaviorRules(),
316
+ blocked: true,
317
+ error:
318
+ 'YOLO-MODE requires the yolo-safety hook pack. ' +
319
+ 'Install it with: soleri hooks add-pack yolo-safety',
320
+ };
321
+ }
322
+
286
323
  const previousMode = this.currentMode;
287
324
  this.currentMode = resolvedMode;
288
325
  const behaviorRules = JSON.parse(row.behavior_rules) as string[];
@@ -65,7 +65,8 @@ export type IntentType =
65
65
  | 'explore'
66
66
  | 'plan'
67
67
  | 'review'
68
- | 'general';
68
+ | 'general'
69
+ | 'yolo';
69
70
 
70
71
  export type OperationalMode =
71
72
  | 'BUILD-MODE'
@@ -77,7 +78,8 @@ export type OperationalMode =
77
78
  | 'EXPLORE-MODE'
78
79
  | 'PLAN-MODE'
79
80
  | 'REVIEW-MODE'
80
- | 'GENERAL-MODE';
81
+ | 'GENERAL-MODE'
82
+ | 'YOLO-MODE';
81
83
 
82
84
  export interface IntentClassification {
83
85
  intent: IntentType;
@@ -95,10 +97,19 @@ export interface ModeConfig {
95
97
  keywords: string[];
96
98
  }
97
99
 
100
+ export interface MorphOptions {
101
+ /** Whether the yolo-safety hook pack is installed. Required for YOLO-MODE activation. */
102
+ hookPackInstalled?: boolean;
103
+ }
104
+
98
105
  export interface MorphResult {
99
106
  previousMode: OperationalMode;
100
107
  currentMode: OperationalMode;
101
108
  behaviorRules: string[];
109
+ /** Present when activation is refused (e.g. missing hook pack). */
110
+ error?: string;
111
+ /** When true, the mode switch was blocked. */
112
+ blocked?: boolean;
102
113
  }
103
114
 
104
115
  export interface RoutingAccuracyReport {
@@ -647,4 +647,96 @@ describe('Curator', () => {
647
647
  expect(result.metrics.tagHealth).toBeLessThan(1);
648
648
  });
649
649
  });
650
+
651
+ // ─── Duplicate Dismissal ──────────────────────────────────────
652
+
653
+ describe('Duplicate Dismissal', () => {
654
+ it('dismissDuplicate stores a pair and detectDuplicates filters it out', () => {
655
+ // Add two similar entries that will be flagged as duplicates
656
+ vault.add(
657
+ makeEntry({
658
+ id: 'dup-a',
659
+ title: 'React hook cleanup pattern',
660
+ description:
661
+ 'Always clean up useEffect hooks with return cleanup function to prevent memory leaks and stale closures.',
662
+ }),
663
+ );
664
+ vault.add(
665
+ makeEntry({
666
+ id: 'dup-b',
667
+ title: 'React hook cleanup pattern for effects',
668
+ description:
669
+ 'Clean up useEffect hooks by returning a cleanup function to avoid memory leaks and stale closures.',
670
+ }),
671
+ );
672
+
673
+ // Verify they show up as duplicates
674
+ const before = curator.detectDuplicates();
675
+ const dupBefore = before.find(
676
+ (d) => d.entryId === 'dup-a' && d.matches.some((m) => m.entryId === 'dup-b'),
677
+ );
678
+ expect(dupBefore).toBeDefined();
679
+
680
+ // Dismiss the pair
681
+ const result = curator.dismissDuplicate('dup-a', 'dup-b', 'intentionally distinct');
682
+ expect(result.dismissed).toBe(true);
683
+
684
+ // Verify they no longer show up
685
+ const after = curator.detectDuplicates();
686
+ const dupAfter = after.find(
687
+ (d) => d.entryId === 'dup-a' && d.matches.some((m) => m.entryId === 'dup-b'),
688
+ );
689
+ expect(dupAfter).toBeUndefined();
690
+ });
691
+
692
+ it('dismissDuplicate works with swapped ID order', () => {
693
+ vault.add(
694
+ makeEntry({
695
+ id: 'sw-a',
696
+ title: 'Semantic color tokens for error states',
697
+ description: 'Use semantic tokens like text-error and bg-error-subtle for error UI.',
698
+ }),
699
+ );
700
+ vault.add(
701
+ makeEntry({
702
+ id: 'sw-b',
703
+ title: 'Semantic color tokens for error UI',
704
+ description:
705
+ 'Always prefer semantic tokens text-error and bg-error-subtle over raw colors.',
706
+ }),
707
+ );
708
+
709
+ // Dismiss with b,a order
710
+ curator.dismissDuplicate('sw-b', 'sw-a');
711
+
712
+ // Should still be filtered (stored as sorted pair)
713
+ const results = curator.detectDuplicates('sw-a');
714
+ const match = results.find((r) => r.matches.some((m) => m.entryId === 'sw-b'));
715
+ expect(match).toBeUndefined();
716
+ });
717
+
718
+ it('dismissDuplicate is idempotent', () => {
719
+ vault.add(
720
+ makeEntry({
721
+ id: 'idem-a',
722
+ title: 'Pattern A idempotent test',
723
+ description: 'First entry for idempotency test of duplicate dismissal.',
724
+ }),
725
+ );
726
+ vault.add(
727
+ makeEntry({
728
+ id: 'idem-b',
729
+ title: 'Pattern A idempotent test duplicate',
730
+ description: 'Second entry for idempotency test of duplicate dismissal.',
731
+ }),
732
+ );
733
+
734
+ const first = curator.dismissDuplicate('idem-a', 'idem-b');
735
+ expect(first.dismissed).toBe(true);
736
+
737
+ // Second call should be a no-op (INSERT OR IGNORE)
738
+ const second = curator.dismissDuplicate('idem-a', 'idem-b');
739
+ expect(second.dismissed).toBe(false);
740
+ });
741
+ });
650
742
  });
@@ -151,7 +151,35 @@ export class Curator {
151
151
  // ─── Duplicates (delegates to duplicate-detector) ─────────────
152
152
 
153
153
  detectDuplicates(entryId?: string, threshold?: number): DuplicateDetectionResult[] {
154
- return detectDuplicatesPure(this.vault.list({ limit: 100000 }), entryId, threshold);
154
+ const results = detectDuplicatesPure(this.vault.list({ limit: 100000 }), entryId, threshold);
155
+ // Filter out dismissed pairs
156
+ const dismissed = this.getDismissedPairs();
157
+ if (dismissed.size === 0) return results;
158
+ return results
159
+ .map((r) => ({
160
+ ...r,
161
+ matches: r.matches.filter((m) => {
162
+ const key = [r.entryId, m.entryId].sort().join('::');
163
+ return !dismissed.has(key);
164
+ }),
165
+ }))
166
+ .filter((r) => r.matches.length > 0);
167
+ }
168
+
169
+ dismissDuplicate(entryIdA: string, entryIdB: string, reason?: string): { dismissed: boolean } {
170
+ const [a, b] = [entryIdA, entryIdB].sort();
171
+ const result = this.provider.run(
172
+ 'INSERT OR IGNORE INTO curator_duplicate_dismissals (entry_id_a, entry_id_b, reason) VALUES (?, ?, ?)',
173
+ [a, b, reason ?? 'reviewed — not duplicate'],
174
+ );
175
+ return { dismissed: result.changes > 0 };
176
+ }
177
+
178
+ private getDismissedPairs(): Set<string> {
179
+ const rows = this.provider.all<{ entry_id_a: string; entry_id_b: string }>(
180
+ 'SELECT entry_id_a, entry_id_b FROM curator_duplicate_dismissals',
181
+ );
182
+ return new Set(rows.map((r) => `${r.entry_id_a}::${r.entry_id_b}`));
155
183
  }
156
184
 
157
185
  // ─── Contradictions (delegates to contradiction-detector) ─────
@@ -56,6 +56,14 @@ export const CURATOR_SCHEMA = `
56
56
  resolved_at INTEGER,
57
57
  UNIQUE(pattern_id, antipattern_id)
58
58
  );
59
+ CREATE TABLE IF NOT EXISTS curator_duplicate_dismissals (
60
+ entry_id_a TEXT NOT NULL,
61
+ entry_id_b TEXT NOT NULL,
62
+ dismissed_at INTEGER NOT NULL DEFAULT (unixepoch()),
63
+ reason TEXT,
64
+ PRIMARY KEY (entry_id_a, entry_id_b)
65
+ );
66
+
59
67
  CREATE INDEX IF NOT EXISTS idx_curator_state_status ON curator_entry_state(status);
60
68
  CREATE INDEX IF NOT EXISTS idx_curator_changelog_entry ON curator_changelog(entry_id);
61
69
  `;
@@ -67,12 +67,17 @@ export interface PackSkillDefinition {
67
67
  // DomainPack — the main interface
68
68
  // ---------------------------------------------------------------------------
69
69
 
70
+ /** Pack tier: determines visibility, licensing, and install behavior */
71
+ export type DomainPackTier = 'default' | 'community' | 'premium';
72
+
70
73
  /** The contract every domain pack must implement. */
71
74
  export interface DomainPack {
72
75
  /** Unique pack name (e.g., 'design', 'security-intelligence') */
73
76
  name: string;
74
77
  /** Semver version */
75
78
  version: string;
79
+ /** Tier: 'default' (ships with engine), 'community' (free, npm), 'premium' (unlocked today, gated later) */
80
+ tier?: DomainPackTier;
76
81
  /** Domains this pack claims. Ops inject into these domain facades. */
77
82
  domains: string[];
78
83
  /** Custom operations with real logic — injected into claimed domain facades. */
@@ -115,6 +120,8 @@ export interface DomainPackRef {
115
120
  package: string;
116
121
  /** Optional version constraint */
117
122
  version?: string;
123
+ /** Pack tier (inherited from pack if not set) */
124
+ tier?: DomainPackTier;
118
125
  }
119
126
 
120
127
  // ---------------------------------------------------------------------------
@@ -148,6 +155,7 @@ const packSkillSchema = z.object({
148
155
  const domainPackSchema = z.object({
149
156
  name: z.string().min(1),
150
157
  version: z.string().min(1),
158
+ tier: z.enum(['default', 'community', 'premium']).optional(),
151
159
  domains: z.array(z.string().min(1)).min(1),
152
160
  ops: z.array(
153
161
  z.object({
@@ -49,7 +49,7 @@ describe('ENGINE_MODULE_MANIFEST', () => {
49
49
  expect(entry.description.length).toBeGreaterThan(0);
50
50
  expect(Array.isArray(entry.keyOps)).toBe(true);
51
51
  expect(entry.keyOps.length).toBeGreaterThan(0);
52
- expect(entry.keyOps.length).toBeLessThanOrEqual(4);
52
+ expect(entry.keyOps.length).toBeLessThanOrEqual(5);
53
53
  }
54
54
  });
55
55
 
@@ -69,7 +69,13 @@ describe('ENGINE_MODULE_MANIFEST', () => {
69
69
 
70
70
  it('plan module has expected keyOps', () => {
71
71
  const plan = ENGINE_MODULE_MANIFEST.find((m) => m.suffix === 'plan')!;
72
- expect(plan.keyOps).toEqual(['create_plan', 'approve_plan', 'plan_split', 'plan_reconcile']);
72
+ expect(plan.keyOps).toEqual([
73
+ 'create_plan',
74
+ 'approve_plan',
75
+ 'plan_split',
76
+ 'plan_reconcile',
77
+ 'plan_close_stale',
78
+ ]);
73
79
  });
74
80
 
75
81
  it('conditional field is optional and boolean when present', () => {
@@ -41,7 +41,7 @@ export const ENGINE_MODULE_MANIFEST: ModuleManifestEntry[] = [
41
41
  {
42
42
  suffix: 'plan',
43
43
  description: 'Plan lifecycle — create, approve, execute, reconcile, complete, grading.',
44
- keyOps: ['create_plan', 'approve_plan', 'plan_split', 'plan_reconcile'],
44
+ keyOps: ['create_plan', 'approve_plan', 'plan_split', 'plan_reconcile', 'plan_close_stale'],
45
45
  intentSignals: {
46
46
  'plan this': 'create_plan',
47
47
  'break this down': 'create_plan',
@@ -56,7 +56,7 @@ export const ENGINE_MODULE_MANIFEST: ModuleManifestEntry[] = [
56
56
  keyOps: ['recommend', 'strengths', 'feedback'],
57
57
  intentSignals: {
58
58
  'what works': 'recommend',
59
- 'recommendations': 'recommend',
59
+ recommendations: 'recommend',
60
60
  'pattern strengths': 'strengths',
61
61
  'give feedback': 'feedback',
62
62
  },
@@ -87,7 +87,7 @@ export const ENGINE_MODULE_MANIFEST: ModuleManifestEntry[] = [
87
87
  {
88
88
  suffix: 'curator',
89
89
  description: 'Quality — duplicate detection, contradictions, grooming, health audit.',
90
- keyOps: ['curator_groom', 'curator_status', 'curator_health'],
90
+ keyOps: ['curator_groom', 'curator_status', 'curator_health', 'curator_dismiss_duplicate'],
91
91
  intentSignals: {
92
92
  'clean up vault': 'curator_groom',
93
93
  'find duplicates': 'curator_groom',
@@ -15,7 +15,7 @@
15
15
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
16
16
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
17
  import { createAgentRuntime } from '../runtime/runtime.js';
18
- import { registerEngine, ENGINE_MODULES } from './register-engine.js';
18
+ import { registerEngine, ENGINE_MODULES, INTERNAL_OPS } from './register-engine.js';
19
19
  import { ENGINE_MODULE_MANIFEST } from './module-manifest.js';
20
20
  import type { AgentRuntime } from '../runtime/types.js';
21
21
  import type { OpDefinition } from '../facades/types.js';
@@ -233,3 +233,75 @@ describe('ENGINE_MODULES descriptions match manifest', () => {
233
233
  }
234
234
  });
235
235
  });
236
+
237
+ describe('registerEngine — op visibility', () => {
238
+ it('INTERNAL_OPS set contains expected ops', () => {
239
+ // Spot-check known internal ops
240
+ expect(INTERNAL_OPS.has('admin_create_token')).toBe(true);
241
+ expect(INTERNAL_OPS.has('vault_bulk_add')).toBe(true);
242
+ expect(INTERNAL_OPS.has('plan_auto_reconcile')).toBe(true);
243
+ expect(INTERNAL_OPS.has('telemetry_errors')).toBe(true);
244
+ // User-facing ops should NOT be in the set
245
+ expect(INTERNAL_OPS.has('admin_health')).toBe(false);
246
+ expect(INTERNAL_OPS.has('search_intelligent')).toBe(false);
247
+ expect(INTERNAL_OPS.has('create_plan')).toBe(false);
248
+ });
249
+
250
+ it('INTERNAL_OPS has at least 25 entries', () => {
251
+ expect(INTERNAL_OPS.size).toBeGreaterThanOrEqual(25);
252
+ });
253
+
254
+ it('ops without visibility field default to user (backward compat)', () => {
255
+ const server = makeServer();
256
+ const userOp: OpDefinition = {
257
+ name: 'my_visible_op',
258
+ description: 'Visible op',
259
+ auth: 'read',
260
+ handler: async () => 'ok',
261
+ };
262
+ const result = registerEngine(server, runtime, {
263
+ agentId: 'vis',
264
+ domainPacks: [{ name: 'test', facades: [{ name: 'test', ops: [userOp] }] }],
265
+ });
266
+ expect(result.tools).toContain('vis_test');
267
+ });
268
+
269
+ it('ops with visibility: internal are excluded from MCP tool description but remain callable', () => {
270
+ const server = makeServer();
271
+ const visibleOp: OpDefinition = {
272
+ name: 'public_op',
273
+ description: 'Public op',
274
+ auth: 'read',
275
+ handler: async () => 'visible',
276
+ };
277
+ const internalOp: OpDefinition = {
278
+ name: 'secret_op',
279
+ description: 'Internal op',
280
+ auth: 'admin',
281
+ visibility: 'internal',
282
+ handler: async () => 'hidden',
283
+ };
284
+ // Register both ops under a pack facade
285
+ registerEngine(server, runtime, {
286
+ agentId: 'vt',
287
+ domainPacks: [{ name: 'test', facades: [{ name: 'check', ops: [visibleOp, internalOp] }] }],
288
+ });
289
+ // We can't easily inspect the MCP schema description string, but we verify
290
+ // that registration succeeds with mixed visibility ops
291
+ expect(true).toBe(true);
292
+ });
293
+
294
+ it('every INTERNAL_OPS entry corresponds to a real op in some facade', () => {
295
+ // Collect all op names across all engine modules
296
+ const allOpNames = new Set<string>();
297
+ for (const mod of ENGINE_MODULES) {
298
+ const ops = mod.createOps(runtime);
299
+ for (const op of ops) {
300
+ allOpNames.add(op.name);
301
+ }
302
+ }
303
+ for (const internalOp of INTERNAL_OPS) {
304
+ expect(allOpNames.has(internalOp)).toBe(true);
305
+ }
306
+ });
307
+ });
@@ -295,11 +295,70 @@ export function registerEngine(
295
295
  return { tools: registeredTools, totalOps, registerTool };
296
296
  }
297
297
 
298
+ // ─── Op Visibility ───────────────────────────────────────────────────
299
+
300
+ /**
301
+ * Ops classified as internal — hidden from MCP tool descriptions but still
302
+ * callable programmatically. Centralized here for easy auditing.
303
+ *
304
+ * Criteria: ops that are infrastructure plumbing, token/account management,
305
+ * bulk operations, or auto-* automation that users never call directly.
306
+ */
307
+ /** @internal Exported for testing — do not use outside engine */
308
+ export const INTERNAL_OPS = new Set([
309
+ // Admin: token management
310
+ 'admin_create_token',
311
+ 'admin_revoke_token',
312
+ 'admin_list_tokens',
313
+ // Admin: account management
314
+ 'admin_add_account',
315
+ 'admin_remove_account',
316
+ 'admin_rotate_account',
317
+ 'admin_list_accounts',
318
+ 'admin_account_status',
319
+ // Admin: infrastructure debug
320
+ 'admin_vault_size',
321
+ 'admin_vault_analytics',
322
+ 'admin_search_insights',
323
+ 'admin_env',
324
+ 'admin_gc',
325
+ 'admin_export_config',
326
+ 'admin_key_pool_status',
327
+ 'admin_persistence_info',
328
+ // Admin: flags
329
+ 'admin_list_flags',
330
+ 'admin_get_flag',
331
+ 'admin_set_flag',
332
+ // Admin: setup internals
333
+ 'admin_inject_claude_md',
334
+ // Admin: telemetry
335
+ 'admin_telemetry',
336
+ 'admin_telemetry_recent',
337
+ 'admin_telemetry_reset',
338
+ 'telemetry_errors',
339
+ 'telemetry_slow_ops',
340
+ // Vault: bulk operations
341
+ 'vault_bulk_add',
342
+ 'vault_bulk_remove',
343
+ // Plan: automation
344
+ 'plan_auto_reconcile',
345
+ 'plan_auto_improve',
346
+ ]);
347
+
348
+ /** Resolve effective visibility: explicit field wins, then INTERNAL_OPS set, else 'user'. */
349
+ function resolveVisibility(op: OpDefinition): 'user' | 'internal' {
350
+ if (op.visibility) return op.visibility;
351
+ return INTERNAL_OPS.has(op.name) ? 'internal' : 'user';
352
+ }
353
+
298
354
  // ─── Tool Registration (No Factory) ──────────────────────────────────
299
355
 
300
356
  /**
301
357
  * Register a single grouped tool with op dispatch.
302
358
  * This is the replacement for registerFacade() — same behavior, no FacadeConfig type.
359
+ *
360
+ * Internal ops (visibility: 'internal') are excluded from the MCP tool description
361
+ * but remain dispatchable — they just don't show up in the op list Claude sees.
303
362
  */
304
363
  function registerModuleTool(
305
364
  server: McpServer,
@@ -308,7 +367,8 @@ function registerModuleTool(
308
367
  ops: OpDefinition[],
309
368
  authPolicy?: () => AuthPolicy,
310
369
  ): void {
311
- const opNames = ops.map((o) => o.name);
370
+ const userOps = ops.filter((o) => resolveVisibility(o) !== 'internal');
371
+ const opNames = userOps.map((o) => o.name);
312
372
  const opMap = new Map(ops.map((o) => [o.name, o]));
313
373
 
314
374
  server.tool(
@@ -25,6 +25,9 @@ export const AUTH_LEVEL_RANK: Record<AuthLevel, number> = {
25
25
  admin: 2,
26
26
  };
27
27
 
28
+ /** Op visibility — controls whether an op is exposed via MCP tool registration */
29
+ export type OpVisibility = 'user' | 'internal';
30
+
28
31
  /** Operation definition within a facade */
29
32
  export interface OpDefinition {
30
33
  name: string;
@@ -34,6 +37,8 @@ export interface OpDefinition {
34
37
  schema?: z.ZodType;
35
38
  /** Promote to a first-class MCP tool with full schema discovery. */
36
39
  hot?: boolean;
40
+ /** Controls MCP exposure: 'user' (default) = listed in tool, 'internal' = hidden from MCP but callable programmatically. */
41
+ visibility?: OpVisibility;
37
42
  }
38
43
 
39
44
  /** Facade configuration — one MCP tool */
package/src/index.ts CHANGED
@@ -627,6 +627,7 @@ export type {
627
627
  LockEntry,
628
628
  PackType,
629
629
  PackSource,
630
+ PackTier,
630
631
  LockfileData,
631
632
  ResolvedPack,
632
633
  ResolveOptions,
@@ -799,3 +800,24 @@ export type {
799
800
  ProfileSnapshot,
800
801
  OperatorProfileHistory,
801
802
  } from './operator/operator-types.js';
803
+
804
+ // ─── Operator Context (Adaptive Persona) ────────────────────────────
805
+ export { OperatorContextStore } from './operator/operator-context-store.js';
806
+ export { DECLINED_CATEGORIES } from './operator/operator-context-types.js';
807
+ export type {
808
+ OperatorSignals,
809
+ ExpertiseSignal,
810
+ CorrectionSignal,
811
+ InterestSignal,
812
+ WorkPatternSignal,
813
+ OperatorContext,
814
+ ExpertiseItem,
815
+ CorrectionItem,
816
+ InterestItem,
817
+ WorkPatternItem,
818
+ ExpertiseLevel,
819
+ SignalScope,
820
+ PatternFrequency,
821
+ ContextItemType,
822
+ DeclinedCategory,
823
+ } from './operator/operator-context-types.js';