@aiwg/cli 2026.10.10 → 2026.10.12

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 (156) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +78 -14
  2. package/agentic/code/providers/model-catalog.v1.json +62 -12
  3. package/dist/src/artifacts/binding-target-identity.js +51 -0
  4. package/dist/src/artifacts/checksum-manifest.js +12 -0
  5. package/dist/src/artifacts/index-builder.js +11 -13
  6. package/dist/src/artifacts/query-engine.js +5 -11
  7. package/dist/src/cli/handlers/doctor-help.js +32 -0
  8. package/dist/src/cli/handlers/gates.js +19 -18
  9. package/dist/src/cli/handlers/models-help.js +7 -1
  10. package/dist/src/cli/handlers/models.js +3 -3
  11. package/dist/src/cli/handlers/script-runner.js +8 -5
  12. package/dist/src/cli/handlers/sessions-intake.js +104 -0
  13. package/dist/src/cli/handlers/sessions-tail.js +172 -0
  14. package/dist/src/cli/handlers/sessions.js +287 -31
  15. package/dist/src/cli/handlers/skill-lint.js +10 -3
  16. package/dist/src/cli/handlers/steward-format.js +100 -0
  17. package/dist/src/cli/handlers/steward-models.js +167 -0
  18. package/dist/src/cli/handlers/steward.js +14 -248
  19. package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
  20. package/dist/src/cli/handlers/use.js +18 -28
  21. package/dist/src/cli/handlers/utilities.js +1 -24
  22. package/dist/src/config/aiwg-config.js +7 -0
  23. package/dist/src/config/authorization-cli.js +89 -0
  24. package/dist/src/config/cli-help.js +1 -0
  25. package/dist/src/config/cli.js +10 -1
  26. package/dist/src/config/worker-context.js +337 -0
  27. package/dist/src/decision/context-pruning.js +3 -2
  28. package/dist/src/decision/ensemble/contract.js +15 -4
  29. package/dist/src/decision/ensemble-study/artifacts.js +5 -0
  30. package/dist/src/decision/ensemble-study/score.js +4 -3
  31. package/dist/src/decision/feature-export.js +3 -9
  32. package/dist/src/decision/heldout/collector.js +2 -2
  33. package/dist/src/decision/issue-triage/runtime.js +3 -9
  34. package/dist/src/decision/qualification/gates.js +2 -1
  35. package/dist/src/decision/qualification/release.js +2 -3
  36. package/dist/src/decision/routing/report.js +2 -0
  37. package/dist/src/decision/sdlc-screening.js +3 -5
  38. package/dist/src/decision/sensitivity/comparative.js +5 -3
  39. package/dist/src/gates/driver.js +6 -77
  40. package/dist/src/gates/evaluate.js +4 -11
  41. package/dist/src/gates/floors.js +4 -4
  42. package/dist/src/gates/index.js +3 -3
  43. package/dist/src/gates/lattice.js +26 -0
  44. package/dist/src/gates/registry.js +2 -1
  45. package/dist/src/gates/report.js +19 -7
  46. package/dist/src/issues/audit-cli.js +120 -0
  47. package/dist/src/issues/audit-contract.js +64 -0
  48. package/dist/src/issues/audit-local.js +82 -0
  49. package/dist/src/issues/audit.js +220 -0
  50. package/dist/src/lint/rule-activation.js +15 -1
  51. package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
  52. package/dist/src/mission-protocol/canonical-codecs.js +270 -0
  53. package/dist/src/mission-protocol/codecs.js +22 -550
  54. package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
  55. package/dist/src/mission-protocol/health-trace-types.js +43 -0
  56. package/dist/src/mission-protocol/types.js +2 -42
  57. package/dist/src/mission-protocol/worker-contract-json.js +10 -0
  58. package/dist/src/mission-protocol/worker-review-gate.js +100 -0
  59. package/dist/src/mission-protocol/worker-review-types.js +2 -0
  60. package/dist/src/models/catalog-policy.js +280 -0
  61. package/dist/src/models/index.js +1 -1
  62. package/dist/src/models/model-capabilities.v1.json +78 -14
  63. package/dist/src/models/model-catalog.v1.json +62 -12
  64. package/dist/src/models/policy-resources.js +28 -0
  65. package/dist/src/models/provider-policy.js +10 -539
  66. package/dist/src/models/resolver.js +26 -8
  67. package/dist/src/models/router.js +15 -2
  68. package/dist/src/models/tiers.js +13 -0
  69. package/dist/src/models/types.js +0 -6
  70. package/dist/src/models/worker-ceilings.js +59 -0
  71. package/dist/src/models/worker-invocation-policy.js +243 -0
  72. package/dist/src/models/worker-plan.js +16 -20
  73. package/dist/src/models/worker-review-lineage.js +55 -0
  74. package/dist/src/models/worker-selection-policy.js +1 -1
  75. package/dist/src/models/wrapper-route.js +6 -3
  76. package/dist/src/policy/authorization.js +30 -1
  77. package/dist/src/policy/hitl-config.js +63 -0
  78. package/dist/src/policy/hitl-receipt.js +57 -0
  79. package/dist/src/policy/hitl-types.js +4 -0
  80. package/dist/src/policy/hitl-validation.js +39 -0
  81. package/dist/src/policy/hitl.js +117 -0
  82. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  83. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  84. package/dist/src/rules/activation.d.mts +1 -1
  85. package/dist/src/rules/activation.mjs +0 -2
  86. package/dist/src/rules/gate-coverage.js +51 -0
  87. package/dist/src/rules/gate-references.js +16 -0
  88. package/dist/src/rules/rule-frontmatter.schema.json +7 -1
  89. package/dist/src/serve/worker-command-request.js +21 -4
  90. package/dist/src/serve/worker-invocation-preparation.js +3 -1
  91. package/dist/src/serve/worker-invocation.js +21 -10
  92. package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
  93. package/dist/src/sessions/adapters/grok-build-native.js +269 -0
  94. package/dist/src/sessions/adapters/grok-build.js +57 -15
  95. package/dist/src/sessions/adapters/grokbot.js +7 -0
  96. package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
  97. package/dist/src/sessions/adapters/hermes.js +42 -3
  98. package/dist/src/sessions/adapters/warp.js +1 -1
  99. package/dist/src/sessions/batch-import.js +44 -6
  100. package/dist/src/sessions/contracts.js +20 -1
  101. package/dist/src/sessions/coverage-preflight.js +7 -1
  102. package/dist/src/sessions/discovery-capability.js +38 -0
  103. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  104. package/dist/src/sessions/host-catalog.js +157 -0
  105. package/dist/src/sessions/image-blobs.js +141 -0
  106. package/dist/src/sessions/importer.js +9 -232
  107. package/dist/src/sessions/index.js +6 -0
  108. package/dist/src/sessions/intake-dir.js +71 -0
  109. package/dist/src/sessions/normalization.js +251 -0
  110. package/dist/src/sessions/refresh.js +25 -2
  111. package/dist/src/sessions/repository.js +164 -22
  112. package/dist/src/sessions/tail.js +91 -0
  113. package/dist/src/sessions/workspace-discovery.js +88 -9
  114. package/dist/src/skills/agent-skill-sidecar.js +28 -0
  115. package/dist/src/skills/agent-skills.js +4 -23
  116. package/dist/src/skills/claude-surface-projection.js +25 -0
  117. package/dist/src/skills/deployer.js +8 -288
  118. package/dist/src/skills/deployment-policy.js +114 -0
  119. package/dist/src/skills/deployment-projection.js +152 -0
  120. package/dist/src/skills/deployment-types.js +20 -0
  121. package/dist/src/skills/exporter.js +8 -31
  122. package/dist/src/skills/provider-extensions.js +41 -0
  123. package/dist/src/skills/run.js +6 -0
  124. package/dist/src/skills/types.js +0 -8
  125. package/dist/src/skills/validator-resources.js +68 -0
  126. package/dist/src/skills/validator.js +6 -55
  127. package/dist/src/storage/cli-migrate-options.js +125 -0
  128. package/dist/src/storage/cli-migrate.js +111 -0
  129. package/dist/src/storage/cli.js +4 -279
  130. package/dist/src/storage/migration-adapter.js +79 -0
  131. package/dist/src/storage/migration-copy.js +108 -0
  132. package/dist/src/storage/migration-protocol.js +14 -197
  133. package/dist/src/storage/migration-records.js +186 -0
  134. package/dist/src/storage/migration-types.js +12 -0
  135. package/dist/src/tracker/forge-http.js +2 -1
  136. package/dist/src/tracker/issue-pages.js +110 -0
  137. package/package.json +7 -1
  138. package/schemas/mission-v1.schema.json +335 -0
  139. package/schemas/models/model-catalog.v1.schema.json +19 -1
  140. package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
  141. package/schemas/models/model-policy.v1.schema.json +1 -1
  142. package/tools/agents/providers/provider-paths-staging.mjs +35 -0
  143. package/tools/agents/providers/rule-binding-targets.mjs +146 -0
  144. package/tools/agents/providers/rule-delivery.mjs +7 -126
  145. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  146. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  147. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  148. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  149. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  150. package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
  151. package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
  152. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  153. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  154. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  155. package/tools/ralph-external/session-launcher.mjs +51 -96
  156. package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
