@open-agent-toolkit/cli 0.2.63 → 0.2.64

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 (92) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/tool-packs.md +83 -0
  3. package/assets/docs/contributing/skills.md +42 -7
  4. package/assets/docs/provider-sync/manifest-and-drift.md +105 -0
  5. package/assets/public-package-versions.json +4 -4
  6. package/assets/skills/create-agnostic-skill/SKILL.md +9 -7
  7. package/assets/skills/create-agnostic-skill/references/skill-template.md +9 -7
  8. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -0
  9. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -0
  10. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -0
  11. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +1 -0
  12. package/assets/skills/oat-project-pr-final/SKILL.md +281 -6
  13. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -0
  14. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -0
  15. package/dist/agents/canonical/resolve.d.ts.map +1 -1
  16. package/dist/agents/canonical/resolve.js +25 -4
  17. package/dist/commands/doctor/index.d.ts +7 -0
  18. package/dist/commands/doctor/index.d.ts.map +1 -1
  19. package/dist/commands/doctor/index.js +10 -2
  20. package/dist/commands/init/tools/index.d.ts.map +1 -1
  21. package/dist/commands/init/tools/index.js +68 -36
  22. package/dist/commands/shared/frontmatter.d.ts +51 -0
  23. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  24. package/dist/commands/shared/frontmatter.js +118 -6
  25. package/dist/commands/status/index.d.ts.map +1 -1
  26. package/dist/commands/status/index.js +5 -3
  27. package/dist/commands/sync/index.d.ts +14 -0
  28. package/dist/commands/sync/index.d.ts.map +1 -1
  29. package/dist/commands/sync/index.js +21 -0
  30. package/dist/commands/tools/info/index.d.ts.map +1 -1
  31. package/dist/commands/tools/info/index.js +14 -1
  32. package/dist/commands/tools/info/info-tool.d.ts +12 -0
  33. package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
  34. package/dist/commands/tools/info/info-tool.js +54 -6
  35. package/dist/commands/tools/info/skill-views.d.ts +86 -0
  36. package/dist/commands/tools/info/skill-views.d.ts.map +1 -0
  37. package/dist/commands/tools/info/skill-views.js +383 -0
  38. package/dist/commands/tools/install/index.d.ts.map +1 -1
  39. package/dist/commands/tools/install/index.js +3 -25
  40. package/dist/commands/tools/list/list-tools.d.ts +2 -0
  41. package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
  42. package/dist/commands/tools/list/list-tools.js +23 -4
  43. package/dist/commands/tools/remove/index.d.ts.map +1 -1
  44. package/dist/commands/tools/remove/index.js +35 -42
  45. package/dist/commands/tools/remove/remove-tools.d.ts +3 -2
  46. package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
  47. package/dist/commands/tools/remove/remove-tools.js +14 -4
  48. package/dist/commands/tools/shared/auto-sync.d.ts +12 -1
  49. package/dist/commands/tools/shared/auto-sync.d.ts.map +1 -1
  50. package/dist/commands/tools/shared/auto-sync.js +20 -4
  51. package/dist/commands/tools/shared/format-pack-inventory.d.ts +12 -1
  52. package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
  53. package/dist/commands/tools/shared/format-pack-inventory.js +48 -6
  54. package/dist/commands/tools/shared/in-process-sync.d.ts +11 -0
  55. package/dist/commands/tools/shared/in-process-sync.d.ts.map +1 -0
  56. package/dist/commands/tools/shared/in-process-sync.js +79 -0
  57. package/dist/commands/tools/shared/pack-evidence.d.ts +78 -2
  58. package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -1
  59. package/dist/commands/tools/shared/pack-evidence.js +96 -2
  60. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +11 -0
  61. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -1
  62. package/dist/commands/tools/shared/pack-lifecycle-outcome.js +24 -3
  63. package/dist/commands/tools/shared/pack-provider-evidence.d.ts +73 -0
  64. package/dist/commands/tools/shared/pack-provider-evidence.d.ts.map +1 -0
  65. package/dist/commands/tools/shared/pack-provider-evidence.js +161 -0
  66. package/dist/commands/tools/shared/provider-context.d.ts +88 -0
  67. package/dist/commands/tools/shared/provider-context.d.ts.map +1 -0
  68. package/dist/commands/tools/shared/provider-context.js +77 -0
  69. package/dist/commands/tools/shared/provider-reachability.d.ts +93 -0
  70. package/dist/commands/tools/shared/provider-reachability.d.ts.map +1 -0
  71. package/dist/commands/tools/shared/provider-reachability.js +358 -0
  72. package/dist/commands/tools/shared/sync-evidence.d.ts +57 -0
  73. package/dist/commands/tools/shared/sync-evidence.d.ts.map +1 -0
  74. package/dist/commands/tools/shared/sync-evidence.js +147 -0
  75. package/dist/commands/tools/update/index.d.ts +0 -4
  76. package/dist/commands/tools/update/index.d.ts.map +1 -1
  77. package/dist/commands/tools/update/index.js +22 -40
  78. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  79. package/dist/commands/tools/update/update-tools.js +23 -8
  80. package/dist/config/json.d.ts +36 -0
  81. package/dist/config/json.d.ts.map +1 -1
  82. package/dist/config/json.js +135 -3
  83. package/dist/drift/index.d.ts +2 -0
  84. package/dist/drift/index.d.ts.map +1 -1
  85. package/dist/drift/index.js +1 -0
  86. package/dist/drift/skill-view-diagnostic.d.ts +197 -0
  87. package/dist/drift/skill-view-diagnostic.d.ts.map +1 -0
  88. package/dist/drift/skill-view-diagnostic.js +343 -0
  89. package/dist/validation/skills.d.ts +1 -0
  90. package/dist/validation/skills.d.ts.map +1 -1
  91. package/dist/validation/skills.js +181 -19
  92. package/package.json +2 -2
