@inixiative/foundry-core 0.5.9 → 0.5.11

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 (50) hide show
  1. package/package.json +4 -4
  2. package/src/action-prompt.ts +11 -2
  3. package/src/adapters/file-memory.ts +5 -0
  4. package/src/adapters/http-memory.ts +5 -0
  5. package/src/adapters/markdown-docs.ts +12 -1
  6. package/src/adapters/markdown-hierarchy.ts +7 -2
  7. package/src/adapters/sqlite-memory.ts +21 -6
  8. package/src/base-agent.ts +5 -0
  9. package/src/bounded-set.ts +5 -0
  10. package/src/cache-lifecycle.ts +5 -0
  11. package/src/capability.ts +5 -0
  12. package/src/clarifier.ts +5 -0
  13. package/src/classifier.ts +5 -0
  14. package/src/context-layer.ts +10 -0
  15. package/src/context-stack.ts +5 -0
  16. package/src/credentials.ts +5 -0
  17. package/src/decider.ts +5 -0
  18. package/src/delivery-evidence.ts +5 -0
  19. package/src/event-stream.ts +5 -0
  20. package/src/executor.ts +17 -4
  21. package/src/feedback/archive-turns.ts +5 -0
  22. package/src/feedback/correction.ts +48 -2
  23. package/src/feedback/experiment-gate.ts +5 -0
  24. package/src/feedback/review-findings.ts +5 -0
  25. package/src/feedback/warden-labels.ts +5 -0
  26. package/src/feedback/warden-prefilter.ts +5 -0
  27. package/src/feedback/warden-probes.ts +5 -0
  28. package/src/harness.ts +7 -2
  29. package/src/hydrator.ts +5 -0
  30. package/src/id.ts +5 -0
  31. package/src/index.ts +25 -12
  32. package/src/intervention.ts +5 -0
  33. package/src/messages.ts +5 -0
  34. package/src/middleware.ts +5 -0
  35. package/src/model-registry.ts +92 -2
  36. package/src/native-evidence.ts +18 -0
  37. package/src/native-tools.ts +5 -0
  38. package/src/retry.ts +5 -0
  39. package/src/router.ts +5 -0
  40. package/src/scope.ts +5 -0
  41. package/src/serialized-queue.ts +35 -0
  42. package/src/session-delivery.ts +5 -0
  43. package/src/signal.ts +5 -0
  44. package/src/thread.ts +196 -21
  45. package/src/token-counts.ts +5 -0
  46. package/src/token-tracker.ts +49 -24
  47. package/src/tools.ts +6 -1
  48. package/src/trace.ts +5 -0
  49. package/src/types.ts +5 -2
  50. package/src/hooks.ts +0 -133
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/foundry-core",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -30,10 +30,10 @@
30
30
  "access": "public"
31
31
  },
32
32
  "engines": {
33
- "bun": ">=1.4.2"
33
+ "bun": ">=1.4.4"
34
34
  },
35
35
  "peerDependencies": {
36
- "@inixiative/archive": "^0.24.0"
36
+ "@inixiative/archive": "^0.33.0"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@inixiative/archive": {
@@ -41,6 +41,6 @@
41
41
  }
42
42
  },
43
43
  "devDependencies": {
44
- "@inixiative/archive": "^0.24.0"
44
+ "@inixiative/archive": "^0.33.1"
45
45
  }
46
46
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:signals
5
+ */
6
+ import { newId } from './id';
1
7
  // ---------------------------------------------------------------------------
2
8
  // ActionPrompt — blocking agent→human interaction primitive
3
9
  // ---------------------------------------------------------------------------
@@ -30,6 +36,8 @@ export interface ActionResolution {
30
36
  readonly by: 'human' | 'policy' | 'timeout';
31
37
  readonly action: string; // "approved", "rejected", or an option ID
32
38
  readonly input?: string; // for "input" kind prompts
39
+ /** Context the user typed in the message box and sent with this answer. */
40
+ readonly note?: string;
33
41
  readonly timestamp: number;
34
42
  }
35
43
 