@@ -3,11 +3,13 @@ import { randomUUID } from 'node:crypto';
3
3
  import { basename, dirname, isAbsolute, join, resolve, } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import { providerScopeWarnings } from '../../sessions/coverage-preflight.js';
6
- import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, GROKBOT_ADAPTER_VERSION, GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, GrokbotSessionAdapter, GROK_BUILD_ADAPTER_VERSION, GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS, GrokBuildSessionAdapter, grokBuildList, grokBuildSearch, grokBuildExport, grokHeadlessSessionIdFromFile, MUSE_ADAPTER_VERSION, MuseSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, createSessionAdapterRegistry, coveragePreflight, expectedSessionProviders, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
6
+ import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, GROKBOT_ADAPTER_VERSION, GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, GrokbotSessionAdapter, GROK_BUILD_ADAPTER_VERSION, GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS, GROK_BUILD_NATIVE_LOCATOR_CLASS, GrokBuildSessionAdapter, grokBuildList, grokBuildSearch, grokBuildExport, grokHeadlessSessionIdFromFile, MUSE_ADAPTER_VERSION, MuseSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, countUniqueSessionsAcrossCatalogs, defaultDiscoveryManifestPath, defaultHostCatalogPath, HOST_CATALOG_WORKSPACE_ID, indexIdentitiesBySessionId, manifestNeedsHostCatalog, mergeCrossCatalogRows, mergeSessionLists, resolveHostReferences, summarizeSessionAnalyticsFacts, discoverWorkspaceHistories, createSessionAdapterRegistry, providerDiscoveryCapability, coveragePreflight, expectedSessionProviders, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
7
7
  import { resolveImportExclusions } from '../../sessions/import-exclusions.js';
8
8
  import { resolveImportLimits } from '../../sessions/import-limits.js';
9
9
  import { classifyGrokbotBlobName } from '../../sessions/adapters/grokbot-locator.js';
10
10
  import { readAiwgConfig } from '../../config/aiwg-config.js';
