@company-semantics/contracts 79.2.0 → 80.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "79.2.0",
3
+ "version": "80.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = 'c8b7d9ff0c4f' as const;
3
- export const SPEC_HASH_FULL = 'c8b7d9ff0c4fda27f5db2cc4afa34e2b80015a930d36e2a6d5106987285b503a' as const;
2
+ export const SPEC_HASH = 'c2954b6d06a9' as const;
3
+ export const SPEC_HASH_FULL = 'c2954b6d06a981632d4d61991aa61f16ee28485116ec66949fe08d64cbde4ceb' as const;
@@ -8653,6 +8653,7 @@ export interface components {
8653
8653
  /** @enum {string} */
8654
8654
  recommendedUse: "attached" | "direction_only" | "excluded";
8655
8655
  use: ("attached" | "direction_only" | "excluded") | null;
8656
+ included: boolean;
8656
8657
  treatmentUnresolved: boolean;
8657
8658
  matchReason: string;
8658
8659
  unreadableReason: ("unsupported_type" | "needs_ocr" | "unsupported_kind") | null;
@@ -8690,7 +8691,11 @@ export interface components {
8690
8691
  };
8691
8692
  }[];
8692
8693
  withheldQuestionCount: number;
8693
- unresolvedTreatmentCount: number;
8694
+ totals: {
8695
+ files: number;
8696
+ filesIncluded: number;
8697
+ filesNeedingChoice: number;
8698
+ };
8694
8699
  scopes: {
8695
8700
  scopeKey: string;
8696
8701
  /** @enum {string} */
@@ -127,10 +127,13 @@ way. This directory holds that shared spelling.
127
127
  - `SourceSyncDiscoveryRunResponseSchema` / `SourceSyncDiscoveryRunResponse` —
128
128
  the discovery run started, and the workflow narrating it.
129
129
  - `SourceSyncCandidateItemSchema` / `SourceSyncCandidateItem` — one source
130
- proposed for one target document.
130
+ proposed for one target document, with the server's `included`.
131
131
  - `SourceSyncCandidateSetResponseSchema` / `SourceSyncCandidateSetResponse` —
132
- the draft set under review: its revision, items, presented questions and
133
- cost estimate.
132
+ the draft set under review: its revision, items, presented questions,
133
+ distinct-file `totals` and cost estimate.
134
+ - `SourceSyncCandidateTotalsSchema` / `SourceSyncCandidateTotals` and
135
+ `candidateTotalsOf(items)` — THE file count: distinct `sourceId`, never lines
136
+ (ADR-CONTRACTS-195).
134
137
  - `SourceSyncCandidateItemPatchSchema` / `SourceSyncCandidateItemPatch` — one
135
138
  edit to one candidate item (use, class or target); never empty.
136
139
  - `SourceSyncAddCandidateRequestSchema` / `SourceSyncAddCandidateRequest` —
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { describe, expect, it } from "vitest";
17
17
  import {
18
+ candidateTotalsOf,
18
19
  SourceSyncCandidateItemSchema,
19
20
  SourceSyncCandidateSetResponseSchema,
20
21
  SourceSyncDiscoveryRunResponseSchema,
@@ -50,6 +51,7 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
50
51
  audienceFit: "fits",
51
52
  recommendedUse: "direction_only",
52
53
  use: null,
54
+ included: true,
53
55
  treatmentUnresolved: false,
54
56
  matchReason: "A board deck for the current quarter.",
55
57
  unreadableReason: null,
@@ -96,7 +98,7 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
96
98
  items: [candidateItem()],
97
99
  questions: [presentedQuestion()],
98
100
  withheldQuestionCount: 0,
99
- unresolvedTreatmentCount: 0,
101
+ totals: { files: 1, filesIncluded: 1, filesNeedingChoice: 0 },
100
102
  scopes: [],
101
103
  estimate: {
102
104
  lowMicroUsd: "250000",
@@ -279,3 +281,16 @@ describe("SourceSyncDiscoveryRunResponseSchema", () => {
279
281
  ).toBe(true);
280
282
  });
281
283
  });