@@ -0,0 +1,77 @@
1
+ import { join } from 'node:path';
2
+ import { DEFAULT_SYNC_CONFIG, loadSyncConfig } from '../../../config/index.js';
3
+ import { resolveProviderScopeContext, userAgentMaterializationCoverage, } from '../../../providers/shared/registry.js';
4
+ /**
5
+ * Resolves the config-aware provider context for one scope, keeping the
6
+ * failure.
7
+ *
8
+ * Never throws: provider reachability is additive evidence on a lifecycle or
9
+ * inventory result, so a sync config that cannot be read must not fail an
10
+ * install that already succeeded. The error is returned instead of swallowed so
11
+ * a caller that can report it honestly is able to.
12
+ */
13
+ export async function resolveScopeProviderContextOutcome(input) {
14
+ const load = input.dependencies?.loadSyncConfig ??
15
+ ((configPath) => loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG));
16
+ const resolve = input.dependencies?.resolveProviderScopeContext ??
17
+ resolveProviderScopeContext;
18
+ try {
19
+ const config = await load(join(input.scopeRoot, '.oat', 'sync', 'config.json'));
20
+ return {
21
+ status: 'resolved',
22
+ context: await resolve({
23
+ scope: input.scope,
24
+ scopeRoot: input.scopeRoot,
25
+ config,
26
+ }),
27
+ };
28
+ }
29
+ catch (error) {
30
+ return { status: 'failed', error };
31
+ }
32
+ }
33
+ /**
34
+ * Resolves the config-aware provider context for one scope.
35
+ *
36
+ * Returns `null` rather than throwing: provider reachability is additive
37
+ * evidence on a lifecycle or inventory result, so a missing or unreadable
38
+ * sync config must degrade to "no provider evidence" instead of failing an
39
+ * install that already succeeded. Callers that need to tell a failure apart
40
+ * from a legitimately empty result use
41
+ * {@link resolveScopeProviderContextOutcome} instead.
42
+ */
43
+ export async function resolveScopeProviderContext(input) {
44
+ const outcome = await resolveScopeProviderContextOutcome(input);
45
+ return outcome.status === 'resolved' ? outcome.context : null;
46
+ }
47
+ /** Resolves provider contexts for every scope with a known root. */
48
+ export async function resolveProviderScopeContexts(input) {
49
+ const entries = Object.entries(input.scopeRoots).filter((entry) => Boolean(entry[1]));
50
+ const contexts = await Promise.all(entries.map(([scope, scopeRoot]) => resolveScopeProviderContext({
51
+ scope,
52
+ scopeRoot,
53
+ ...(input.dependencies ? { dependencies: input.dependencies } : {}),
54
+ })));
55
+ return contexts.filter((context) => context !== null);
56
+ }
57
+ /**
58
+ * Resolves the provider surface every pack inventory renderer needs.
59
+ *
60
+ * `list`, `info`, `status`, and `doctor` all report the same packs, so they
61
+ * must agree on whether user-scope managed roles are materialized. Sharing
62
+ * this resolution is what keeps `list`/`info` from reporting unmaterialized
63
+ * user agents that `status`/`doctor` correctly suppress.
64
+ */
65
+ export async function resolvePackProviderSurface(input) {
66
+ const contexts = await resolveProviderScopeContexts(input);
67
+ const userContext = contexts.find(({ scope }) => scope === 'user');
68
+ return {
69
+ contexts,
70
+ userAgentCoverage: userContext
71
+ ? userAgentMaterializationCoverage({
72
+ registrations: userContext.registrations,
73
+ activeProviders: userContext.activeProviders,
74
+ })
75
+ : 'none',
76
+ };
77
+ }
@@ -0,0 +1,93 @@
1
+ import type { SyncOperationResult } from '../../../engine/engine.types.js';
2
+ import type { MaterializationOperationResult } from '../../../providers/shared/materialization-extension.js';
3
+ import type { ManagedContentKind, ProviderCatalogRefreshPolicy, ProviderContentCapability, ProviderScopeContext } from '../../../providers/shared/registry.js';
4
+ import type { ConcreteScope } from '../../../shared/types.js';
5
+ import type { ProviderReachabilityEvidence } from './pack-evidence.js';
6
+ /**
7
+ * Maps config-aware provider registry state onto per-pack reachability
8
+ * evidence.
9
+ *
10
+ * Per DR-260831 the reachability decision is registry- and config-derived:
11
+ * activation comes from `ProviderScopeContext.activation` (sync config plus
12
+ * adapter detection), capability and refresh policy come from the registered
13
+ * `ProviderContentCapability` rows, and projection/materialization come from
14
+ * the sync run's own operation results. The mapper never probes the
15
+ * filesystem for a provider directory to decide that a provider is reachable.
16
+ *
17
+ * The one absence signal that originates outside the registry is
18
+ * `unmaterializedAssets`: the pack inventory's list of managed assets that
19
+ * have no provider materialization. That list is an inventory observation
20
+ * about assets, not a reachability claim about a provider — the mapper still
21
+ * decides *which* providers are responsible for it from registry capability
22
+ * alone, and attributes it to none when no active provider supports the
23
+ * scope and content kind.
24
+ */
25
+ /** A sync operation result narrowed to the fields reachability needs. */
26
+ export type ProviderSyncOperationEvidence = Pick<SyncOperationResult, 'provider' | 'contentKind' | 'asset' | 'status'> & {
27
+ scope: ConcreteScope;
28
+ failure?: string;
29
+ };
30
+ /** A materialization-extension result narrowed the same way. */
31
+ export type ProviderExtensionOperationEvidence = Pick<MaterializationOperationResult, 'provider' | 'status'> & {
32
+ scope: ConcreteScope;
33
+ contentKind?: ManagedContentKind;
34
+ path?: string;
35
+ /**
36
+ * `role` targets one managed entry; `config` targets the provider's shared
37
+ * aggregate configuration, which every managed role for that provider
38
+ * depends on.
39
+ */
40
+ target?: string;
41
+ failure?: string;
42
+ };
43
+ /** Optional per-provider refresh-policy override observed by a sync run. */
44
+ export type ProviderRefreshPolicyLookup = (input: {
45
+ provider: string;
46
+ scope: ConcreteScope;
47
+ contentKind: ManagedContentKind;
48
+ }) => ProviderCatalogRefreshPolicy | undefined;
49
+ export interface ProviderReachabilityInput {
50
+ /** Config-aware activation, detection, and registrations for one scope. */
51
+ providerScopeContext: ProviderScopeContext;
52
+ /** Content kinds the pack actually places at this scope, with their assets. */
53
+ assets: Readonly<Partial<Record<ManagedContentKind, readonly string[]>>>;
54
+ /** Registry capability rows; defaults to each registration's own rows. */
55
+ capabilities?: readonly ProviderContentCapability[];
56
+ /** Core sync operation results for this scope, when a sync ran. */
57
+ syncOperationResults?: readonly ProviderSyncOperationEvidence[];
58
+ /** Materialization-extension results for this scope, when a sync ran. */
59
+ extensionResults?: readonly ProviderExtensionOperationEvidence[];
60
+ /** Overrides the registry catalog-refresh policy with observed advice. */
61
+ refreshPolicy?: ProviderRefreshPolicyLookup;
62
+ /**
63
+ * `lifecycle` means a sync actually ran and its results are authoritative.
64
+ * `inventory` is a read-only surface that observed no sync.
65
+ */
66
+ mode: 'lifecycle' | 'inventory';
67
+ /**
68
+ * `false` records that auto-sync was disabled or skipped: no projection or
69
+ * materialization claim can be made, so no diagnostic is produced.
70
+ */
71
+ syncRan?: boolean;
72
+ /** Managed assets the pack inventory reports as unmaterialized. */
73
+ unmaterializedAssets?: Readonly<Partial<Record<ManagedContentKind, readonly string[]>>>;
74
+ }
75
+ /**
76
+ * Projects one reachability row per active-or-registered provider and per
77
+ * content kind the pack places at this scope.
78
+ */
79
+ export declare function projectProviderReachability(input: ProviderReachabilityInput): ProviderReachabilityEvidence[];
80
+ /**
81
+ * Re-projects existing reachability rows with a sync run's observed results.
82
+ *
83
+ * Activation and capability were already derived from the registry before the
84
+ * sync ran and do not change; only projection, materialization, visibility,
85
+ * and recovery are refined. Inactive or unsupported rows are returned
86
+ * untouched.
87
+ */
88
+ export declare function applySyncEvidence(providers: readonly ProviderReachabilityEvidence[], input: {
89
+ syncRan: boolean;
90
+ syncOperationResults?: readonly ProviderSyncOperationEvidence[];
91
+ extensionResults?: readonly ProviderExtensionOperationEvidence[];
92
+ }): ProviderReachabilityEvidence[];
93
+ //# sourceMappingURL=provider-reachability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-reachability.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/provider-reachability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAClG,OAAO,KAAK,EACV,kBAAkB,EAClB,4BAA4B,EAC5B,yBAAyB,EAEzB,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAEV,4BAA4B,EAG7B,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;GAkBG;AAEH,yEAAyE;AACzE,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,mBAAmB,EACnB,UAAU,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAChD,GAAG;IACF,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,kCAAkC,GAAG,IAAI,CACnD,8BAA8B,EAC9B,UAAU,GAAG,QAAQ,CACtB,GAAG;IACF,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,2BAA2B,GAAG,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,kBAAkB,CAAC;CACjC,KAAK,4BAA4B,GAAG,SAAS,CAAC;AAE/C,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,+EAA+E;IAC/E,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAC;IACpD,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAChE,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,SAAS,kCAAkC,EAAE,CAAC;IACjE,0EAA0E;IAC1E,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C;;;OAGG;IACH,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,QAAQ,CAC7B,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CACvD,CAAC;CACH;AAwHD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,yBAAyB,GAC/B,4BAA4B,EAAE,CAmJhC;AA2GD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,4BAA4B,EAAE,EAClD,KAAK,EAAE;IACL,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAChE,gBAAgB,CAAC,EAAE,SAAS,kCAAkC,EAAE,CAAC;CAClE,GACA,4BAA4B,EAAE,CAwChC"}
@@ -0,0 +1,358 @@
1
+ const CONTENT_KIND_ORDER = [
2
+ 'skill',
3
+ 'agent',
4
+ 'rule',
5
+ 'directory',
6
+ ];
7
+ function projectionModeFor(capability) {
8
+ return (capability.projectionModes.find((mode) => mode !== 'unsupported') ?? null);
9
+ }
10
+ /**
11
+ * Maps a registered catalog-refresh policy onto visibility evidence.
12
+ *
13
+ * The switch is exhaustive with a `never` default rather than a fall-through:
14
+ * a policy state added later must fail to compile instead of silently
15
+ * becoming "needs a new session", which would be advice this repository never
16
+ * sourced.
17
+ */
18
+ function visibilityFor(policy, provider) {
19
+ switch (policy.state) {
20
+ case 'unknown':
21
+ return { state: 'unknown', reason: policy.reason };
22
+ case 'live':
23
+ return {
24
+ state: 'live',
25
+ reason: `${provider} reads the projected content without a refresh`,
26
+ };
27
+ case 'manual-refresh':
28
+ return {
29
+ state: 'manual-refresh',
30
+ reason: `${provider} needs a catalog refresh before the change is visible`,
31
+ };
32
+ case 'restart-required':
33
+ return {
34
+ state: 'restart-required',
35
+ reason: `${provider} needs a new session before the change is visible`,
36
+ };
37
+ default: {
38
+ const unhandled = policy;
39
+ throw new Error(`Unhandled provider catalog refresh state: ${JSON.stringify(unhandled)}`);
40
+ }
41
+ }
42
+ }
43
+ function syncRecovery(scope) {
44
+ return {
45
+ code: 'run-sync',
46
+ command: `oat sync --scope ${scope}`,
47
+ message: `Run sync for ${scope} scope to materialize provider views.`,
48
+ };
49
+ }
50
+ function recoveryFor(input) {
51
+ if (input.activationState === 'inactive') {
52
+ return [
53
+ {
54
+ code: 'enable-provider',
55
+ message: `Enable ${input.provider} in the ${input.scope} sync config to project content to it.`,
56
+ },
57
+ ];
58
+ }
59
+ if (input.support !== 'supported')
60
+ return [];
61
+ if (input.materialization === 'failed') {
62
+ return [
63
+ {
64
+ code: 'retry-sync',
65
+ command: `oat sync --scope ${input.scope}`,
66
+ message: `Re-run sync for ${input.provider} and inspect the reported failure.`,
67
+ },
68
+ ];
69
+ }
70
+ if (input.materialization === 'missing')
71
+ return [syncRecovery(input.scope)];
72
+ if (!input.projected)
73
+ return [];
74
+ if (input.visibility === 'manual-refresh') {
75
+ return [
76
+ {
77
+ code: 'refresh-provider-catalog',
78
+ message: `Refresh the ${input.provider} catalog to see the updated content.`,
79
+ },
80
+ ];
81
+ }
82
+ if (input.visibility === 'restart-required') {
83
+ return [
84
+ {
85
+ code: 'restart-provider-session',
86
+ message: `Start a new ${input.provider} session to see the updated content.`,
87
+ },
88
+ ];
89
+ }
90
+ if (input.visibility === 'unknown') {
91
+ return [
92
+ {
93
+ code: 'verify-provider-visibility',
94
+ message: `Verify in ${input.provider} that the content is listed; no sourced refresh contract is registered.`,
95
+ },
96
+ ];
97
+ }
98
+ return [];
99
+ }
100
+ /**
101
+ * Projects one reachability row per active-or-registered provider and per
102
+ * content kind the pack places at this scope.
103
+ */
104
+ export function projectProviderReachability(input) {
105
+ const { providerScopeContext: context } = input;
106
+ const scope = context.scope;
107
+ const active = new Set(context.activeProviders);
108
+ const kinds = CONTENT_KIND_ORDER.filter((kind) => (input.assets[kind]?.length ?? 0) > 0);
109
+ const evidence = [];
110
+ for (const registration of context.registrations) {
111
+ const provider = registration.adapter.name;
112
+ const activation = context.activation.find((candidate) => candidate.provider === provider);
113
+ const activationState = active.has(provider) ? 'active' : 'inactive';
114
+ const capabilities = input.capabilities ?? registration.capabilities;
115
+ for (const contentKind of kinds) {
116
+ const assets = input.assets[contentKind] ?? [];
117
+ const capability = capabilities.find((candidate) => candidate.scope === scope && candidate.contentKind === contentKind);
118
+ const support = capability?.support ?? 'unknown';
119
+ const projectionModes = capability?.projectionModes ?? [];
120
+ const capabilityReason = capability === undefined
121
+ ? `${provider} registers no ${scope} ${contentKind} capability`
122
+ : (capability.unsupportedReason ??
123
+ `${provider} projects ${scope} ${contentKind} content via ${projectionModes.join(', ') || 'no mode'}`);
124
+ let materialization;
125
+ if (activationState === 'inactive' || support !== 'supported') {
126
+ materialization = {
127
+ state: 'not-applicable',
128
+ detail: activationState === 'inactive'
129
+ ? `${provider} is inactive for ${scope} scope`
130
+ : capabilityReason,
131
+ };
132
+ }
133
+ else if (input.mode === 'lifecycle' && input.syncRan === false) {
134
+ materialization = {
135
+ state: 'not-applicable',
136
+ detail: 'No sync ran for this operation',
137
+ };
138
+ }
139
+ else if (input.mode === 'lifecycle') {
140
+ materialization = observedMaterialization({
141
+ provider,
142
+ scope,
143
+ contentKind,
144
+ assets,
145
+ syncOperationResults: input.syncOperationResults ?? [],
146
+ extensionResults: input.extensionResults ?? [],
147
+ });
148
+ }
149
+ else {
150
+ const unmaterialized = input.unmaterializedAssets?.[contentKind] ?? [];
151
+ materialization =
152
+ unmaterialized.length > 0
153
+ ? {
154
+ state: 'missing',
155
+ detail: `${provider} has no ${scope} ${contentKind} materialization for ${unmaterialized.length} managed asset(s)`,
156
+ }
157
+ : {
158
+ // A read-only surface observed no sync. The pack inventory can
159
+ // report a positive absence, but its silence is not evidence
160
+ // that the provider view exists: claiming `materialized` here
161
+ // would assert reachability nothing established, which is
162
+ // exactly what DR-260831 forbids. Silence stays a non-claim.
163
+ state: 'not-applicable',
164
+ detail: `No sync was observed for ${provider} ${scope} ${contentKind} content`,
165
+ };
166
+ }
167
+ const policy = input.refreshPolicy?.({ provider, scope, contentKind }) ??
168
+ capability?.catalogRefresh ?? {
169
+ state: 'unknown',
170
+ reason: `No ${provider} ${scope} ${contentKind} refresh contract is registered`,
171
+ };
172
+ const reachable = activationState === 'active' && support === 'supported';
173
+ // Projection and materialization already collapse to `not-applicable`
174
+ // for a provider that is inactive or cannot carry this content kind.
175
+ // Visibility follows them: refresh or restart advice about a provider
176
+ // that is not even active reads as guidance the user should act on.
177
+ const visibility = reachable
178
+ ? visibilityFor(policy, provider)
179
+ : {
180
+ state: 'not-applicable',
181
+ reason: activationState === 'inactive'
182
+ ? `${provider} is inactive for ${scope} scope, so it has no catalog visibility`
183
+ : `${provider} does not project ${scope} ${contentKind} content, so it has no catalog visibility`,
184
+ };
185
+ const projected = materialization.state === 'materialized';
186
+ const projectionState = activationState === 'inactive' || support !== 'supported'
187
+ ? 'not-applicable'
188
+ : projected
189
+ ? 'projected'
190
+ : materialization.state === 'missing'
191
+ ? 'absent'
192
+ : 'not-applicable';
193
+ evidence.push({
194
+ provider,
195
+ scope,
196
+ contentKind,
197
+ assets: materialization.state === 'missing'
198
+ ? (input.unmaterializedAssets?.[contentKind] ?? assets)
199
+ : assets,
200
+ activation: {
201
+ state: activationState,
202
+ source: activation?.source ?? 'undetected-unset',
203
+ reason: activation?.reason ??
204
+ `${provider} has no recorded activation evidence`,
205
+ },
206
+ capability: {
207
+ support,
208
+ projectionModes,
209
+ reason: capabilityReason,
210
+ },
211
+ projection: {
212
+ state: projectionState,
213
+ mode: projectionState === 'not-applicable' || capability === undefined
214
+ ? null
215
+ : projectionModeFor(capability),
216
+ },
217
+ materialization,
218
+ visibility,
219
+ recovery: recoveryFor({
220
+ provider,
221
+ scope,
222
+ activationState,
223
+ support,
224
+ materialization: materialization.state,
225
+ visibility: visibility.state,
226
+ projected,
227
+ }),
228
+ });
229
+ }
230
+ }
231
+ return evidence;
232
+ }
233
+ /**
234
+ * Materialization state observed from a sync run's own operation results.
235
+ *
236
+ * A sync that ran and planned nothing for this provider and content kind is
237
+ * not evidence of a missing projection, so it stays a non-claim rather than
238
+ * becoming a warning that would flip a healthy pack evidence block to
239
+ * `partial`.
240
+ */
241
+ /**
242
+ * Canonical identity of an asset path.
243
+ *
244
+ * Sync operation results identify an asset by its canonical entry name, while
245
+ * a pack inventory row carries the asset's filesystem path. Comparing the
246
+ * final path segment is what keeps one pack's operation from being attributed
247
+ * to a different pack that merely shares a content kind.
248
+ */
249
+ function canonicalAssetName(path) {
250
+ return (path.split('/').pop() ?? path).replace(/\.[^.]+$/, '');
251
+ }
252
+ function operationBelongsToAssets(identity, assetNames) {
253
+ // An operation with no usable identity cannot be attributed to a specific
254
+ // pack, so it is deliberately not counted for any of them.
255
+ if (identity === undefined || identity === '')
256
+ return false;
257
+ return assetNames.has(canonicalAssetName(identity));
258
+ }
259
+ function observedMaterialization(input) {
260
+ const { provider, scope, contentKind } = input;
261
+ const assetNames = new Set(input.assets.map(canonicalAssetName));
262
+ const operations = [
263
+ ...input.syncOperationResults.filter((result) => result.provider === provider &&
264
+ result.scope === scope &&
265
+ result.contentKind === contentKind &&
266
+ operationBelongsToAssets(result.asset, assetNames)),
267
+ ...input.extensionResults.filter((result) => result.provider === provider &&
268
+ result.scope === scope &&
269
+ (result.contentKind === undefined ||
270
+ result.contentKind === contentKind) &&
271
+ // A `config` target is the provider's shared aggregate file, which is
272
+ // not any one pack's asset but which every managed role for this
273
+ // provider depends on. It is therefore attributed provider-wide
274
+ // rather than dropped; a `role` target must still match this row's
275
+ // own assets so one pack's failure cannot be reported against another.
276
+ (result.target === 'config' ||
277
+ operationBelongsToAssets(result.path, assetNames))),
278
+ ];
279
+ const failed = operations.filter((operation) => operation.status === 'failed');
280
+ if (failed.length > 0) {
281
+ return {
282
+ state: 'failed',
283
+ detail: `${provider} ${scope} ${contentKind} materialization failed: ${failed
284
+ .map((operation) => operation.failure)
285
+ .filter((failure) => Boolean(failure))
286
+ .join('; ') || 'no failure detail reported'}`,
287
+ };
288
+ }
289
+ // Status is classified explicitly rather than as "anything but failed":
290
+ // `missing`, `unsupported`, `planned`, and `unknown` are not evidence that
291
+ // the provider view now exists.
292
+ const succeeded = operations.filter((operation) => operation.status === 'changed' || operation.status === 'current');
293
+ if (succeeded.length > 0) {
294
+ return {
295
+ state: 'materialized',
296
+ detail: `${succeeded.length} ${provider} ${scope} ${contentKind} operation(s) succeeded`,
297
+ };
298
+ }
299
+ const missing = operations.filter((operation) => operation.status === 'missing');
300
+ if (missing.length > 0) {
301
+ return {
302
+ state: 'missing',
303
+ detail: `${provider} has no ${scope} ${contentKind} projection for ${missing.length} asset(s)`,
304
+ };
305
+ }
306
+ return {
307
+ state: 'not-applicable',
308
+ detail: `No ${provider} ${scope} ${contentKind} operation was required`,
309
+ };
310
+ }
311
+ /**
312
+ * Re-projects existing reachability rows with a sync run's observed results.
313
+ *
314
+ * Activation and capability were already derived from the registry before the
315
+ * sync ran and do not change; only projection, materialization, visibility,
316
+ * and recovery are refined. Inactive or unsupported rows are returned
317
+ * untouched.
318
+ */
319
+ export function applySyncEvidence(providers, input) {
320
+ if (!input.syncRan)
321
+ return [...providers];
322
+ return providers.map((evidence) => {
323
+ if (evidence.activation.state === 'inactive' ||
324
+ evidence.capability.support !== 'supported') {
325
+ return evidence;
326
+ }
327
+ const materialization = observedMaterialization({
328
+ provider: evidence.provider,
329
+ scope: evidence.scope,
330
+ contentKind: evidence.contentKind,
331
+ assets: evidence.assets,
332
+ syncOperationResults: input.syncOperationResults ?? [],
333
+ extensionResults: input.extensionResults ?? [],
334
+ });
335
+ const projected = materialization.state === 'materialized';
336
+ return {
337
+ ...evidence,
338
+ projection: {
339
+ state: projected
340
+ ? 'projected'
341
+ : materialization.state === 'missing'
342
+ ? 'absent'
343
+ : 'not-applicable',
344
+ mode: evidence.projection.mode,
345
+ },
346
+ materialization,
347
+ recovery: recoveryFor({
348
+ provider: evidence.provider,
349
+ scope: evidence.scope,
350
+ activationState: evidence.activation.state,
351
+ support: evidence.capability.support,
352
+ materialization: materialization.state,
353
+ visibility: evidence.visibility.state,
354
+ projected,
355
+ }),
356
+ };
357
+ });
358
+ }
@@ -0,0 +1,57 @@
1
+ import type { ManagedContentKind, ProviderCatalogRefreshPolicy } from '../../../providers/shared/registry.js';
2
+ import type { ConcreteScope } from '../../../shared/types.js';
3
+ import type { ProviderExtensionOperationEvidence, ProviderSyncOperationEvidence } from './provider-reachability.js';
4
+ /**
5
+ * Per-provider refresh advice as the sync run reports it.
6
+ *
7
+ * Structurally mirrors the `providerRefreshAdvice` entries in the sync JSON
8
+ * payload. The tools lifecycle consumes that payload; it never reshapes it.
9
+ */
10
+ export interface SyncProviderRefreshAdvice {
11
+ scope: ConcreteScope;
12
+ provider: string;
13
+ contentKind: ManagedContentKind;
14
+ visibility?: {
15
+ state?: string;
16
+ reason?: string;
17
+ policy?: ProviderCatalogRefreshPolicy;
18
+ };
19
+ }
20
+ /** Normalized evidence from one sync run at one scope. */
21
+ export interface SyncRunEvidence {
22
+ scope: ConcreteScope;
23
+ ran: boolean;
24
+ /**
25
+ * The run's aggregate failure count from `summary.failed`.
26
+ *
27
+ * Carried separately because a sync can fail without throwing and without
28
+ * emitting a per-operation `failed` result: a rejected collection counts as
29
+ * a failure but is not a planned operation.
30
+ */
31
+ failedOperations: number;
32
+ operationResults: readonly ProviderSyncOperationEvidence[];
33
+ extensionResults: readonly ProviderExtensionOperationEvidence[];
34
+ refreshAdvice: readonly SyncProviderRefreshAdvice[];
35
+ }
36
+ export declare function emptySyncRunEvidence(scope: ConcreteScope): SyncRunEvidence;
37
+ /**
38
+ * Normalizes a captured sync JSON payload into reachability inputs.
39
+ *
40
+ * Defensive rather than trusting: the payload shape is owned by the sync
41
+ * command, so an absent or renamed field degrades to "no evidence" instead of
42
+ * throwing inside a lifecycle operation that already succeeded.
43
+ */
44
+ export declare function normalizeSyncEvidence(scope: ConcreteScope, payload: unknown): SyncRunEvidence;
45
+ /**
46
+ * Builds a refresh-policy lookup from observed sync advice.
47
+ *
48
+ * Returns `undefined` for a provider the run reported nothing about, so the
49
+ * mapper falls back to the registered registry policy rather than inventing
50
+ * one.
51
+ */
52
+ export declare function refreshPolicyFromAdvice(advice: readonly SyncProviderRefreshAdvice[]): (input: {
53
+ provider: string;
54
+ scope: ConcreteScope;
55
+ contentKind: ManagedContentKind;
56
+ }) => ProviderCatalogRefreshPolicy | undefined;
57
+ //# sourceMappingURL=sync-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-evidence.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/sync-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,4BAA4B,EAC7B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EACV,kCAAkC,EAClC,6BAA6B,EAC9B,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,CAAC,EAAE;QACX,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,4BAA4B,CAAC;KACvC,CAAC;CACH;AAED,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC;IACb;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC3D,gBAAgB,EAAE,SAAS,kCAAkC,EAAE,CAAC;IAChE,aAAa,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACrD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,CAS1E;AAgBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,OAAO,GACf,eAAe,CA0HjB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,yBAAyB,EAAE,GAC3C,CAAC,KAAK,EAAE;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,kBAAkB,CAAC;CACjC,KAAK,4BAA4B,GAAG,SAAS,CAQ7C"}