11
+ import { importFromDir } from './sessions-intake.js';
12
+ import { executeSessionsTail } from './sessions-tail.js';
11
13
  const JSON_CONTRACT_VERSION = '1.0.0';
12
14
  async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
13
15
  const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
@@ -38,9 +40,13 @@ Commands:
38
40
  grok-export <id> --workspace <path> --out <dir> Export one CLI transcript
39
41
  grok-id <file> --format json|streaming-json Read a headless session ID
40
42
  import <file> --source-id <id> Import a supported provider JSONL source
43
+ import --from-dir <dir> [--source-id <prefix>] Batch-import every recognised export in a directory
41
44
  list [--workspace <path-or-id>] [--limit N] [--cursor N] List normalized sessions
42
45
  timeline [--workspace <path-or-id>] [--gap 30m] Report chronological activity segments
43
46
  show <session-id> [--workspace <path-or-id>] Show a session with events and tags
47
+ tail <session-id> | --provider <p> --latest [--workspace <path-or-id>] [--follow] [--json] [--import]
48
+ Read new native events; --follow polls every 500 ms
49
+ Read-only unless --import; --input <file> authorizes older direct imports
44
50
  search <query> [--workspace <path-or-id>] Search authorized normalized content
45
51
  extract [session-id] --workspace <id> Extract structural candidates
46
52
  export plan --workspace <id> --out <file> <session-id...> Select sessions into a reviewable plan
@@ -81,6 +87,9 @@ Options:
81
87
  --muse-root <path> Explicitly authorize a Muse Code sessions root
82
88
  --dsh-root <path> Explicitly authorize a DeepSeek Harness sessions root
83
89
  --grokbot-root <path> Explicitly authorize a Grok Bot sand-client-persistence root
90
+ --grok-root <path> Explicitly authorize a Grok Build sessions root (native chat_history.jsonl discovery)
91
+ --grok-active-sessions <path> Explicitly authorize active_sessions.json for Grok Build live detection (read-only; requires --grok-root)
92
+ --hermes-root <path> Explicitly authorize a Hermes home for a read-only state.db backup snapshot
84
93
  --grok-bin <path> Absolute Grok Build CLI path for grok-list/grok-search/grok-export
85
94
  (default: grok on PATH, then $GROK_HOME/bin/grok)
86
95
  --confirm, --yes Confirm a persistent discovered batch import
@@ -92,6 +101,9 @@ Options:
92
101
  --inactivity-threshold <duration> Historical inactivity threshold (default 24h)
93
102
  --min-coverage <0..1> Fail list/report commands below a coverage ratio
94
103
  --provider <id> Filter list or select an import adapter
104
+ --from-dir <dir> Batch-import every recognised export file in a directory (import only);
105
+ --source-id becomes an optional per-file source-ID prefix;
106
+ files already imported are skipped by content digest
95
107
  --consumer <id> Select a named memory consumer for promotion
96
108
  --tag <tag>, --limit <n>, --cursor <n>
97
109
  --page-size <n> Extraction scan page size (default 250, maximum 500)
@@ -156,6 +168,8 @@ export const sessionsHandler = {
156
168
  const explicitHelp = parsed.flags.has('--help') || parsed.flags.has('-h');
157
169
  return printHelp(ctx, explicitHelp ? EXIT.ok : EXIT.usage);
158
170
  }
