@open-agent-toolkit/cli 0.2.65 → 0.2.66

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 (142) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +19 -1
  3. package/assets/docs/cli-utilities/index.md +3 -0
  4. package/assets/docs/cli-utilities/remote-project-management.md +191 -0
  5. package/assets/docs/reference/cli-reference.md +12 -1
  6. package/assets/docs/reference/file-locations.md +22 -0
  7. package/assets/docs/reference/oat-directory-structure.md +30 -17
  8. package/assets/docs/reference/troubleshooting.md +44 -0
  9. package/assets/docs/workflows/skills/index.md +33 -0
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-doctor/SKILL.md +4 -4
  12. package/assets/skills/oat-pjm-remote/SKILL.md +101 -0
  13. package/assets/skills/oat-pjm-remote/references/external-action-protocol.md +43 -0
  14. package/dist/commands/backlog/new.d.ts +1 -0
  15. package/dist/commands/backlog/new.d.ts.map +1 -1
  16. package/dist/commands/backlog/new.js +3 -1
  17. package/dist/commands/config/index.d.ts +1 -0
  18. package/dist/commands/config/index.d.ts.map +1 -1
  19. package/dist/commands/config/index.js +232 -1
  20. package/dist/commands/pjm/doctor.d.ts +2 -0
  21. package/dist/commands/pjm/doctor.d.ts.map +1 -1
  22. package/dist/commands/pjm/doctor.js +71 -1
  23. package/dist/commands/pjm/index.d.ts +2 -0
  24. package/dist/commands/pjm/index.d.ts.map +1 -1
  25. package/dist/commands/pjm/index.js +3 -0
  26. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts +60 -0
  27. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts.map +1 -0
  28. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.js +151 -0
  29. package/dist/commands/pjm/remote/association.d.ts +27 -0
  30. package/dist/commands/pjm/remote/association.d.ts.map +1 -0
  31. package/dist/commands/pjm/remote/association.js +68 -0
  32. package/dist/commands/pjm/remote/authority.d.ts +210 -0
  33. package/dist/commands/pjm/remote/authority.d.ts.map +1 -0
  34. package/dist/commands/pjm/remote/authority.js +341 -0
  35. package/dist/commands/pjm/remote/batch.d.ts +15 -0
  36. package/dist/commands/pjm/remote/batch.d.ts.map +1 -0
  37. package/dist/commands/pjm/remote/batch.js +126 -0
  38. package/dist/commands/pjm/remote/closeout.d.ts +69 -0
  39. package/dist/commands/pjm/remote/closeout.d.ts.map +1 -0
  40. package/dist/commands/pjm/remote/closeout.js +208 -0
  41. package/dist/commands/pjm/remote/create-binding.d.ts +62 -0
  42. package/dist/commands/pjm/remote/create-binding.d.ts.map +1 -0
  43. package/dist/commands/pjm/remote/create-binding.js +126 -0
  44. package/dist/commands/pjm/remote/credential-safety.d.ts +7 -0
  45. package/dist/commands/pjm/remote/credential-safety.d.ts.map +1 -0
  46. package/dist/commands/pjm/remote/credential-safety.js +21 -0
  47. package/dist/commands/pjm/remote/discussion.d.ts +49 -0
  48. package/dist/commands/pjm/remote/discussion.d.ts.map +1 -0
  49. package/dist/commands/pjm/remote/discussion.js +124 -0
  50. package/dist/commands/pjm/remote/doctor.d.ts +22 -0
  51. package/dist/commands/pjm/remote/doctor.d.ts.map +1 -0
  52. package/dist/commands/pjm/remote/doctor.js +380 -0
  53. package/dist/commands/pjm/remote/external-action.d.ts +208 -0
  54. package/dist/commands/pjm/remote/external-action.d.ts.map +1 -0
  55. package/dist/commands/pjm/remote/external-action.js +367 -0
  56. package/dist/commands/pjm/remote/host-execution.d.ts +28 -0
  57. package/dist/commands/pjm/remote/host-execution.d.ts.map +1 -0
  58. package/dist/commands/pjm/remote/host-execution.js +63 -0
  59. package/dist/commands/pjm/remote/index.d.ts +41 -0
  60. package/dist/commands/pjm/remote/index.d.ts.map +1 -0
  61. package/dist/commands/pjm/remote/index.js +280 -0
  62. package/dist/commands/pjm/remote/jira-adf.d.ts +35 -0
  63. package/dist/commands/pjm/remote/jira-adf.d.ts.map +1 -0
  64. package/dist/commands/pjm/remote/jira-adf.js +169 -0
  65. package/dist/commands/pjm/remote/lifecycle.d.ts +163 -0
  66. package/dist/commands/pjm/remote/lifecycle.d.ts.map +1 -0
  67. package/dist/commands/pjm/remote/lifecycle.js +224 -0
  68. package/dist/commands/pjm/remote/local-projection.d.ts +30 -0
  69. package/dist/commands/pjm/remote/local-projection.d.ts.map +1 -0
  70. package/dist/commands/pjm/remote/local-projection.js +95 -0
  71. package/dist/commands/pjm/remote/managed-markdown.d.ts +30 -0
  72. package/dist/commands/pjm/remote/managed-markdown.d.ts.map +1 -0
  73. package/dist/commands/pjm/remote/managed-markdown.js +128 -0
  74. package/dist/commands/pjm/remote/migrate.d.ts +14 -0
  75. package/dist/commands/pjm/remote/migrate.d.ts.map +1 -0
  76. package/dist/commands/pjm/remote/migrate.js +32 -0
  77. package/dist/commands/pjm/remote/operation-state.d.ts +15 -0
  78. package/dist/commands/pjm/remote/operation-state.d.ts.map +1 -0
  79. package/dist/commands/pjm/remote/operation-state.js +143 -0
  80. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts +25 -0
  81. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts.map +1 -0
  82. package/dist/commands/pjm/remote/outbound-projection-safety.js +99 -0
  83. package/dist/commands/pjm/remote/output.d.ts +60 -0
  84. package/dist/commands/pjm/remote/output.d.ts.map +1 -0
  85. package/dist/commands/pjm/remote/output.js +48 -0
  86. package/dist/commands/pjm/remote/preview.d.ts +88 -0
  87. package/dist/commands/pjm/remote/preview.d.ts.map +1 -0
  88. package/dist/commands/pjm/remote/preview.js +154 -0
  89. package/dist/commands/pjm/remote/provider-conformance.d.ts +13 -0
  90. package/dist/commands/pjm/remote/provider-conformance.d.ts.map +1 -0
  91. package/dist/commands/pjm/remote/provider-conformance.js +37 -0
  92. package/dist/commands/pjm/remote/provider.d.ts +57 -0
  93. package/dist/commands/pjm/remote/provider.d.ts.map +1 -0
  94. package/dist/commands/pjm/remote/provider.js +28 -0
  95. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts +46 -0
  96. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts.map +1 -0
  97. package/dist/commands/pjm/remote/providers/github-publication-safety.js +126 -0
  98. package/dist/commands/pjm/remote/providers/github.d.ts +207 -0
  99. package/dist/commands/pjm/remote/providers/github.d.ts.map +1 -0
  100. package/dist/commands/pjm/remote/providers/github.js +1456 -0
  101. package/dist/commands/pjm/remote/providers/jira.d.ts +245 -0
  102. package/dist/commands/pjm/remote/providers/jira.d.ts.map +1 -0
  103. package/dist/commands/pjm/remote/providers/jira.js +1650 -0
  104. package/dist/commands/pjm/remote/providers/linear.d.ts +182 -0
  105. package/dist/commands/pjm/remote/providers/linear.d.ts.map +1 -0
  106. package/dist/commands/pjm/remote/providers/linear.js +1289 -0
  107. package/dist/commands/pjm/remote/purpose-policy.d.ts +29 -0
  108. package/dist/commands/pjm/remote/purpose-policy.d.ts.map +1 -0
  109. package/dist/commands/pjm/remote/purpose-policy.js +118 -0
  110. package/dist/commands/pjm/remote/reconcile.d.ts +42 -0
  111. package/dist/commands/pjm/remote/reconcile.d.ts.map +1 -0
  112. package/dist/commands/pjm/remote/reconcile.js +101 -0
  113. package/dist/commands/pjm/remote/resolution.d.ts +122 -0
  114. package/dist/commands/pjm/remote/resolution.d.ts.map +1 -0
  115. package/dist/commands/pjm/remote/resolution.js +243 -0
  116. package/dist/commands/pjm/remote/schema.d.ts +9309 -0
  117. package/dist/commands/pjm/remote/schema.d.ts.map +1 -0
  118. package/dist/commands/pjm/remote/schema.js +1503 -0
  119. package/dist/commands/pjm/remote/service.d.ts +20 -0
  120. package/dist/commands/pjm/remote/service.d.ts.map +1 -0
  121. package/dist/commands/pjm/remote/service.js +5121 -0
  122. package/dist/commands/pjm/remote/shared-storage.d.ts +36 -0
  123. package/dist/commands/pjm/remote/shared-storage.d.ts.map +1 -0
  124. package/dist/commands/pjm/remote/shared-storage.js +110 -0
  125. package/dist/commands/pjm/remote/snapshot.d.ts +32 -0
  126. package/dist/commands/pjm/remote/snapshot.d.ts.map +1 -0
  127. package/dist/commands/pjm/remote/snapshot.js +140 -0
  128. package/dist/commands/pjm/remote/storage-locator.d.ts +29 -0
  129. package/dist/commands/pjm/remote/storage-locator.d.ts.map +1 -0
  130. package/dist/commands/pjm/remote/storage-locator.js +62 -0
  131. package/dist/commands/pjm/remote/store.d.ts +85 -0
  132. package/dist/commands/pjm/remote/store.d.ts.map +1 -0
  133. package/dist/commands/pjm/remote/store.js +553 -0
  134. package/dist/commands/pjm/remote/verification.d.ts +40 -0
  135. package/dist/commands/pjm/remote/verification.d.ts.map +1 -0
  136. package/dist/commands/pjm/remote/verification.js +100 -0
  137. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  138. package/dist/commands/tools/shared/pack-manifest.js +1 -0
  139. package/dist/config/oat-config.d.ts +26 -0
  140. package/dist/config/oat-config.d.ts.map +1 -1
  141. package/dist/config/oat-config.js +186 -2
  142. package/package.json +2 -2