@@ -129,7 +137,7 @@ export class ActionQueue {
129
137
  * Returns the resolution (human choice, policy decision, or timeout).
130
138
  */
131
139
  async prompt(opts: PromptOpts): Promise<ActionResolution> {
132
- const id = `prompt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
140
+ const id = newId('prompt');
133
141
 
134
142
  const prompt: ActionPrompt = {
135
143
  id,
@@ -197,7 +205,7 @@ export class ActionQueue {
197
205
  resolve(
198
206
  promptId: string,
199
207
  action: string,
200
- opts?: { by?: 'human' | 'policy'; input?: string },
208
+ opts?: { by?: 'human' | 'policy'; input?: string; note?: string },
201
209
  ): boolean {
202
210
  const prompt = this._prompts.get(promptId);
203
211
  if (!prompt || prompt.status !== 'pending') return false;
@@ -206,6 +214,7 @@ export class ActionQueue {
206
214
  by: opts?.by ?? 'human',
207
215
  action,
208
216
  input: opts?.input,
217
+ ...(opts?.note ? { note: opts.note } : {}),
209
218
  timestamp: Date.now(),
210
219
  };
211
220
 
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind adapter
4
+ * @partOf primitive:memory
5
+ */
1
6
  import { existsSync, mkdirSync } from 'node:fs';
2
7
  import { join, relative, resolve } from 'node:path';
3
8
  import {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind adapter
4
+ * @partOf primitive:memory
5
+ */
1
6
  import type { ContextSource } from '../context-layer';
2
7
  import type { ContextRef, HydrationAdapter } from '../hydrator';
3
8
  import type { Signal } from '../signal';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind adapter
4
+ * @partOf primitive:memory
5
+ */
1
6
  import { existsSync, realpathSync } from 'node:fs';
2
7
  import { realpath, stat } from 'node:fs/promises';
3
8
  import { homedir } from 'node:os';
@@ -288,7 +293,13 @@ export function secretInstructionPath(path: string, trusted: readonly string[] =
288
293
  name.startsWith('.') ||
289
294
  /^id_[a-z0-9]+/i.test(name) ||
290
295
  /\.(pem|key|p12|pfx|keystore|jks)$/i.test(name) ||
291
- rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part))
296
+ rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part)) ||
297
+ // A `.private` path is never read (FEAT-005): a segment named or ending `.private`, or holding `.private.`.
298
+ path
299
+ .split(sep)
300
+ .some(
301
+ (part) => part === '.private' || part.endsWith('.private') || part.includes('.private.'),
302
+ )
292
303
  );
293
304
  }
294
305
 
@@ -1,6 +1,11 @@
1
- import { createHash } from 'node:crypto';
1
+ /**
2
+ * @atlas
3
+ * @kind adapter
4
+ * @partOf feature:composition
5
+ */
2
6
  import { readFileSync, realpathSync, statSync } from 'node:fs';
3
7
  import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
8
+ import { sha256Hex } from '../context-layer';
4
9
  import {
5
10
  fenceSegments,
6
11
  INSTRUCTION_FILE_LIMIT,
@@ -685,7 +690,7 @@ export async function discoverHierarchy(
685
690
  return { files, duplicates, refused, probes: [...probes] };
686
691
  }
687
692
 
688
- const digest = (text: string) => createHash('sha256').update(text).digest('hex');
693
+ const digest = sha256Hex;
689
694
 
690
695
  /**
691
696
  * Blank lines, trailing whitespace, line endings and runs of spaces inside a line do not make two
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind adapter
4
+ * @partOf primitive:memory
5
+ */
1
6
  import { Database } from 'bun:sqlite';
2
7
  import type { ContextSource } from '../context-layer';
3
8
  import type { ContextRef, HydrationAdapter } from '../hydrator';
@@ -71,7 +76,7 @@ export class SqliteMemory {
71
76
 
72
77
  /** Read by id. */
73
78
  get(id: string): SqliteEntry | undefined {
74
- const row = this.db.query('SELECT * FROM entries WHERE id = ?').get(id) as any;
79
+ const row = this.db.query('SELECT * FROM entries WHERE id = ?').get(id) as SqliteRow | null;
75
80
  return row ? this._rowToEntry(row) : undefined;
76
81
  }
77
82
 
@@ -81,7 +86,7 @@ export class SqliteMemory {
81
86
  ? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC')
82
87
  : this.db.query('SELECT * FROM entries ORDER BY timestamp DESC');
83
88
  const rows = kind ? query.all(kind) : query.all();
84
- return (rows as any[]).map(this._rowToEntry);
89
+ return (rows as SqliteRow[]).map(this._rowToEntry);
85
90
  }
86
91
 
87
92
  /** Full-text search. */
@@ -91,7 +96,7 @@ export class SqliteMemory {
91
96
  `SELECT entries.* FROM entries_fts JOIN entries ON entries_fts.id = entries.id WHERE entries_fts MATCH ? ORDER BY rank LIMIT ?`,
92
97
  )
93
98
  .all(query, limit);
94
- return (rows as any[]).map(this._rowToEntry);
99
+ return (rows as SqliteRow[]).map(this._rowToEntry);
95
100
  }
96
101
 
97
102
  /** Delete by id. */
@@ -105,7 +110,7 @@ export class SqliteMemory {
105
110
  const query = kind
106
111
  ? this.db.query('SELECT COUNT(*) as c FROM entries WHERE kind = ?')
107
112
  : this.db.query('SELECT COUNT(*) as c FROM entries');
108
- const row = (kind ? query.get(kind) : query.get()) as any;
113
+ const row = (kind ? query.get(kind) : query.get()) as { c: number };
109
114
  return row.c;
110
115
  }
111
116
 
@@ -115,7 +120,7 @@ export class SqliteMemory {
115
120
  ? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC LIMIT ?')
116
121
  : this.db.query('SELECT * FROM entries ORDER BY timestamp DESC LIMIT ?');
117
122
  const rows = kind ? query.all(kind, limit) : query.all(limit);
118
- return (rows as any[]).map(this._rowToEntry);
123
+ return (rows as SqliteRow[]).map(this._rowToEntry);
119
124
  }
120
125
 
121
126
  /** Create a ContextSource that loads entries as context. */
@@ -168,7 +173,7 @@ export class SqliteMemory {
168
173
  this.db.close();
169
174
  }
170
175
 
171
- private _rowToEntry(row: any): SqliteEntry {
176
+ private _rowToEntry(row: SqliteRow): SqliteEntry {
172
177
  return {
173
178
  id: row.id,
174
179
  kind: row.kind,
@@ -180,6 +185,16 @@ export class SqliteMemory {
180
185
  }
181
186
  }
182
187
 
188
+ /** A row of the `entries` table as bun:sqlite returns it. */
189
+ interface SqliteRow {
190
+ id: string;
191
+ kind: string;
192
+ content: string;
193
+ source: string | null;
194
+ timestamp: number;
195
+ meta: string | null;
196
+ }
197
+
183
198
  export interface SqliteEntry {
184
199
  readonly id: string;
185
200
  readonly kind: string;
package/src/base-agent.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind agent
4
+ * @partOf primitive:agents
5
+ */
1
6
  import { computeHash } from './context-layer';
2
7
  import type { AssembledContext, ContextStack, LayerFilter } from './context-stack';
3
8
  import type { TokenCounts } from './token-counts';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind utils
4
+ * @partOf primitive:signals
5
+ */
1
6
  /**
2
7
  * A set with bounded capacity that evicts oldest entries when full.
3
8
  * Used for message deduplication at harness ingress.
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:contextLayers
5
+ */
1
6
  import type { ContextLayer, LayerState } from './context-layer';
2
7
  import type { ContextStack } from './context-stack';
3
8
 
package/src/capability.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind helper
4
+ * @partOf primitive:agents, primitive:tools
5
+ */
1
6
  // ---------------------------------------------------------------------------
2
7
  // Capability — permission flags and gating for dangerous operations
3
8
  // ---------------------------------------------------------------------------
package/src/clarifier.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind agent
4
+ * @partOf primitive:agents
5
+ */
1
6
  import type { AgentConfig } from './base-agent';
2
7
  import type { Classification } from './classifier';
3
8
  import type { ContextStack } from './context-stack';
package/src/classifier.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind agent
4
+ * @partOf primitive:agents
5
+ */
1
6
  import type { AgentConfig } from './base-agent';
2
7
  import type { ContextStack } from './context-stack';
3
8
  import { Decider, type DeciderConfig, type Decision } from './decider';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:contextLayers
5
+ */
1
6
  import type { OwnershipScope } from './scope';
2
7
 
3
8
  /**
@@ -354,6 +359,11 @@ export function computeHash(content: string): string {
354
359
  return Bun.hash(content).toString(16).slice(0, 16);
355
360
  }
356
361
 
362
+ /** SHA-256 as hex: for content addresses and digests that are persisted or compared across processes (computeHash is a fast in-memory cache key, not this). */
363
+ export function sha256Hex(content: string | Uint8Array): string {
364
+ return new Bun.CryptoHasher('sha256').update(content).digest('hex');
365
+ }
366
+
357
367
  export class ContextLayer {
358
368
  readonly id: string;
359
369
  readonly owner?: OwnershipScope;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:contextLayers
5
+ */
1
6
  import { type ContextLayer, computeHash, type LayerState } from './context-layer';
2
7
  import type { OwnershipScope } from './scope';
3
8
 
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind helper
4
+ * @partOf primitive:secrets
5
+ */
1
6
  /** Credential references belong in configuration; secrets stay behind the resolver. */
2
7
  /** `kingdom-signet` names the Signet a paired Kingdom granted, by the destination's origin plus the owner it was paired as. */
3
8
  export type CredentialReference =
package/src/decider.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind agent
4
+ * @partOf primitive:agents
5
+ */
1
6
  import { type AgentConfig, BaseAgent, type ExecutionResult } from './base-agent';
2
7
  import { computeHash } from './context-layer';
3
8
  import type { ContextStack, LayerFilter } from './context-stack';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind helper
4
+ * @partOf primitive:nativeSessions
5
+ */
1
6
  /** Portable SHA-256 for public evidence, shared by Bun and the historical browser.
2
7
  * This is an integrity join, not a signature or native delivery acknowledgment. */
3
8
  export function evidenceDigest(text: string): string {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:signals
5
+ */
1
6
  import type { ActionPrompt } from './action-prompt';
2
7
  import type { LifecycleEvent } from './cache-lifecycle';
3
8
  import type { DispatchContext } from './middleware';
package/src/executor.ts CHANGED
@@ -1,6 +1,11 @@
1
+ /**
2
+ * @atlas
3
+ * @kind agent
4
+ * @partOf primitive:agents
5
+ */
1
6
  import { type AgentConfig, BaseAgent, type ExecutionResult } from './base-agent';
2
7
  import { computeHash, REQUIRED_CONTEXT_BLOCKED } from './context-layer';
3
- import type { AssembledContext, ContextStack, LayerFilter } from './context-stack';
8
+ import type { AssembledContext, ContextStack, LayerFilter, PromptBlock } from './context-stack';
4
9
  import { buildInjectionArtifact, type InjectionArtifact, type MessageDecoration } from './messages';
5
10
  import type { NativeEvidence, NativeObservation } from './native-evidence';
6
11
  import {
@@ -52,6 +57,16 @@ export interface ExecuteMeta {
52
57
  images?: readonly ImageInput[];
53
58
  }
54
59
 
60
+ /**
61
+ * The user's message as a turn sends it: the after-message notes (Wardens chiming in) appended to
62
+ * its end below a rule, each note's text in order. No notes: the message as typed.
63
+ */
64
+ export function withAfterMessageNotes(message: string, notes: readonly PromptBlock[]): string {
65
+ return notes.length
66
+ ? `${message}\n\n---\n\n${notes.map((note) => note.text).join('\n\n')}`
67
+ : message;
68
+ }
69
+
55
70
  export type ExecuteHandler<TPayload, TResult> = (
56
71
  context: string,
57
72
  payload: TPayload,
@@ -214,9 +229,7 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<T
214
229
  let recording = true;
215
230
  try {
216
231
  const sent = (
217
- notes.length && typeof payload === 'string'
218
- ? `${payload}\n\n---\n\n${notes.map((note) => note.text).join('\n\n')}`
219
- : payload
232
+ typeof payload === 'string' ? withAfterMessageNotes(payload, notes) : payload
220
233
  ) as TPayload;
221
234
  output = await this._handler(context, sent, {
222
235
  ...meta,
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind transformer
4
+ * @partOf feature:feedback, integration:archive
5
+ */
1
6
  import type { ArchiveSnapshot } from '@inixiative/archive';
2
7
  import { typedPrompt } from '@inixiative/archive/import';
3
8
  import type { TranscriptTurn } from './correction';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf feature:feedback
5
+ */
1
6
  import { createHash } from 'node:crypto';
2
7
 
3
8
  // The Prospector's correction detection (moved from Oracle's session miner), shared by Oracle's
@@ -216,8 +221,12 @@ export function extractCorrections(
216
221
  return { corrections, secretsScrubbed };
217
222
  }
218
223
 
219
- const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
220
- [/sk-ant-[A-Za-z0-9_-]{8,}/g, '[REDACTED:anthropic-key]'],
224
+ /**
225
+ * Credentials by their own shape (a provider's token prefix, a key block): specific enough that a
226
+ * match is a secret. Foundry lifts these from a message even when the person forgot `$$` (FEAT-005).
227
+ */
228
+ const SHAPED_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
229
+ [/sk-ant-[A-Za-z0-9_-]{20,}/g, '[REDACTED:anthropic-key]'],
221
230
  [/sk-[A-Za-z0-9_-]{20,}/g, '[REDACTED:api-key]'],
222
231
  [/gh[pousr]_[A-Za-z0-9]{20,}/g, '[REDACTED:github-token]'],
223
232
  [/github_pat_[A-Za-z0-9_]{20,}/g, '[REDACTED:github-token]'],
@@ -231,8 +240,19 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
231
240
  [/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, '[REDACTED:jwt]'],
232
241
  [/\b[a-z]+_(?:live|test|prod|runtime)_[A-Za-z0-9]{16,}/g, '[REDACTED:token]'],
233
242
  [/\b(?:npm_|glpat-|hf_)[A-Za-z0-9_-]{20,}/g, '[REDACTED:token]'],
243
+ // Kingdom's run, refresh and Signet access tokens: the prefix and exactly 43 base64url characters.
244
+ [/\bkingdom_(?:run_|refresh_)?[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/g, '[REDACTED:kingdom-token]'],
245
+ // API tokens Kingdom, template and Tribe issue: `<environment>_<owner model>_<48 hex>`.
246
+ [
247
+ /\b(?:local|pr|staging|prod|test)_(?:user|org|orgUser|space|spaceUser|integration)_[0-9a-f]{48}\b/g,
248
+ '[REDACTED:api-token]',
249
+ ],
234
250
  [/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g, '[REDACTED:slack-webhook]'],
235
251
  [/\bASIA[A-Z0-9]{16}/g, '[REDACTED:aws-key]'],
252
+ ];
253
+
254
+ /** Credentials by context (an assignment, a header, a URL): a match may be a secret, so it is scrubbed but never lifted. */
255
+ const CONTEXT_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
236
256
  [/\b(Bearer|Basic|DPoP)\s+[A-Za-z0-9._~+/=-]{16,}/g, '$1 [REDACTED:credential]'],
237
257
  [/\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]+@/gi, '$1[REDACTED:password]@'],
238
258
  [
@@ -245,6 +265,32 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
245
265
  ],
246
266
  ];
247
267
 
268
+ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
269
+ ...SHAPED_SECRETS,
270
+ ...CONTEXT_SECRETS,
271
+ ];
272
+
273
+ /**
274
+ * The shaped credentials safe to lift from a message the person forgot to mark: each never starts
275
+ * inside a word (`task-runner-…` holds `sk-…`), and the two loosest shapes (`sk-…` and
276
+ * `<word>_live_<…>`, which long hyphenated or snake_case names also fit) must carry a digit. Fresh
277
+ * regex objects, so lifting never shares match state with `scrubSecrets`. Scrubbing a false match is
278
+ * harmless; lifting one would take words out of the person's message.
279
+ */
280
+ export const SHAPED_SECRET_PATTERNS: readonly RegExp[] = SHAPED_SECRETS.map(([pattern]) => {
281
+ const loose =
282
+ pattern.source.startsWith('sk-[') || pattern.source.includes('(?:live|test|prod|runtime)');
283
+ const digit = loose ? '(?=[A-Za-z0-9_-]*\\d)' : '';
284
+ return new RegExp(`(?<![A-Za-z0-9_-])${digit}(?:${pattern.source})`, pattern.flags);
285
+ });
286
+
287
+ /** How many credentials known only by context (`PASSWORD=…`, `Bearer …`, a URL password) the text holds. */
288
+ export function contextSecretHits(text: string): number {
289
+ let hits = 0;
290
+ for (const [pattern] of CONTEXT_SECRETS) hits += [...text.matchAll(pattern)].length;
291
+ return hits;
292
+ }
293
+
248
294
  /** Replace credential-shaped substrings. Transcripts are exactly where leaked keys live. */
249
295
  export function scrubSecrets(text: string): { text: string; hits: number } {
250
296
  let hits = 0;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind helper
4
+ * @partOf feature:evals
5
+ */
1
6
  /**
2
7
  * The experiment gate (`.claude/skills/experiment/SKILL.md`), shared by Foundry's eval scripts and
3
8
  * Oracle's replay runner: a wide run (more than TINY_MOMENTS moments, or whatever else the caller
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf feature:feedback
5
+ */
1
6
  import {
2
7
  type Authorship,
3
8
  CONCEPT_CLUSTERS,
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind type
4
+ * @partOf feature:evals
5
+ */
1
6
  import type { WardenProbe } from './warden-probes';
2
7
 
3
8
  /** One Warden of the roster as the labeler sees it. */
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf feature:wardens
5
+ */
1
6
  // The Warden pre-filter: one cheap local yes/no per Warden before it is asked. Data and pure
2
7
  // functions only, so Oracle scores runs the same way; Foundry's provider makes the call. It fails
3
8
  // open: a Warden is skipped only on a confident no, and anything else (an unsure score, an error, no
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf feature:evals
5
+ */
1
6
  // Warden probes: the offline measurement of how well a Warden charter keeps the chorus quiet, in
2
7
  // scope and right. Data and pure functions only, so Oracle can score runs on a core-only dependency;
3
8
  // callers bring the probes, the Warden calls and the judge call.
package/src/harness.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:agents
5
+ */
1
6
  import type { ExecutionResult } from './base-agent';
2
7
  import { BoundedSet } from './bounded-set';
3
8
  import type { ClarificationResult, ClarifyPayload } from './clarifier';
@@ -512,7 +517,7 @@ export class Harness {
512
517
  const layerFilter = this.buildLayerFilter(undefined, undefined, requestedLayers);
513
518
  const settled = await Promise.allSettled(
514
519
  decisionStages.map(async (stage) => {
515
- const span = trace.startDetached(`${stage.role}:${stage.agentId}`, stage.role as any, {
520
+ const span = trace.startDetached(`${stage.role}:${stage.agentId}`, stage.role, {
516
521
  agentId: stage.agentId,
517
522
  threadId: this.thread.id,
518
523
  input: message.payload,
@@ -638,7 +643,7 @@ export class Harness {
638
643
  }
639
644
 
640
645
  // Trace and dispatch (foreground — blocks pipeline)
641
- const stageSpan = trace.start(`${stage.role}:${agentId}`, stage.role as any, {
646
+ const stageSpan = trace.start(`${stage.role}:${agentId}`, stage.role, {
642
647
  agentId,
643
648
  threadId: this.thread.id,
644
649
  input: message.payload,
package/src/hydrator.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:contextLayers
5
+ */
1
6
  import type { ContextSource } from './context-layer';
2
7
 
3
8
  /**
package/src/id.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind utils
4
+ * @partOf primitive:thread
5
+ */
1
6
  import { uuidv7 } from 'uuidv7';
2
7
 
3
8
  /**