171
+ if (parsed.command === 'tail')
172
+ return executeSessionsTail(ctx, parsed, { databasePath, readAuthorizationContext });
159
173
  try {
160
174
  const result = await executeCommand(ctx, parsed);
161
175
  if (json)
@@ -179,7 +193,10 @@ async function executeCommand(ctx, args) {
179
193
  const command = args.command;
180
194
  if (command === 'sources') {
181
195
  const registry = createSessionAdapterRegistry();
182
- const reports = registry.list().map((adapter) => providerDisposition(adapter.provider));
196
+ const reports = registry.list().map((adapter) => ({
197
+ ...providerDisposition(adapter.provider),
198
+ discovery: providerDiscoveryCapability(adapter.provider, adapter.supportedOperations),
199
+ }));
183
200
  const preflight = coveragePreflight({ expectedProviders: requestedProviders(args), registry });
184
201
  return ok(command, { providers: reports, count: reports.length, preflight });
185
202
  }
@@ -227,18 +244,35 @@ async function executeCommand(ctx, args) {
227
244
  limit,
228
245
  cursor,
229
246
  });
247
+ // Account-scoped sessions live once in the host catalog; the
248
+ // workspace holds references (#3232). Read through them on the
249
+ // opening page so the workspace lists its referenced sessions.
250
+ // Cursor pages continue the local snapshot. Totals dedupe legacy
251
+ // copied rows against references, counting each session once.
252
+ const hostView = cursor
253
+ ? null
254
+ : readHostReferencedSessions(repository, { provider, tag: args.values.get('--tag') });
255
+ const items = hostView && hostView.sessions.length > 0
256
+ ? mergeSessionLists(result.items, hostView.sessions).slice(0, limit)
257
+ : result.items;
258
+ const total = hostView && hostView.references.length > 0
259
+ ? countUniqueSessionsAcrossCatalogs([{
260
+ identities: repository.listSessionIdentities(workspaceId),
261
+ references: hostView.resolvedReferences,
262
+ }])
263
+ : result.total;
230
264
  const coverage = {
231
265
  ...repository.getCoverage(workspaceId),
232
266
  catalogScope: catalogScope(ctx, args, repository, workspaceId),
233
267
  };
234
268
  const response = {
235
- items: result.items,
269
+ items,
236
270
  page: {
237
271
  limit,
238
272
  cursor: cursor ?? null,
239
273
  nextCursor: result.nextCursor,
240
274
  snapshotRowid: result.snapshotRowid,
241
- total: result.total,
275
+ total,
242
276
  },
243
277
  coverage,
244
278
  };
@@ -314,10 +348,15 @@ async function executeCommand(ctx, args) {
314
348
  const session = matches[0];
315
349
  if (!session)
316
350
  throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}${scope.hint ? `; ${scope.hint}` : ''}`, EXIT.unavailable, { catalogScope: scope });
351
+ // Image references resolve against the blob store: available blobs
352
+ // report their reference, missing ones an explicit unavailable
353
+ // state. Bytes stay out of the response; fetch by digest (#3265).
354
+ const events = repository.listEvents(session.sessionId, session.workspaceId)
355
+ .map((event) => ({ ...event, images: repository.resolveEventImages(event) }));
317
356
  return ok(command, {
318
357
  session,
319
358
  tags: repository.listTags(session.sessionId, session.workspaceId),
320
- events: repository.listEvents(session.sessionId, session.workspaceId),
359
+ events,
321
360
  catalogScope: catalogScope(ctx, args, repository, session.workspaceId),
322
361
  });
323
362
  }
@@ -326,7 +365,8 @@ async function executeCommand(ctx, args) {
326
365
  const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
327
366
  const providerInput = args.values.get('--provider');
328
367
  const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
329
- const result = repository.search({
368
+ const limit = boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit');
369
+ const searchOptions = {
330
370
  query,
331
371
  workspaceId,
332
372
  providers: provider ? [provider] : undefined,
@@ -341,13 +381,24 @@ async function executeCommand(ctx, args) {
341
381
  sensitivity: args.values.get('--sensitivity'),
342
382
  extractionState: args.values.get('--extraction-state'),
343
383
  controlEvents: controlEventMode(args.values.get('--control-events')),
344
- limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
384
+ limit,
345
385
  cursor: args.values.get('--cursor'),
346
- });
386
+ };
387
+ const result = repository.search(searchOptions);
388
+ // Read through host references on the opening page so workspace
389
+ // search finds referenced account-scoped sessions. Each session
390
+ // counts once: host hits for sessions already present locally
391
+ // (legacy copied rows) are dropped by provider + native session.
392
+ const hostRead = searchOptions.cursor
393
+ ? null
394
+ : searchHostReferencedSessions(repository, searchOptions, provider);
395
+ const items = hostRead && hostRead.hits.length > 0
396
+ ? mergeCrossCatalogRows(result.items, hostRead.hits, indexIdentitiesBySessionId(repository.listSessionIdentities(workspaceId)), indexIdentitiesBySessionId(hostRead.hostIdentities), (hit) => [hit.provider, hit.eventId].join(' ')).slice(0, limit)
397
+ : result.items;
347
398
  return ok(command, {
348
- items: result.items,
399
+ items,
349
400
  catalogScope: catalogScope(ctx, args, repository, workspaceId),
350
- page: { limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
401
+ page: { limit,
351
402
  nextCursor: result.nextCursor },
352
403
  });
353
404
  }
@@ -355,14 +406,22 @@ async function executeCommand(ctx, args) {
355
406
  const view = requiredPositional(args, 0, 'analytics view');
356
407
  const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
357
408
  const query = analyticsQuery(args, workspaceId);
409
+ // Host references resolve to facts stored under the host catalog;
410
+ // each session counts once by provider + native session id. Without
411
+ // references the single-catalog path below is unchanged.
412
+ const mergedFacts = readWorkspaceAnalyticsFacts(repository, query);
358
413
  if (view === 'summary') {
359
414
  return ok(command, {
360
415
  view,
361
- summary: repository.analyticsSummary(query),
416
+ summary: mergedFacts
417
+ ? summarizeSessionAnalyticsFacts(mergedFacts, workspaceId)
418
+ : repository.analyticsSummary(query),
362
419
  });
363
420
  }
364
421
  const categories = analyticsCategories(view);
365
- const items = repository.listAnalyticsFacts({ ...query, categories });
422
+ const items = mergedFacts
423
+ ? mergedFacts.filter((fact) => categories.includes(fact.category))
424
+ : repository.listAnalyticsFacts({ ...query, categories });
366
425
  return ok(command, {
367
426
  analyticsVersion: '1.0.0',
368
427
  view,
@@ -752,10 +811,7 @@ async function executeCommand(ctx, args) {
752
811
  repository.close();
753
812
  }
754
813
  }
755
- async function importSource(ctx, args) {
756
- const limits = await configuredImportLimits(ctx, args);
757
- const input = resolve(ctx.cwd, requiredPositional(args, 0, 'file'));
758
- const providerInput = args.values.get('--provider') ?? 'generic';
814
+ function assertSupportedImportProvider(providerInput) {
759
815
  const provider = assertSessionProviderId(providerInput);
760
816
  if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
761
817
  && provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
@@ -763,6 +819,57 @@ async function importSource(ctx, args) {
763
819
  && provider !== 'openhuman' && provider !== 'grokbot' && provider !== 'grok-build' && provider !== 'muse' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
764
820
  throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
765
821
  }
822
+ return provider;
823
+ }
824
+ function adapterVersionFor(provider) {
825
+ switch (provider) {
826
+ case 'claude': return CLAUDE_ADAPTER_VERSION;
827
+ case 'codex': return CODEX_ADAPTER_VERSION;
828
+ case 'copilot': return COPILOT_ADAPTER_VERSION;
829
+ case 'cursor': return CURSOR_ADAPTER_VERSION;
830
+ case 'factory': return FACTORY_ADAPTER_VERSION;
831
+ case 'hermes': return HERMES_ADAPTER_VERSION;
832
+ case 'opencode': return OPENCODE_ADAPTER_VERSION;
833
+ case 'openclaw': return OPENCLAW_ADAPTER_VERSION;
834
+ case 'openhuman': return OPENHUMAN_ADAPTER_VERSION;
835
+ case 'grokbot': return GROKBOT_ADAPTER_VERSION;
836
+ case 'grok-build': return GROK_BUILD_ADAPTER_VERSION;
837
+ case 'muse': return MUSE_ADAPTER_VERSION;
838
+ case 'pi': return PI_ADAPTER_VERSION;
839
+ case 'omp': return OMP_ADAPTER_VERSION;
840
+ case 'deepseek-harness': return DEEPSEEK_HARNESS_ADAPTER_VERSION;
841
+ case 'warp': return WARP_ADAPTER_VERSION;
842
+ case 'devin-desktop': return DEVIN_DESKTOP_ADAPTER_VERSION;
843
+ default: return GENERIC_ADAPTER_VERSION;
844
+ }
845
+ }
846
+ /**
847
+ * `aiwg sessions import --from-dir <dir>` (#3168). Batch-imports every
848
+ * recognised provider export file in one intake directory: one receipt per
849
+ * file plus a summary. Files already imported are skipped by content digest
850
+ * before any adapter work; unrecognised files are skipped with a reason.
851
+ * Each remaining file imports through the single-file path, so adapter
852
+ * selection, redaction, and idempotency semantics are identical.
853
+ */
854
+ function importFailureReason(error) {
855
+ if (error instanceof CliError)
856
+ return `${error.code}: ${error.message}`;
857
+ if (error instanceof SessionContractError)
858
+ return `${error.code}: ${error.message}`;
859
+ return `IMPORT_INTERRUPTED: ${error instanceof Error ? error.message : String(error)}`;
860
+ }
861
+ async function importSource(ctx, args) {
862
+ const fromDir = args.values.get('--from-dir');
863
+ if (fromDir !== undefined) {
864
+ return importFromDir(ctx, args, resolve(ctx.cwd, fromDir), {
865
+ assertSupportedImportProvider, normalizeWorkspaceId, isDryRun, preview, ok,
866
+ openRepository, adapterVersionFor, importSource, importFailureReason,
867
+ });
868
+ }
869
+ const limits = await configuredImportLimits(ctx, args);
870
+ const input = resolve(ctx.cwd, requiredPositional(args, 0, 'file'));
871
+ const providerInput = args.values.get('--provider') ?? 'generic';
872
+ const provider = assertSupportedImportProvider(providerInput);
766
873
  const sourceId = requiredValue(args, '--source-id');
767
874
  const workspaceId = args.values.has('--workspace')
768
875
  ? normalizeWorkspaceId(ctx.cwd, args.values.get('--workspace'))
@@ -780,6 +887,8 @@ async function importSource(ctx, args) {
780
887
  const isGrokbotNative = isGrokbot
781
888
  && classifyGrokbotBlobName(basename(input))?.kind === 'transcript-replica';
782
889
  const isGrokBuild = provider === 'grok-build';
890
+ const isGrokBuildNative = isGrokBuild
891
+ && basename(input) === 'chat_history.jsonl';
783
892
  const isMuse = provider === 'muse';
784
893
  const isPi = provider === 'pi';
785
894
  const isOmp = provider === 'omp';
@@ -838,7 +947,7 @@ async function importSource(ctx, args) {
838
947
  : isGrokbot
839
948
  ? (isGrokbotNative ? GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS : 'manual-export')
840
949
  : isGrokBuild
841
- ? GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS
950
+ ? (isGrokBuildNative ? GROK_BUILD_NATIVE_LOCATOR_CLASS : GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS)
842
951
  : isMuse
843
952
  ? 'manual-export'
844
953
  : isPi
@@ -877,7 +986,7 @@ async function importSource(ctx, args) {
877
986
  : isGrokbot
878
987
  ? (isGrokbotNative ? 'grokbot-native-transcript-replica-v1' : 'manual-interchange')
879
988
  : isGrokBuild
880
- ? 'documented-cli-markdown-export'
989
+ ? (isGrokBuildNative ? 'grok-build-native-chat-history-v1' : 'documented-cli-markdown-export')
881
990
  : isMuse
882
991
  ? 'muse-export-trajectory-v1'
883
992
  : isWarp
@@ -944,7 +1053,7 @@ async function importSource(ctx, args) {
944
1053
  : isGrokbot
945
1054
  ? { 'native.grokbot': {} }
946
1055
  : isGrokBuild
947
- ? { 'native.grok-build': { acquisition: 'grok-cli-export-markdown' } }
1056
+ ? { 'native.grok-build': { acquisition: isGrokBuildNative ? GROK_BUILD_NATIVE_LOCATOR_CLASS : 'grok-cli-export-markdown' } }
948
1057
  : isMuse
949
1058
  ? { 'native.muse': {} }
950
1059
  : isWarp
@@ -1075,7 +1184,10 @@ async function exportBuild(ctx, args, repository, command) {
1075
1184
  ...output,
1076
1185
  bytesEmbedded: includeBytes && outputBytesByRegistrationId.has(output.registrationId),
1077
1186
  }));
1078
- const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId);
1187
+ const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId,
1188
+ // Resolve image references against the live catalog: available blobs
1189
+ // export as references, missing ones as explicit unavailable (#3265).
1190
+ (event) => repository.resolveEventImages(event));
1079
1191
  // aiwgFortemiIndexToKnowledgeShardWithReport targets the declared
1080
1192
  // full-v1/2.0.0 archive contract and validates losslessness -- unlike the
1081
1193
  // plain aiwgFortemiIndexToKnowledgeShard (core-v1), which silently embeds
@@ -1290,6 +1402,12 @@ async function discoverWorkspace(ctx, args) {
1290
1402
  ? resolve(ctx.cwd, args.values.get('--dsh-root')) : undefined,
1291
1403
  grokbotRoot: args.values.has('--grokbot-root')
1292
1404
  ? resolve(ctx.cwd, args.values.get('--grokbot-root')) : undefined,
1405
+ grokRoot: args.values.has('--grok-root')
1406
+ ? resolve(ctx.cwd, args.values.get('--grok-root')) : undefined,
1407
+ grokActiveSessions: args.values.has('--grok-active-sessions')
1408
+ ? resolve(ctx.cwd, args.values.get('--grok-active-sessions')) : undefined,
1409
+ hermesRoot: args.values.has('--hermes-root')
1410
+ ? resolve(ctx.cwd, args.values.get('--hermes-root')) : undefined,
1293
1411
  codexRoots: [
1294
1412
  ...(config?.sessions?.codex_roots ?? []).map((root) => resolve(workspace, root)),
1295
1413
  ...args.codexRoots.map((root) => resolve(ctx.cwd, root)),
@@ -1355,18 +1473,33 @@ async function importDiscovered(ctx, args) {
1355
1473
  ...(carryPath ? { carryForward: redactSourceLocator(carryPath) } : {}),
1356
1474
  });
1357
1475
  }
1358
- const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), {
1359
- waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
1360
- });
1476
+ const waitMs = boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms');
1477
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), { waitMs });
1478
+ let hostLease;
1361
1479
  try {
1362
1480
  const previous = carryPath ? new SessionRepository(carryPath, { readonly: true }) : undefined;
1363
1481
  let repository;
1482
+ let hostRepository;
1364
1483
  try {
1365
1484
  repository = openRepository(ctx, args);
1485
+ // Account-scoped providers (Grok Bot) import once into the host
1486
+ // catalog; the workspace catalog records a reference (#3232). The
1487
+ // host catalog is only opened when the manifest actually holds an
1488
+ // account-scoped source.
1489
+ const hostCatalogPath = defaultHostCatalogPath();
1490
+ if (manifestNeedsHostCatalog(manifest.sources)) {
1491
+ mkdirSync(dirname(hostCatalogPath), { recursive: true, mode: 0o700 });
1492
+ // The workspace lease does not cover the shared host catalog; serialize
1493
+ // host writers the same way the hourly refresh does.
1494
+ hostLease = await acquireImportLease(hostCatalogPath, `host-import-${process.pid}`, { waitMs });
1495
+ hostRepository = new SessionRepository(hostCatalogPath);
1496
+ }
1366
1497
  const interruptedRuns = repository.interruptRunningBatchImports();
1367
1498
  const receipt = await importDiscoveryManifest({
1368
1499
  manifest,
1369
1500
  repository,
1501
+ hostRepository,
1502
+ hostCatalogPath,
1370
1503
  carryForward: previous,
1371
1504
  limits,
1372
1505
  exclude,
@@ -1388,10 +1521,12 @@ async function importDiscovered(ctx, args) {
1388
1521
  }
1389
1522
  finally {
1390
1523
  repository?.close();
1524
+ hostRepository?.close();
1391
1525
  previous?.close();
1392
1526
  }
1393
1527
  }
1394
1528
  finally {
1529
+ await hostLease?.release();
1395
1530
  await lease.release();
1396
1531
  }
1397
1532
  }
@@ -1470,10 +1605,10 @@ function providerDisposition(provider) {
1470
1605
  if (provider === 'hermes') {
1471
1606
  return {
1472
1607
  provider, disposition: 'implemented', operationalState: 'available',
1473
- supportedOperations: ['inspect', 'stream'],
1608
+ supportedOperations: ['discover', 'inspect', 'stream'],
1474
1609
  acquisitionModes: ['jsonl', 'api', 'sqlite-snapshot'],
1475
1610
  reasonCode: null,
1476
- remediation: 'Use `hermes sessions export` or an explicitly verified sqlite3.backup() snapshot export.',
1611
+ remediation: 'Pass --hermes-root with an explicitly authorized Hermes home for a read-only state.db backup snapshot; or use `hermes sessions export` and import the explicitly authorized JSONL file.',
1477
1612
  evidence: {
1478
1613
  adapterVersion: HERMES_ADAPTER_VERSION,
1479
1614
  verifiedAt: '2026-07-27',
@@ -1581,10 +1716,10 @@ function providerDisposition(provider) {
1581
1716
  if (provider === 'grok-build') {
1582
1717
  return {
1583
1718
  provider, disposition: 'implemented', operationalState: 'available',
1584
- supportedOperations: ['inspect', 'stream'],
1585
- acquisitionModes: ['manual-export'],
1586
- reasonCode: 'CLI_EXPORT_REQUIRED',
1587
- remediation: 'Run `grok sessions list`, then `grok export <session-id> <session-id>.md` and import the explicitly authorized Markdown file. `aiwg sessions grok-list` finds `grok` on PATH or in $GROK_HOME/bin; pass --grok-bin <absolute path> otherwise.',
1719
+ supportedOperations: ['discover', 'inspect', 'stream'],
1720
+ acquisitionModes: ['manual-export', 'jsonl'],
1721
+ reasonCode: null,
1722
+ remediation: 'Pass --grok-root with an explicitly authorized sessions root for native chat_history.jsonl discovery, optionally with --grok-active-sessions for live detection; or run `grok export <session-id> <session-id>.md` and import the explicitly authorized Markdown file.',
1588
1723
  evidence: {
1589
1724
  adapterVersion: GROK_BUILD_ADAPTER_VERSION,
1590
1725
  verifiedAt: '2026-09-21',
@@ -1718,6 +1853,127 @@ function catalogScope(ctx, args, repository, workspaceId) {
1718
1853
  : null,
1719
1854
  };
1720
1855
  }
1856
+ /**
1857
+ * Host-catalog read-through for account-scoped sessions (#3232). The
1858
+ * workspace catalog holds references instead of copies; each reference
1859
+ * records its host catalog path. Every referenced catalog opens read-only
1860
+ * and closes before returning. Unreadable catalogs contribute nothing, so
1861
+ * local rows still list when the host catalog is missing.
1862
+ */
1863
+ function eachReferencedHostCatalog(repository, provider, read) {
1864
+ const references = repository.listHostSessionReferences()
1865
+ .filter((reference) => !provider || reference.provider === provider);
1866
+ if (references.length === 0)
1867
+ return [];
1868
+ const byCatalog = new Map();
1869
+ for (const reference of references) {
1870
+ const group = byCatalog.get(reference.hostCatalog);
1871
+ if (group)
1872
+ group.push(reference);
1873
+ else
1874
+ byCatalog.set(reference.hostCatalog, [reference]);
1875
+ }
1876
+ const reads = [];
1877
+ for (const [catalogPath, group] of byCatalog) {
1878
+ let host;
1879
+ try {
1880
+ host = new SessionRepository(catalogPath, { readonly: true });
1881
+ }
1882
+ catch {
1883
+ continue;
1884
+ }
1885
+ try {
1886
+ reads.push({ references: group, value: read(host, group) });
1887
+ }
1888
+ finally {
1889
+ host.close();
1890
+ }
1891
+ }
1892
+ return reads;
1893
+ }
1894
+ function readHostReferencedSessions(repository, filters) {
1895
+ const reads = eachReferencedHostCatalog(repository, filters.provider, (host, group) => {
1896
+ const page = host.listSessions({
1897
+ provider: filters.provider,
1898
+ workspaceId: HOST_CATALOG_WORKSPACE_ID,
1899
+ limit: group.length,
1900
+ });
1901
+ const sessions = resolveHostReferences(group, page.items)
1902
+ .filter((session) => !filters.tag || host.listTags(session.sessionId).includes(filters.tag));
1903
+ const resolvedSourceIds = new Set(sessions.map((session) => session.sourceId));
1904
+ return {
1905
+ sessions,
1906
+ resolvedReferences: group.filter((reference) => resolvedSourceIds.has(reference.sourceId)),
1907
+ };
1908
+ });
1909
+ return {
1910
+ references: reads.flatMap((read) => read.references),
1911
+ resolvedReferences: reads.flatMap((read) => read.value.resolvedReferences),
1912
+ sessions: reads.flatMap((read) => read.value.sessions),
1913
+ };
1914
+ }
1915
+ function searchHostReferencedSessions(repository, searchOptions, provider) {
1916
+ const reads = eachReferencedHostCatalog(repository, provider, (host) => {
1917
+ const result = host.search({
1918
+ ...searchOptions,
1919
+ workspaceId: HOST_CATALOG_WORKSPACE_ID,
1920
+ cursor: undefined,
1921
+ });
1922
+ return {
1923
+ hits: result.items,
1924
+ hostIdentities: host.listSessionIdentities(HOST_CATALOG_WORKSPACE_ID),
1925
+ };
1926
+ });
1927
+ return {
1928
+ hits: reads.flatMap((read) => read.value.hits),
1929
+ hostIdentities: reads.flatMap((read) => read.value.hostIdentities),
1930
+ };
1931
+ }
1932
+ function readHostReferencedAnalyticsFacts(repository, query) {
1933
+ // query.sessionId is a workspace-local internal id; host rows carry host
1934
+ // stable ids, so resolve it to a provider + native session key first.
1935
+ let sessionKey = null;
1936
+ if (query.sessionId) {
1937
+ const session = repository.getSession(query.sessionId, query.workspaceId);
1938
+ if (!session)
1939
+ return { facts: [], hostIdentities: [] };
1940
+ sessionKey = `${session.provider} ${session.nativeSessionId}`;
1941
+ }
1942
+ const reads = eachReferencedHostCatalog(repository, query.provider, (host, group) => {
1943
+ const wanted = new Set(group.map((reference) => `${reference.provider} ${reference.nativeSessionId}`));
1944
+ const identities = host.listSessionIdentities(HOST_CATALOG_WORKSPACE_ID);
1945
+ const hostSessionIds = new Set(identities
1946
+ .filter((identity) => wanted.has(`${identity.provider} ${identity.nativeSessionId}`))
1947
+ .map((identity) => identity.sessionId));
1948
+ const { sessionId: _localSessionId, ...hostQuery } = query;
1949
+ const facts = host.listAnalyticsFacts({ ...hostQuery, workspaceId: HOST_CATALOG_WORKSPACE_ID })
1950
+ .filter((fact) => hostSessionIds.has(fact.sessionId)
1951
+ && (!sessionKey || `${fact.provider} ${hostSessionKey(fact.sessionId, identities)}` === sessionKey));
1952
+ return { facts, identities };
1953
+ });
1954
+ return {
1955
+ facts: reads.flatMap((read) => read.value.facts),
1956
+ hostIdentities: reads.flatMap((read) => read.value.identities),
1957
+ };
1958
+ }
1959
+ function hostSessionKey(sessionId, identities) {
1960
+ return identities.find((identity) => identity.sessionId === sessionId)?.nativeSessionId ?? null;
1961
+ }
1962
+ /**
1963
+ * Workspace analytics facts with host references resolved. Returns null
1964
+ * when the workspace holds no references so callers keep the exact
1965
+ * single-catalog query path (SQL limit/filter semantics unchanged).
1966
+ */
1967
+ function readWorkspaceAnalyticsFacts(repository, query) {
1968
+ const references = repository.listHostSessionReferences()
1969
+ .filter((reference) => !query.provider || reference.provider === query.provider);
1970
+ if (references.length === 0)
1971
+ return null;
1972
+ const hostRead = readHostReferencedAnalyticsFacts(repository, query);
1973
+ if (hostRead.facts.length === 0)
1974
+ return null;
1975
+ return mergeCrossCatalogRows(repository.listAnalyticsFacts(query), hostRead.facts, indexIdentitiesBySessionId(repository.listSessionIdentities(query.workspaceId)), indexIdentitiesBySessionId(hostRead.hostIdentities), (fact) => `${fact.provider} ${fact.factId}`);
1976
+ }
1721
1977
  async function configuredImportLimits(ctx, args, workspace) {
1722
1978
  const selectedWorkspace = workspace ?? (args.values.has('--workspace')
1723
1979
  ? resolve(ctx.cwd, args.values.get('--workspace')) : ctx.cwd);
@@ -1739,12 +1995,12 @@ function parseArgs(argv) {
1739
1995
  const exclude = [];
1740
1996
  const positionals = [];
1741
1997
  const valueFlags = new Set([
1742
- '--db', '--provider', '--workspace', '--tag', '--limit', '--cursor', '--source-id',
1998
+ '--db', '--provider', '--workspace', '--tag', '--limit', '--cursor', '--source-id', '--from-dir',
1743
1999
  '--date-from', '--date-to', '--participant', '--model', '--role', '--tool',
1744
2000
  '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
1745
2001
  '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
1746
2002
  '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
1747
- '--carry-forward', '--manifest', '--provider-home', '--codex-root', '--omp-root', '--muse-root', '--dsh-root', '--grokbot-root', '--lock-wait-ms', '--min-coverage', '--gap',
2003
+ '--carry-forward', '--manifest', '--provider-home', '--codex-root', '--omp-root', '--muse-root', '--dsh-root', '--grokbot-root', '--grok-root', '--grok-active-sessions', '--hermes-root', '--lock-wait-ms', '--min-coverage', '--gap',
1748
2004
  '--inactivity-threshold', '--expected-providers', '--exclude', '--max-record-bytes', '--max-source-bytes',
1749
2005
  '--control-events',
1750
2006
  '--session', '--status', '--actor', '--group-by',
@@ -13,6 +13,8 @@ import * as fs from 'fs/promises';
13
13
  import * as path from 'path';
14
14
  import { validateSkillFrontmatter } from '../../extensions/validation.js';
15
15
  import { validateAgentSkillFile } from '../../skills/validator.js';
16
+ import { CLAUDE_SKILL_FIELDS } from '../../skills/provider-extensions.js';
17
+ import { projectClaudeSkillSurfaces } from '../../skills/claude-surface-projection.js';
16
18
  const THRESHOLDS = {
17
19
  strict: 80,
18
20
  standard: 60,
@@ -44,7 +46,10 @@ function scoreSchema(frontmatter, diagnostics) {
44
46
  : `${item.yamlPath}: ${item.message} [${item.code}]`));
45
47
  return scoreDimension(false, notes);
46
48
  }
47
- const result = validateSkillFrontmatter(frontmatter);
49
+ // Native Claude fields belong to the provider layer, including string effort.
50
+ const portableAndAiwg = Object.fromEntries(Object.entries(frontmatter)
51
+ .filter(([key]) => !CLAUDE_SKILL_FIELDS.includes(key)));
52
+ const result = validateSkillFrontmatter(portableAndAiwg);
48
53
  if (result.success)
49
54
  return scoreDimension(true);
50
55
  const notes = result.errors.issues.map((e) => `${e.path.join('.') || '<root>'}: ${e.message}`);
@@ -211,6 +216,7 @@ export async function lintSkillFile(filePath, rubric = 'standard', profile = 'co
211
216
  return {
212
217
  file: filePath,
213
218
  score,
219
+ projections: projectClaudeSkillSurfaces(body),
214
220
  conformance: {
215
221
  profile,
216
222
  state: validation.state,
@@ -321,14 +327,15 @@ function parseArgs(args) {
321
327
  function renderTextReport(report) {
322
328
  const { files, threshold, rubric, profile, averageScore, failedCount } = report;
323
329
  for (const f of files) {
324
- if (f.passes && f.conformance.diagnostics.length === 0)
325
- continue;
326
330
  const mark = !f.passes
327
331
  ? '✗'
328
332
  : f.conformance.diagnostics.some((item) => item.severity === 'warning')
329
333
  ? '⚠'
330
334
  : '✓';
331
335
  console.log(`${mark} ${f.file} (${f.score}/100, conformance=${f.conformance.state})`);
336
+ for (const p of f.projections) {
337
+ console.log(` ${p.provider}/${p.surface}: ${p.status}${p.shellInterpolation ? ` (shell: ${p.shellInterpolation})` : ''}; ${p.reasons.join(' ')}`);
338
+ }
332
339
  for (const item of f.conformance.diagnostics) {
333
340
  console.log(` ${item.severity} ${item.code} ${item.yamlPath}: ${item.message}; `
334
341
  + `fix: ${item.remediation}`);