@@ -0,0 +1,29 @@
1
+ import type { OatPjmRemoteOperationClass } from '../../../config/oat-config.js';
2
+ import type { RemoteBindingMetadata } from './schema.js';
3
+ export type BindingPurpose = RemoteBindingMetadata['purposes'][number];
4
+ export type SharedRemoteField = 'title' | 'description' | 'priority';
5
+ export type FieldDirection = 'inbound' | 'outbound';
6
+ export type CloseoutAnnotationPolicy = 'propose' | 'none';
7
+ export type CloseoutTransitionPolicy = 'propose' | 'provider-automation' | 'none';
8
+ export interface PurposePolicy {
9
+ fields: Record<SharedRemoteField, readonly FieldDirection[]>;
10
+ lifecycle: readonly OatPjmRemoteOperationClass[];
11
+ closeout: {
12
+ annotation: CloseoutAnnotationPolicy;
13
+ transition: CloseoutTransitionPolicy;
14
+ };
15
+ }
16
+ export interface ComposedPurposePolicy {
17
+ purposes: BindingPurpose[];
18
+ fields: Record<SharedRemoteField, FieldDirection[]>;
19
+ lifecycle: OatPjmRemoteOperationClass[];
20
+ closeout: {
21
+ annotation: CloseoutAnnotationPolicy;
22
+ transition: CloseoutTransitionPolicy | 'choice-required';
23
+ };
24
+ choiceRequired: boolean;
25
+ noOp: boolean;
26
+ }
27
+ export declare const PURPOSE_POLICIES: Readonly<Record<BindingPurpose, PurposePolicy>>;
28
+ export declare function composePurposePolicies(purposes: readonly BindingPurpose[]): ComposedPurposePolicy;
29
+ //# sourceMappingURL=purpose-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purpose-policy.d.ts","sourceRoot":"","sources":["../../../../src/commands/pjm/remote/purpose-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,aAAa,GAAG,UAAU,CAAC;AACrE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,CAAC;AACpD,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,MAAM,CAAC;AAC1D,MAAM,MAAM,wBAAwB,GAChC,SAAS,GACT,qBAAqB,GACrB,MAAM,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,cAAc,EAAE,CAAC,CAAC;IAC7D,SAAS,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACjD,QAAQ,EAAE;QACR,UAAU,EAAE,wBAAwB,CAAC;QACrC,UAAU,EAAE,wBAAwB,CAAC;KACtC,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,iBAAiB,EAAE,cAAc,EAAE,CAAC,CAAC;IACpD,SAAS,EAAE,0BAA0B,EAAE,CAAC;IACxC,QAAQ,EAAE;QACR,UAAU,EAAE,wBAAwB,CAAC;QACrC,UAAU,EAAE,wBAAwB,GAAG,iBAAiB,CAAC;KAC1D,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAWD,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,aAAa,CAAC,CAkDzE,CAAC;AAkBL,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,qBAAqB,CA2DvB"}
@@ -0,0 +1,118 @@
1
+ const INBOUND = Object.freeze(['inbound']);
2
+ const BIDIRECTIONAL = Object.freeze(['inbound', 'outbound']);
3
+ const NO_DIRECTIONS = Object.freeze([]);
4
+ const CLOSEOUT_LIFECYCLE = Object.freeze([
5
+ 'annotate',
6
+ 'transition',
7
+ ]);
8
+ const NO_LIFECYCLE = Object.freeze([]);
9
+ export const PURPOSE_POLICIES = Object.freeze({
10
+ source: Object.freeze({
11
+ fields: Object.freeze({
12
+ title: INBOUND,
13
+ description: INBOUND,
14
+ priority: INBOUND,
15
+ }),
16
+ lifecycle: CLOSEOUT_LIFECYCLE,
17
+ closeout: Object.freeze({
18
+ annotation: 'propose',
19
+ transition: 'propose',
20
+ }),
21
+ }),
22
+ planning: Object.freeze({
23
+ fields: Object.freeze({
24
+ title: BIDIRECTIONAL,
25
+ description: BIDIRECTIONAL,
26
+ priority: BIDIRECTIONAL,
27
+ }),
28
+ lifecycle: CLOSEOUT_LIFECYCLE,
29
+ closeout: Object.freeze({
30
+ annotation: 'propose',
31
+ transition: 'propose',
32
+ }),
33
+ }),
34
+ delivery: Object.freeze({
35
+ fields: Object.freeze({
36
+ title: NO_DIRECTIONS,
37
+ description: NO_DIRECTIONS,
38
+ priority: NO_DIRECTIONS,
39
+ }),
40
+ lifecycle: NO_LIFECYCLE,
41
+ closeout: Object.freeze({
42
+ annotation: 'none',
43
+ transition: 'provider-automation',
44
+ }),
45
+ }),
46
+ reference: Object.freeze({
47
+ fields: Object.freeze({
48
+ title: NO_DIRECTIONS,
49
+ description: NO_DIRECTIONS,
50
+ priority: NO_DIRECTIONS,
51
+ }),
52
+ lifecycle: NO_LIFECYCLE,
53
+ closeout: Object.freeze({
54
+ annotation: 'none',
55
+ transition: 'none',
56
+ }),
57
+ }),
58
+ });
59
+ const SHARED_FIELDS = [
60
+ 'title',
61
+ 'description',
62
+ 'priority',
63
+ ];
64
+ const OPERATION_ORDER = [
65
+ 'create',
66
+ 'update-fields',
67
+ 'annotate',
68
+ 'transition',
69
+ 'delete',
70
+ 'relink',
71
+ 'detach',
72
+ 'recreate',
73
+ ];
74
+ export function composePurposePolicies(purposes) {
75
+ if (purposes.length === 0) {
76
+ throw new Error('A binding must declare at least one purpose.');
77
+ }
78
+ const uniquePurposes = new Set();
79
+ for (const purpose of purposes) {
80
+ if (!(purpose in PURPOSE_POLICIES)) {
81
+ throw new Error(`Unknown purpose '${String(purpose)}'.`);
82
+ }
83
+ if (uniquePurposes.has(purpose)) {
84
+ throw new Error('Binding purposes must be unique.');
85
+ }
86
+ uniquePurposes.add(purpose);
87
+ }
88
+ const policies = purposes.map((purpose) => PURPOSE_POLICIES[purpose]);
89
+ const fields = Object.fromEntries(SHARED_FIELDS.map((field) => [
90
+ field,
91
+ intersectOrdered(policies.map((policy) => policy.fields[field]), ['inbound', 'outbound']),
92
+ ]));
93
+ const lifecycle = intersectOrdered(policies.map((policy) => policy.lifecycle), OPERATION_ORDER);
94
+ const annotation = policies.every((policy) => policy.closeout.annotation === 'propose')
95
+ ? 'propose'
96
+ : 'none';
97
+ const transitionValues = new Set(policies.map((policy) => policy.closeout.transition));
98
+ const nonNoneTransitions = new Set([...transitionValues].filter((value) => value !== 'none'));
99
+ const transition = transitionValues.has('none')
100
+ ? 'none'
101
+ : nonNoneTransitions.size > 1
102
+ ? 'choice-required'
103
+ : ([...nonNoneTransitions][0] ?? 'none');
104
+ const choiceRequired = transition === 'choice-required';
105
+ const noOp = SHARED_FIELDS.every((field) => fields[field].length === 0) &&
106
+ lifecycle.length === 0;
107
+ return {
108
+ purposes: [...purposes],
109
+ fields,
110
+ lifecycle,
111
+ closeout: { annotation, transition },
112
+ choiceRequired,
113
+ noOp,
114
+ };
115
+ }
116
+ function intersectOrdered(values, order) {
117
+ return order.filter((candidate) => values.every((value) => value.includes(candidate)));
118
+ }
@@ -0,0 +1,42 @@
1
+ import type { OatPjmRemoteDescriptionMode } from '../../../config/oat-config.js';
2
+ import type { FieldDirection, SharedRemoteField } from './purpose-policy.js';
3
+ export type ReconciliationClass = 'no-change' | 'local-only' | 'remote-only' | 'converged' | 'disjoint' | 'conflict' | 'remote-anomaly' | 'uncertain-operation';
4
+ export type ReconciliationDirection = 'inbound' | 'outbound' | 'none' | 'choice-required';
5
+ export type RemoteLifecycleCondition = 'active' | 'archived' | 'moved' | 'missing-or-invisible' | 'deleted-confirmed' | 'temporarily-unavailable';
6
+ export interface SharedFieldValues {
7
+ title: string | null;
8
+ description: string | null;
9
+ priority: string | null;
10
+ }
11
+ export interface ReconciliationInput {
12
+ base: SharedFieldValues;
13
+ local: SharedFieldValues;
14
+ remote: SharedFieldValues;
15
+ fieldDirections: Record<SharedRemoteField, readonly FieldDirection[]>;
16
+ descriptionMode: OatPjmRemoteDescriptionMode;
17
+ priorityMapping: boolean;
18
+ remoteLifecycle: RemoteLifecycleCondition;
19
+ uncertainOperation: boolean;
20
+ }
21
+ export interface FieldReconciliation {
22
+ field: SharedRemoteField;
23
+ base: string | null;
24
+ local: string | null;
25
+ remote: string | null;
26
+ classification: Exclude<ReconciliationClass, 'disjoint' | 'remote-anomaly' | 'uncertain-operation'>;
27
+ proposedDirection: ReconciliationDirection;
28
+ }
29
+ export interface BindingReconciliationPreview {
30
+ classification: ReconciliationClass;
31
+ fields: {
32
+ title: FieldReconciliation;
33
+ description: FieldReconciliation & {
34
+ scope: 'none' | 'managed-section' | 'full';
35
+ };
36
+ priority?: FieldReconciliation;
37
+ };
38
+ choiceRequired: boolean;
39
+ blockedBy: string[];
40
+ }
41
+ export declare function reconcileBinding(input: ReconciliationInput): BindingReconciliationPreview;
42
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../../src/commands/pjm/remote/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1E,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,YAAY,GACZ,aAAa,GACb,WAAW,GACX,UAAU,GACV,UAAU,GACV,gBAAgB,GAChB,qBAAqB,CAAC;AAE1B,MAAM,MAAM,uBAAuB,GAC/B,SAAS,GACT,UAAU,GACV,MAAM,GACN,iBAAiB,CAAC;AAEtB,MAAM,MAAM,wBAAwB,GAChC,QAAQ,GACR,UAAU,GACV,OAAO,GACP,sBAAsB,GACtB,mBAAmB,GACnB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,cAAc,EAAE,CAAC,CAAC;IACtE,eAAe,EAAE,2BAA2B,CAAC;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,wBAAwB,CAAC;IAC1C,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,iBAAiB,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,cAAc,EAAE,OAAO,CACrB,mBAAmB,EACnB,UAAU,GAAG,gBAAgB,GAAG,qBAAqB,CACtD,CAAC;IACF,iBAAiB,EAAE,uBAAuB,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,cAAc,EAAE,mBAAmB,CAAC;IACpC,MAAM,EAAE;QACN,KAAK,EAAE,mBAAmB,CAAC;QAC3B,WAAW,EAAE,mBAAmB,GAAG;YACjC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,MAAM,CAAC;SAC5C,CAAC;QACF,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CA6C9B"}
@@ -0,0 +1,101 @@
1
+ export function reconcileBinding(input) {
2
+ const blockedBy = [];
3
+ if (!input.priorityMapping) {
4
+ blockedBy.push('priority-mapping-unavailable');
5
+ }
6
+ const title = reconcileField('title', input);
7
+ const description = {
8
+ ...reconcileField('description', input),
9
+ scope: descriptionScope(input.descriptionMode),
10
+ };
11
+ if (input.descriptionMode === 'none' &&
12
+ description.proposedDirection === 'outbound') {
13
+ description.proposedDirection = 'none';
14
+ }
15
+ const priority = input.priorityMapping
16
+ ? reconcileField('priority', input)
17
+ : undefined;
18
+ const fieldList = [title, description, ...(priority ? [priority] : [])];
19
+ let classification = classifyBinding(fieldList);
20
+ if (input.uncertainOperation) {
21
+ classification = 'uncertain-operation';
22
+ blockedBy.unshift('uncertain-operation');
23
+ blockDirections(fieldList);
24
+ }
25
+ else if (input.remoteLifecycle !== 'active') {
26
+ classification = 'remote-anomaly';
27
+ blockedBy.unshift(`remote-lifecycle:${input.remoteLifecycle}`);
28
+ blockDirections(fieldList);
29
+ }
30
+ return {
31
+ classification,
32
+ fields: {
33
+ title,
34
+ description,
35
+ ...(priority ? { priority } : {}),
36
+ },
37
+ choiceRequired: fieldList.some((field) => field.proposedDirection === 'choice-required'),
38
+ blockedBy,
39
+ };
40
+ }
41
+ function reconcileField(field, input) {
42
+ const base = input.base[field];
43
+ const local = input.local[field];
44
+ const remote = input.remote[field];
45
+ const classification = classifyField(base, local, remote);
46
+ return {
47
+ field,
48
+ base,
49
+ local,
50
+ remote,
51
+ classification,
52
+ proposedDirection: proposedDirection(classification, input.fieldDirections[field]),
53
+ };
54
+ }
55
+ function classifyField(base, local, remote) {
56
+ if (local === remote)
57
+ return local === base ? 'no-change' : 'converged';
58
+ const localChanged = local !== base;
59
+ const remoteChanged = remote !== base;
60
+ if (localChanged && remoteChanged)
61
+ return 'conflict';
62
+ if (localChanged)
63
+ return 'local-only';
64
+ if (remoteChanged)
65
+ return 'remote-only';
66
+ return 'no-change';
67
+ }
68
+ function proposedDirection(classification, allowed) {
69
+ if (classification === 'conflict')
70
+ return 'choice-required';
71
+ if (classification === 'local-only' && allowed.includes('outbound')) {
72
+ return 'outbound';
73
+ }
74
+ if (classification === 'remote-only' && allowed.includes('inbound')) {
75
+ return 'inbound';
76
+ }
77
+ return 'none';
78
+ }
79
+ function classifyBinding(fields) {
80
+ const classes = new Set(fields.map((field) => field.classification));
81
+ if (classes.has('conflict'))
82
+ return 'conflict';
83
+ if (classes.has('local-only') && classes.has('remote-only'))
84
+ return 'disjoint';
85
+ if (classes.has('local-only'))
86
+ return 'local-only';
87
+ if (classes.has('remote-only'))
88
+ return 'remote-only';
89
+ if (classes.has('converged'))
90
+ return 'converged';
91
+ return 'no-change';
92
+ }
93
+ function descriptionScope(mode) {
94
+ if (mode === 'replace')
95
+ return 'full';
96
+ return mode;
97
+ }
98
+ function blockDirections(fields) {
99
+ for (const field of fields)
100
+ field.proposedDirection = 'none';
101
+ }
@@ -0,0 +1,122 @@
1
+ import { type RemoteProvider } from './provider.js';
2
+ export interface ResolutionIdentity {
3
+ stableId: string;
4
+ context: Record<string, string | undefined>;
5
+ aliases: Array<{
6
+ kind: 'url' | 'display' | 'key';
7
+ value: string;
8
+ }>;
9
+ }
10
+ export interface ResolutionBinding {
11
+ bindingId: string;
12
+ provider: RemoteProvider;
13
+ targetRef: string;
14
+ remoteIdentity: ResolutionIdentity;
15
+ identityHistory: Array<{
16
+ provider: RemoteProvider;
17
+ identity: ResolutionIdentity;
18
+ replacedAt: string;
19
+ replacedByOperationId: string;
20
+ }>;
21
+ lifecycle: 'active' | 'blocked' | 'tombstoned';
22
+ snapshotDigest: string | null;
23
+ }
24
+ export interface ResolutionApproval {
25
+ previewDigest: string;
26
+ approvedAt: string;
27
+ source: string;
28
+ }
29
+ export interface ResolutionJournal {
30
+ schemaVersion: 1;
31
+ operationId: string;
32
+ bindingId: string;
33
+ kind: 'relink' | 'detach' | 'recreate';
34
+ previewDigest: string;
35
+ state: 'pending' | 'blocked' | 'partial' | 'uncertain' | 'complete';
36
+ bindingTransitionCompleted: boolean;
37
+ associationCompleted: boolean;
38
+ resultingBinding: ResolutionBinding | null;
39
+ searchCompleted?: boolean;
40
+ createIntentPersisted?: boolean;
41
+ createAttempted?: boolean;
42
+ createdAt: string;
43
+ updatedAt: string;
44
+ }
45
+ export interface ResolutionStore {
46
+ findByIdentity(provider: RemoteProvider, identity: ResolutionIdentity): Promise<string | null>;
47
+ writeBinding(binding: ResolutionBinding): Promise<void>;
48
+ writeAssociation(input: {
49
+ targetRef: string;
50
+ bindingId: string | null;
51
+ referenceRef: string | null;
52
+ }): Promise<void>;
53
+ readJournal(operationId: string): Promise<ResolutionJournal | null>;
54
+ writeJournal(journal: ResolutionJournal): Promise<void>;
55
+ }
56
+ interface CommonResolutionInput {
57
+ operationId: string;
58
+ binding: ResolutionBinding;
59
+ now: string;
60
+ previewDigest: string;
61
+ approval?: ResolutionApproval;
62
+ crash?: (point: 'after-binding' | 'after-association') => void;
63
+ }
64
+ export declare function relinkBinding(input: CommonResolutionInput & {
65
+ replacement: {
66
+ identity: ResolutionIdentity;
67
+ verifiedAt: string;
68
+ evidenceDigest: string;
69
+ };
70
+ }, store: ResolutionStore): Promise<{
71
+ binding: ResolutionBinding;
72
+ journal: ResolutionJournal;
73
+ }>;
74
+ export declare function detachBinding(input: CommonResolutionInput & {
75
+ keepReference: boolean;
76
+ }, store: ResolutionStore): Promise<{
77
+ binding: ResolutionBinding;
78
+ journal: ResolutionJournal;
79
+ }>;
80
+ export type DuplicateSearchOutcome = {
81
+ kind: 'found-existing';
82
+ replacement: {
83
+ identity: ResolutionIdentity;
84
+ verifiedAt: string;
85
+ evidenceDigest: string;
86
+ };
87
+ } | {
88
+ kind: 'search-unavailable';
89
+ } | {
90
+ kind: 'ambiguous';
91
+ candidates: string[];
92
+ } | {
93
+ kind: 'no-match';
94
+ };
95
+ export type RecreateAttemptOutcome = {
96
+ kind: 'committed';
97
+ replacement: {
98
+ identity: ResolutionIdentity;
99
+ verifiedAt: string;
100
+ evidenceDigest: string;
101
+ };
102
+ } | {
103
+ kind: 'uncertain';
104
+ } | {
105
+ kind: 'rejected';
106
+ };
107
+ export declare function recreateBinding(input: CommonResolutionInput & {
108
+ searchDuplicates(): Promise<DuplicateSearchOutcome>;
109
+ createReplacement(): Promise<RecreateAttemptOutcome>;
110
+ }, store: ResolutionStore): Promise<{
111
+ status: 'verified' | 'blocked' | 'uncertain';
112
+ binding: ResolutionBinding;
113
+ journal: ResolutionJournal;
114
+ }>;
115
+ export declare function buildResolutionPreviewDigest(input: {
116
+ kind: ResolutionJournal['kind'];
117
+ binding: ResolutionBinding;
118
+ replacement?: ResolutionIdentity;
119
+ keepReference?: boolean;
120
+ }): string;
121
+ export {};
122
+ //# sourceMappingURL=resolution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolution.d.ts","sourceRoot":"","sources":["../../../../src/commands/pjm/remote/resolution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,243 @@
1
+ import { semanticDigest } from './provider.js';
2
+ export async function relinkBinding(input, store) {
3
+ assertFreshApproval(input);
4
+ assertVerifiedReplacement(input.replacement);
5
+ const duplicate = await store.findByIdentity(input.binding.provider, input.replacement.identity);
6
+ if (duplicate && duplicate !== input.binding.bindingId) {
7
+ throw new Error(`Replacement identity is already bound by '${duplicate}'.`);
8
+ }
9
+ let journal = await store.readJournal(input.operationId);
10
+ assertJournalMatches(journal, input, 'relink');
11
+ if (!journal) {
12
+ journal = createJournal(input, 'relink');
13
+ await store.writeJournal(journal);
14
+ }
15
+ if (!journal.bindingTransitionCompleted) {
16
+ const nextBinding = {
17
+ ...input.binding,
18
+ remoteIdentity: structuredClone(input.replacement.identity),
19
+ identityHistory: [
20
+ ...input.binding.identityHistory,
21
+ {
22
+ provider: input.binding.provider,
23
+ identity: structuredClone(input.binding.remoteIdentity),
24
+ replacedAt: input.now,
25
+ replacedByOperationId: input.operationId,
26
+ },
27
+ ],
28
+ lifecycle: 'active',
29
+ };
30
+ await store.writeBinding(nextBinding);
31
+ journal = {
32
+ ...journal,
33
+ state: 'partial',
34
+ bindingTransitionCompleted: true,
35
+ resultingBinding: nextBinding,
36
+ updatedAt: input.now,
37
+ };
38
+ await store.writeJournal(journal);
39
+ input.crash?.('after-binding');
40
+ }
41
+ if (!journal.associationCompleted) {
42
+ await store.writeAssociation({
43
+ targetRef: input.binding.targetRef,
44
+ bindingId: input.binding.bindingId,
45
+ referenceRef: null,
46
+ });
47
+ journal = completeAssociation(journal, input.now);
48
+ await store.writeJournal(journal);
49
+ input.crash?.('after-association');
50
+ }
51
+ return { binding: journal.resultingBinding, journal };
52
+ }
53
+ export async function detachBinding(input, store) {
54
+ assertFreshApproval(input);
55
+ let journal = await store.readJournal(input.operationId);
56
+ assertJournalMatches(journal, input, 'detach');
57
+ if (!journal) {
58
+ journal = createJournal(input, 'detach');
59
+ await store.writeJournal(journal);
60
+ }
61
+ if (!journal.bindingTransitionCompleted) {
62
+ const nextBinding = {
63
+ ...input.binding,
64
+ lifecycle: 'tombstoned',
65
+ };
66
+ await store.writeBinding(nextBinding);
67
+ journal = {
68
+ ...journal,
69
+ state: 'partial',
70
+ bindingTransitionCompleted: true,
71
+ resultingBinding: nextBinding,
72
+ updatedAt: input.now,
73
+ };
74
+ await store.writeJournal(journal);
75
+ input.crash?.('after-binding');
76
+ }
77
+ if (!journal.associationCompleted) {
78
+ await store.writeAssociation({
79
+ targetRef: input.binding.targetRef,
80
+ bindingId: null,
81
+ referenceRef: input.keepReference
82
+ ? `${input.binding.provider}:${input.binding.remoteIdentity.stableId}`
83
+ : null,
84
+ });
85
+ journal = completeAssociation(journal, input.now);
86
+ await store.writeJournal(journal);
87
+ input.crash?.('after-association');
88
+ }
89
+ return { binding: journal.resultingBinding, journal };
90
+ }
91
+ export async function recreateBinding(input, store) {
92
+ assertFreshApproval(input);
93
+ let journal = await store.readJournal(input.operationId);
94
+ assertJournalMatches(journal, input, 'recreate');
95
+ if (journal?.createAttempted && !journal.bindingTransitionCompleted) {
96
+ return {
97
+ status: 'uncertain',
98
+ binding: { ...input.binding, lifecycle: 'blocked' },
99
+ journal,
100
+ };
101
+ }
102
+ if (!journal) {
103
+ journal = createJournal(input, 'recreate');
104
+ await store.writeJournal(journal);
105
+ }
106
+ const search = await input.searchDuplicates();
107
+ journal = {
108
+ ...journal,
109
+ searchCompleted: true,
110
+ updatedAt: input.now,
111
+ };
112
+ await store.writeJournal(journal);
113
+ if (search.kind === 'search-unavailable' || search.kind === 'ambiguous') {
114
+ journal = { ...journal, state: 'blocked', updatedAt: input.now };
115
+ await store.writeJournal(journal);
116
+ return { status: 'blocked', binding: input.binding, journal };
117
+ }
118
+ let replacement = null;
119
+ if (search.kind === 'found-existing') {
120
+ replacement = search.replacement;
121
+ }
122
+ else {
123
+ journal = {
124
+ ...journal,
125
+ createIntentPersisted: true,
126
+ updatedAt: input.now,
127
+ };
128
+ await store.writeJournal(journal);
129
+ journal = { ...journal, createAttempted: true, updatedAt: input.now };
130
+ await store.writeJournal(journal);
131
+ const create = await input.createReplacement();
132
+ if (create.kind !== 'committed') {
133
+ journal = {
134
+ ...journal,
135
+ state: create.kind === 'uncertain' ? 'uncertain' : 'blocked',
136
+ updatedAt: input.now,
137
+ };
138
+ await store.writeJournal(journal);
139
+ return {
140
+ status: create.kind === 'uncertain' ? 'uncertain' : 'blocked',
141
+ binding: { ...input.binding, lifecycle: 'blocked' },
142
+ journal,
143
+ };
144
+ }
145
+ replacement = create.replacement;
146
+ }
147
+ assertVerifiedReplacement(replacement);
148
+ const duplicate = await store.findByIdentity(input.binding.provider, replacement.identity);
149
+ if (duplicate && duplicate !== input.binding.bindingId) {
150
+ throw new Error(`Replacement identity is already bound by '${duplicate}'.`);
151
+ }
152
+ if (!journal.bindingTransitionCompleted) {
153
+ const nextBinding = {
154
+ ...input.binding,
155
+ remoteIdentity: structuredClone(replacement.identity),
156
+ identityHistory: [
157
+ ...input.binding.identityHistory,
158
+ {
159
+ provider: input.binding.provider,
160
+ identity: structuredClone(input.binding.remoteIdentity),
161
+ replacedAt: input.now,
162
+ replacedByOperationId: input.operationId,
163
+ },
164
+ ],
165
+ lifecycle: 'active',
166
+ };
167
+ await store.writeBinding(nextBinding);
168
+ journal = {
169
+ ...journal,
170
+ state: 'partial',
171
+ bindingTransitionCompleted: true,
172
+ resultingBinding: nextBinding,
173
+ updatedAt: input.now,
174
+ };
175
+ await store.writeJournal(journal);
176
+ input.crash?.('after-binding');
177
+ }
178
+ if (!journal.associationCompleted) {
179
+ await store.writeAssociation({
180
+ targetRef: input.binding.targetRef,
181
+ bindingId: input.binding.bindingId,
182
+ referenceRef: null,
183
+ });
184
+ journal = completeAssociation(journal, input.now);
185
+ await store.writeJournal(journal);
186
+ input.crash?.('after-association');
187
+ }
188
+ return { status: 'verified', binding: journal.resultingBinding, journal };
189
+ }
190
+ function assertJournalMatches(journal, input, kind) {
191
+ if (journal &&
192
+ (journal.kind !== kind ||
193
+ journal.bindingId !== input.binding.bindingId ||
194
+ journal.previewDigest !== input.previewDigest)) {
195
+ throw new Error('Persisted resolution journal does not match the requested kind, binding, and preview.');
196
+ }
197
+ }
198
+ function assertFreshApproval(input) {
199
+ const approval = input.approval;
200
+ const age = approval
201
+ ? Date.parse(input.now) - Date.parse(approval.approvedAt)
202
+ : Number.POSITIVE_INFINITY;
203
+ if (!approval ||
204
+ approval.previewDigest !== input.previewDigest ||
205
+ !Number.isFinite(age) ||
206
+ age < 0 ||
207
+ age > 5 * 60 * 1_000) {
208
+ throw new Error('Resolution requires fresh approval of the exact preview.');
209
+ }
210
+ }
211
+ function assertVerifiedReplacement(replacement) {
212
+ if (replacement.identity.stableId.length === 0 ||
213
+ replacement.evidenceDigest.length === 0 ||
214
+ !Number.isFinite(Date.parse(replacement.verifiedAt))) {
215
+ throw new Error('Relink requires a verified stable replacement identity.');
216
+ }
217
+ }
218
+ function createJournal(input, kind) {
219
+ return {
220
+ schemaVersion: 1,
221
+ operationId: input.operationId,
222
+ bindingId: input.binding.bindingId,
223
+ kind,
224
+ previewDigest: input.previewDigest,
225
+ state: 'pending',
226
+ bindingTransitionCompleted: false,
227
+ associationCompleted: false,
228
+ resultingBinding: null,
229
+ createdAt: input.now,
230
+ updatedAt: input.now,
231
+ };
232
+ }
233
+ function completeAssociation(journal, updatedAt) {
234
+ return {
235
+ ...journal,
236
+ state: 'complete',
237
+ associationCompleted: true,
238
+ updatedAt,
239
+ };
240
+ }
241
+ export function buildResolutionPreviewDigest(input) {
242
+ return semanticDigest(input);
243
+ }