284
+
285
+ describe("candidateTotalsOf", () => {
286
+ it("counts distinct files, never lines", () => {
287
+ expect(
288
+ candidateTotalsOf([
289
+ { sourceId: "a", included: true, treatmentUnresolved: true },
290
+ { sourceId: "a", included: true, treatmentUnresolved: false },
291
+ { sourceId: "b", included: false, treatmentUnresolved: true },
292
+ { sourceId: "c", included: true, treatmentUnresolved: false },
293
+ ]),
294
+ ).toEqual({ files: 3, filesIncluded: 2, filesNeedingChoice: 1 });
295
+ });
296
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * THE ONE FILE COUNT for a candidate review: distinct `sourceId`, never lines.
3
+ *
4
+ * A file routed to two company.md documents is two lines and ONE file. The
5
+ * server computes `SourceSyncCandidateSetResponse.totals` with this over every
6
+ * line the reader may see; a client that overlays an optimistic edit recomputes
7
+ * with the same function, so no surface counts "files" its own way.
8
+ */
9
+
10
+ import type {
11
+ SourceSyncCandidateItem,
12
+ SourceSyncCandidateTotals,
13
+ } from "./schemas";
14
+
15
+ /** The item facts the count reads. */
16
+ export type CandidateTotalsInput = Pick<
17
+ SourceSyncCandidateItem,
18
+ "sourceId" | "included" | "treatmentUnresolved"
19
+ >;
20
+
21
+ /**
22
+ * Included when any of the file's lines is; in need of a choice when any
23
+ * included line's treatment is unresolved.
24
+ */
25
+ export function candidateTotalsOf(
26
+ items: readonly CandidateTotalsInput[],
27
+ ): SourceSyncCandidateTotals {
28
+ const files = new Set<string>();
29
+ const included = new Set<string>();
30
+ const needingChoice = new Set<string>();
31
+ for (const item of items) {
32
+ files.add(item.sourceId);
33
+ if (!item.included) continue;
34
+ included.add(item.sourceId);
35
+ if (item.treatmentUnresolved) needingChoice.add(item.sourceId);
36
+ }
37
+ return {
38
+ files: files.size,
39
+ filesIncluded: included.size,
40
+ filesNeedingChoice: needingChoice.size,
41
+ };
42
+ }
@@ -48,6 +48,7 @@ export type {
48
48
  SourceSyncDiscoveryRunResponse,
49
49
  SourceSyncCandidateItem,
50
50
  SourceSyncCandidateSetResponse,
51
+ SourceSyncCandidateTotals,
51
52
  SourceSyncCandidateItemPatch,
52
53
  SourceSyncAddCandidateRequest,
53
54
  SourceSyncApproveRequest,
@@ -69,6 +70,7 @@ export {
69
70
  SourceSyncDiscoveryRunResponseSchema,
70
71
  SourceSyncCandidateItemSchema,
71
72
  SourceSyncCandidateSetResponseSchema,
73
+ SourceSyncCandidateTotalsSchema,
72
74
  SourceSyncCandidateItemPatchSchema,
73
75
  SourceSyncAddCandidateRequestSchema,
74
76
  SourceSyncApproveRequestSchema,
@@ -139,3 +141,6 @@ export {
139
141
  SourceSyncReachUpgradeRequestSchema,
140
142
  SourceSyncReachUpgradeResponseSchema,
141
143
  } from "./reach";
144
+
145
+ export type { CandidateTotalsInput } from "./candidate-totals";
146
+ export { candidateTotalsOf } from "./candidate-totals";
@@ -118,6 +118,12 @@ export const SourceSyncCandidateItemSchema = z.object({
118
118
  recommendedUse: z.enum(SOURCE_USES),
119
119
  /** What the person has chosen; null until they (or an accepted default) decide. */
120
120
  use: z.enum(SOURCE_USES).nullable(),
121
+ /**
122
+ * Whether this line is in the set that approval would land — the SERVER's
123
+ * answer, never derived from `use`. A decoy, a series' history or a line a
124
+ * person left out is false while its `use` is still null.
125
+ */
126
+ included: z.boolean(),
121
127
  /** True while a `widens` item — or one whose fit is not computed yet — has no person-chosen use: approval is refused until none remain. */
122
128
  treatmentUnresolved: z.boolean(),
123
129
  /** Product-owned copy saying why this file was proposed. Never model-authored prose. */
@@ -128,6 +134,16 @@ export const SourceSyncCandidateItemSchema = z.object({
128
134
  .nullable(),
129
135
  });
130
136
 
137
+ /** Distinct-file totals over every line of a candidate set the caller may see. */
138
+ export const SourceSyncCandidateTotalsSchema = z.object({
139
+ /** Files in the review: at least one line, included or left out. */
140
+ files: z.number().int().min(0),
141
+ /** Files with at least one included line. */
142
+ filesIncluded: z.number().int().min(0),
143
+ /** Files with at least one included line whose treatment is unresolved. */
144
+ filesNeedingChoice: z.number().int().min(0),
145
+ });
146
+
131
147
  /** The draft set under review. `revision` moves on every edit and is echoed on approve. */
132
148
  export const SourceSyncCandidateSetResponseSchema = z.object({
133
149
  setId: z.string().uuid(),
@@ -138,7 +154,12 @@ export const SourceSyncCandidateSetResponseSchema = z.object({
138
154
  /** The low-confidence routing, class and treatment questions, as presented. */
139
155
  questions: z.array(DecisionQuestionPresentationSchema).max(50),
140
156
  withheldQuestionCount: z.number().int().min(0),
141
- unresolvedTreatmentCount: z.number().int().min(0),
157
+ /**
158
+ * The review's top-level totals, counted by DISTINCT FILE (`sourceId`) — never
159
+ * by line. A file routed to two documents is two lines and one file. Every
160
+ * surface reads these rather than counting `items`, which may be capped.
161
+ */
162
+ totals: SourceSyncCandidateTotalsSchema,
142
163
  /** Every scope of the set the caller may see, and where its approval stands. */
143
164
  scopes: z.array(SourceSyncScopeApprovalProjectionSchema).max(500),
144
165
  /** A RANGE, and the cap that is the promise. Null until the estimator has run. */
@@ -378,6 +399,9 @@ export type SourceSyncCandidateItem = z.infer<
378
399
  export type SourceSyncCandidateSetResponse = z.infer<
379
400
  typeof SourceSyncCandidateSetResponseSchema
380
401
  >;
402
+ export type SourceSyncCandidateTotals = z.infer<
403
+ typeof SourceSyncCandidateTotalsSchema
404
+ >;
381
405
  export type SourceSyncCandidateItemPatch = z.infer<
382
406
  typeof SourceSyncCandidateItemPatchSchema
383
407
  >;