@descryy/core 0.1.1 → 0.2.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.
Files changed (72) hide show
  1. package/dist/budget/index.d.ts +3 -0
  2. package/dist/budget/index.d.ts.map +1 -0
  3. package/dist/budget/index.js +2 -0
  4. package/dist/budget/index.js.map +1 -0
  5. package/dist/budget/size.d.ts +43 -0
  6. package/dist/budget/size.d.ts.map +1 -0
  7. package/dist/budget/size.js +52 -0
  8. package/dist/budget/size.js.map +1 -0
  9. package/dist/contracts/engine.d.ts.map +1 -1
  10. package/dist/contracts/engine.js +16 -9
  11. package/dist/contracts/engine.js.map +1 -1
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/multipr/mechanical.d.ts.map +1 -1
  17. package/dist/multipr/mechanical.js +1 -0
  18. package/dist/multipr/mechanical.js.map +1 -1
  19. package/dist/query/confirmed-facts.d.ts +21 -5
  20. package/dist/query/confirmed-facts.d.ts.map +1 -1
  21. package/dist/query/confirmed-facts.js +30 -7
  22. package/dist/query/confirmed-facts.js.map +1 -1
  23. package/dist/query/index.d.ts +2 -0
  24. package/dist/query/index.d.ts.map +1 -1
  25. package/dist/query/index.js +1 -0
  26. package/dist/query/index.js.map +1 -1
  27. package/dist/query/memory.d.ts.map +1 -1
  28. package/dist/query/memory.js +4 -0
  29. package/dist/query/memory.js.map +1 -1
  30. package/dist/query/provider.d.ts +7 -0
  31. package/dist/query/provider.d.ts.map +1 -1
  32. package/dist/query/provider.js.map +1 -1
  33. package/dist/query/queries.d.ts.map +1 -1
  34. package/dist/query/queries.js +39 -15
  35. package/dist/query/queries.js.map +1 -1
  36. package/dist/query/resolution-floor.d.ts +36 -0
  37. package/dist/query/resolution-floor.d.ts.map +1 -0
  38. package/dist/query/resolution-floor.js +39 -0
  39. package/dist/query/resolution-floor.js.map +1 -0
  40. package/dist/query/sqlite.d.ts.map +1 -1
  41. package/dist/query/sqlite.js +1 -0
  42. package/dist/query/sqlite.js.map +1 -1
  43. package/dist/query/traverse.d.ts.map +1 -1
  44. package/dist/query/traverse.js +0 -0
  45. package/dist/query/traverse.js.map +1 -1
  46. package/dist/scoping/traverse.d.ts.map +1 -1
  47. package/dist/scoping/traverse.js +18 -4
  48. package/dist/scoping/traverse.js.map +1 -1
  49. package/dist/sources/confirmed/incidents.d.ts +47 -0
  50. package/dist/sources/confirmed/incidents.d.ts.map +1 -0
  51. package/dist/sources/confirmed/incidents.js +173 -0
  52. package/dist/sources/confirmed/incidents.js.map +1 -0
  53. package/dist/sources/confirmed/index.d.ts +3 -0
  54. package/dist/sources/confirmed/index.d.ts.map +1 -0
  55. package/dist/sources/confirmed/index.js +2 -0
  56. package/dist/sources/confirmed/index.js.map +1 -0
  57. package/dist/sources/git/diff.d.ts +18 -0
  58. package/dist/sources/git/diff.d.ts.map +1 -1
  59. package/dist/sources/git/diff.js +37 -1
  60. package/dist/sources/git/diff.js.map +1 -1
  61. package/dist/sources/git/incidents.d.ts +109 -0
  62. package/dist/sources/git/incidents.d.ts.map +1 -0
  63. package/dist/sources/git/incidents.js +187 -0
  64. package/dist/sources/git/incidents.js.map +1 -0
  65. package/dist/sources/git/index.d.ts +3 -1
  66. package/dist/sources/git/index.d.ts.map +1 -1
  67. package/dist/sources/git/index.js +2 -1
  68. package/dist/sources/git/index.js.map +1 -1
  69. package/dist/sources/git/source.d.ts.map +1 -1
  70. package/dist/sources/git/source.js +0 -0
  71. package/dist/sources/git/source.js.map +1 -1
  72. package/package.json +2 -2
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The `IRSource` that projects developer-confirmed incidents into the graph.
3
+ *
4
+ * ## Why a source, and not a direct write
5
+ *
6
+ * A `ConfirmedIncident` lives in `.descry/config.json` because it is testimony
7
+ * — a developer fixed something and said so, and no commit records that they
8
+ * said it (see the type's own doc). But a store is derived data: DEC-017 drops
9
+ * every derived table on a schema change and rebuilds from source. So a
10
+ * confirmed incident written *only* into the graph would survive until the next
11
+ * IR-version bump and then vanish, which is the failure the EARNED partition
12
+ * exists to prevent — arriving through a writer that simply did not think about
13
+ * it.
14
+ *
15
+ * Making this a source solves it by construction. The file is the record; the
16
+ * graph is a projection of the record; every `analyze` re-projects. There is no
17
+ * migration to write and no earned table to extend, and the same discipline
18
+ * every other producer in this system already follows applies unchanged: a
19
+ * batch in, `writeBatch` out, invalidation by producer id.
20
+ *
21
+ * ## Identity
22
+ *
23
+ * The `INCIDENT` node keys on `confirmed:<confirmedAt>:<summary>` — the moment
24
+ * the developer said it, plus what they said. Deliberately **not** the file set:
25
+ * node ids never contain a path (renames churn them and silently destroy
26
+ * incident links), and a developer correcting the file list of an incident they
27
+ * already recorded is editing one incident rather than creating a second.
28
+ *
29
+ * ## What it does not do
30
+ *
31
+ * It mints no `FIX_PATTERN` unless the record names a fixing commit, and it
32
+ * never infers one from HEAD. "The developer confirmed an incident" and "the
33
+ * developer confirmed which commit fixed it" are separate claims, and only the
34
+ * second one licenses a `FIXED_BY` edge.
35
+ */
36
+ import { fileQsp, nodeId, } from "@descryy/ir";
37
+ export const CONFIRMED_INCIDENT_SOURCE_ID = "confirmed-incidents";
38
+ export const CONFIRMED_INCIDENT_SOURCE_VERSION = "0.1.0";
39
+ const PRODUCED_BY = `${CONFIRMED_INCIDENT_SOURCE_ID}@${CONFIRMED_INCIDENT_SOURCE_VERSION}`;
40
+ /** The `INCIDENT` node id for one record. Stable across re-projections. */
41
+ export function confirmedIncidentId(scope, incident) {
42
+ return nodeId(scope, "INCIDENT", `confirmed:${incident.confirmedAt}:${incident.summary}`, null);
43
+ }
44
+ export function createConfirmedIncidentSource(options) {
45
+ return {
46
+ id: CONFIRMED_INCIDENT_SOURCE_ID,
47
+ version: CONFIRMED_INCIDENT_SOURCE_VERSION,
48
+ emit(ctx) {
49
+ const repo = options.repo || ctx.root.repo;
50
+ // INCIDENT and FIX_PATTERN are workspace-scoped (DEC-054), FILE is
51
+ // repo-scoped, and `scopeOf` picks between them per type.
52
+ const scope = { repo, workspace: ctx.root.workspace };
53
+ // A record for another repository is not this batch's business. The
54
+ // structural refusal `applyConfirmedFacts` makes for the same reason:
55
+ // never an inference about a second case.
56
+ const mine = options.incidents.filter((i) => i.repo === repo);
57
+ const nodes = [];
58
+ const edges = [];
59
+ const unresolved = [];
60
+ const touched = new Set();
61
+ for (const incident of mine)
62
+ for (const path of incident.files)
63
+ touched.add(path);
64
+ const sourceFiles = [...touched].sort();
65
+ for (const path of sourceFiles) {
66
+ nodes.push({
67
+ id: nodeId(scope, "FILE", fileQsp(path), null),
68
+ type: "FILE",
69
+ name: path.split("/").pop() ?? path,
70
+ file: path,
71
+ range: null,
72
+ // A developer naming a path says nothing about what language it is
73
+ // written in, and guessing from the extension would be a language
74
+ // decision made where none may be made (DEC-026).
75
+ language: null,
76
+ producedBy: PRODUCED_BY,
77
+ resolution: 0,
78
+ attrs: {},
79
+ });
80
+ }
81
+ for (const incident of mine) {
82
+ const incidentId = confirmedIncidentId(scope, incident);
83
+ nodes.push({
84
+ id: incidentId,
85
+ type: "INCIDENT",
86
+ name: incident.summary.slice(0, 120),
87
+ file: null,
88
+ range: null,
89
+ language: null,
90
+ producedBy: PRODUCED_BY,
91
+ resolution: 0,
92
+ attrs: {
93
+ // The record's own shape decides this, never the caller: a run id
94
+ // is present exactly when a run witnessed the failure, and absent
95
+ // exactly when a person asserted it.
96
+ detectedVia: incident.observedInRun === undefined ? "developer-confirmed" : "runtime-observed",
97
+ ...(incident.observedInRun === undefined ? {} : { observedInRun: incident.observedInRun }),
98
+ confirmedBy: incident.confirmedBy,
99
+ confirmedAt: incident.confirmedAt,
100
+ // The message-similarity signal's real input, which until now had
101
+ // only a commit subject line to work with.
102
+ ...(incident.errorText === undefined ? {} : { errorText: incident.errorText }),
103
+ ...(incident.fixedAtCommit === undefined ? {} : { fixedAtCommit: incident.fixedAtCommit }),
104
+ },
105
+ });
106
+ for (const path of [...new Set(incident.files)].sort()) {
107
+ edges.push({
108
+ from: incidentId,
109
+ to: nodeId(scope, "FILE", fileQsp(path), null),
110
+ type: "INCIDENT_CORRELATED",
111
+ resolution: 0,
112
+ confidence: 1,
113
+ producedBy: PRODUCED_BY,
114
+ attrs: {
115
+ detectedVia: incident.observedInRun === undefined ? "developer-confirmed" : "runtime-observed",
116
+ },
117
+ });
118
+ }
119
+ // Only when the record names one. "A defect existed" and "this commit
120
+ // fixed it" are separate claims and only the second licenses this edge.
121
+ if (incident.fixedAtCommit === undefined)
122
+ continue;
123
+ const fixId = nodeId(scope, "FIX_PATTERN", incident.fixedAtCommit, null);
124
+ nodes.push({
125
+ id: fixId,
126
+ type: "FIX_PATTERN",
127
+ name: `fix for: ${incident.summary}`.slice(0, 120),
128
+ file: null,
129
+ range: null,
130
+ language: null,
131
+ producedBy: PRODUCED_BY,
132
+ resolution: 0,
133
+ attrs: {
134
+ commitSha: incident.fixedAtCommit,
135
+ detectedVia: incident.observedInRun === undefined ? "developer-confirmed" : "runtime-observed",
136
+ },
137
+ });
138
+ edges.push({
139
+ from: incidentId,
140
+ to: fixId,
141
+ type: "FIXED_BY",
142
+ resolution: 0,
143
+ confidence: 1,
144
+ producedBy: PRODUCED_BY,
145
+ attrs: {
146
+ detectedVia: incident.observedInRun === undefined ? "developer-confirmed" : "runtime-observed",
147
+ },
148
+ });
149
+ }
150
+ // Deterministic: two projections of the same records must produce a
151
+ // byte-identical batch, or the store's content hash moves for no reason.
152
+ nodes.sort((a, b) => a.id.localeCompare(b.id));
153
+ edges.sort((a, b) => `${a.from}${a.to}${a.type}`.localeCompare(`${b.from}${b.to}${b.type}`));
154
+ return Promise.resolve({
155
+ repo,
156
+ ...(scope.workspace === undefined ? {} : { workspace: scope.workspace }),
157
+ commitSha: ctx.root.commitSha,
158
+ producedBy: PRODUCED_BY,
159
+ sourceFiles,
160
+ skippedFiles: [],
161
+ // R0. A person's sentence is not a resolution level at all — the
162
+ // reliability consequence lives in `humanAsserted` (DEC-223), a second
163
+ // and independent ceiling, and slotting testimony into the R0–R4 ladder
164
+ // to express it would be the overclaim that ladder exists to prevent.
165
+ reachedResolution: 0,
166
+ nodes,
167
+ edges,
168
+ unresolved,
169
+ });
170
+ },
171
+ };
172
+ }
173
+ //# sourceMappingURL=incidents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incidents.js","sourceRoot":"","sources":["../../../src/sources/confirmed/incidents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createConfirmedIncidentSource, confirmedIncidentId, CONFIRMED_INCIDENT_SOURCE_ID, CONFIRMED_INCIDENT_SOURCE_VERSION, } from "./incidents.ts";
2
+ export type { ConfirmedIncidentSourceOptions } from "./incidents.ts";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sources/confirmed/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,4BAA4B,EAC5B,iCAAiC,GAClC,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createConfirmedIncidentSource, confirmedIncidentId, CONFIRMED_INCIDENT_SOURCE_ID, CONFIRMED_INCIDENT_SOURCE_VERSION, } from "./incidents.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sources/confirmed/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,4BAA4B,EAC5B,iCAAiC,GAClC,MAAM,gBAAgB,CAAC"}
@@ -12,6 +12,13 @@
12
12
  * rather than silently shrinking the reply — the empty-denominator rule
13
13
  * applies to a dropped patch exactly as it does to a dropped finding.
14
14
  *
15
+ * Two size caps, because one of them bounds nothing on its own:
16
+ * `maxPatchBytes` per file, and `maxTotalPatchBytes` across the whole reply.
17
+ * The second exists because a real 48-file branch diff, with 44 patches each
18
+ * *under* the per-file cap, summed to 229KB and hard-failed the call — the
19
+ * reply never reached the caller at all, which is the one failure mode a
20
+ * disclosure cannot repair after the fact.
21
+ *
15
22
  * One `git diff` invocation for every file at once, not one per file — a
16
23
  * PR touching fifty files would otherwise spawn fifty processes. The output
17
24
  * is a concatenation of per-file blocks, each beginning with a line
@@ -23,6 +30,12 @@
23
30
  * own regex — an accepted, narrow gap, not a silent one, since every other
24
31
  * line used here does not share it).
25
32
  */
33
+ /**
34
+ * The stable prefix of `patchOmittedReason` when the *total* budget, not the
35
+ * per-file cap, is what withheld a patch. Exported so a caller can tell the
36
+ * two apart without matching on prose it does not own.
37
+ */
38
+ export declare const TOTAL_PATCH_BUDGET_REASON_PREFIX = "the reply's total patch budget";
26
39
  export interface GitDiffFile {
27
40
  /** Path as it is known after the change (the only path for A/M/D). */
28
41
  readonly path: string;
@@ -43,6 +56,11 @@ export interface GitDiffOptions {
43
56
  readonly files?: readonly string[];
44
57
  /** A file's patch is omitted (stats are still reported) above this many bytes. Default 20,000. */
45
58
  readonly maxPatchBytes?: number;
59
+ /**
60
+ * The cap on the sum of all returned patches. Files past it keep their
61
+ * stats and name the budget as the reason. Default 80,000.
62
+ */
63
+ readonly maxTotalPatchBytes?: number;
46
64
  }
47
65
  /**
48
66
  * `base...head` (three dots), matching `changedFiles`'s own reasoning: two
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwBH,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kGAAkG;IAClG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AA8GD;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,SAAS,EACb,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAEjC;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;CAChD;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAIjC"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAkCH;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,mCAAmC,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kGAAkG;IAClG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AA6HD;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,SAAS,EACb,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAEjC;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;CAChD;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAIjC"}
@@ -12,6 +12,13 @@
12
12
  * rather than silently shrinking the reply — the empty-denominator rule
13
13
  * applies to a dropped patch exactly as it does to a dropped finding.
14
14
  *
15
+ * Two size caps, because one of them bounds nothing on its own:
16
+ * `maxPatchBytes` per file, and `maxTotalPatchBytes` across the whole reply.
17
+ * The second exists because a real 48-file branch diff, with 44 patches each
18
+ * *under* the per-file cap, summed to 229KB and hard-failed the call — the
19
+ * reply never reached the caller at all, which is the one failure mode a
20
+ * disclosure cannot repair after the fact.
21
+ *
15
22
  * One `git diff` invocation for every file at once, not one per file — a
16
23
  * PR touching fifty files would otherwise spawn fifty processes. The output
17
24
  * is a concatenation of per-file blocks, each beginning with a line
@@ -25,6 +32,7 @@
25
32
  */
26
33
  import { execFile } from "node:child_process";
27
34
  import { promisify } from "node:util";
35
+ import { applySerializedSizeBudget } from "../../budget/index.js";
28
36
  import { cleanGitEnv } from "./env.js";
29
37
  const execFileAsync = promisify(execFile);
30
38
  // Always strips GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE/GIT_COMMON_DIR — see env.ts.
@@ -36,6 +44,20 @@ function run(command, args, options) {
36
44
  });
37
45
  }
38
46
  const DEFAULT_MAX_PATCH_BYTES = 20_000;
47
+ /**
48
+ * The cap on the sum of every returned patch. A per-file cap bounds no total:
49
+ * 44 files each under the 20,000-byte per-file cap summed to 229KB and
50
+ * hard-failed an ordinary 48-file branch diff before the reply was ever
51
+ * returned. A starting value, not a measurement — roughly 20k tokens of
52
+ * patch text, leaving a caller's context room for the answer built on it.
53
+ */
54
+ const DEFAULT_MAX_TOTAL_PATCH_BYTES = 80_000;
55
+ /**
56
+ * The stable prefix of `patchOmittedReason` when the *total* budget, not the
57
+ * per-file cap, is what withheld a patch. Exported so a caller can tell the
58
+ * two apart without matching on prose it does not own.
59
+ */
60
+ export const TOTAL_PATCH_BUDGET_REASON_PREFIX = "the reply's total patch budget";
39
61
  function stripPrefix(path, prefix) {
40
62
  return path.startsWith(prefix) ? path.slice(prefix.length) : path;
41
63
  }
@@ -98,6 +120,7 @@ function parseDiffBlock(block) {
98
120
  }
99
121
  async function runGitDiff(cwd, revisionArgs, options) {
100
122
  const maxPatchBytes = options.maxPatchBytes ?? DEFAULT_MAX_PATCH_BYTES;
123
+ const maxTotalPatchBytes = options.maxTotalPatchBytes ?? DEFAULT_MAX_TOTAL_PATCH_BYTES;
101
124
  const args = ["diff", "-M", ...revisionArgs];
102
125
  if (options.files !== undefined && options.files.length > 0)
103
126
  args.push("--", ...options.files);
@@ -130,7 +153,20 @@ async function runGitDiff(cwd, revisionArgs, options) {
130
153
  : null,
131
154
  });
132
155
  }
133
- return files;
156
+ // The aggregate cap, applied after every per-file decision: a file whose
157
+ // patch was already withheld (binary, or over its own cap) costs nothing
158
+ // here and keeps the reason it already has.
159
+ return applySerializedSizeBudget(files, {
160
+ maxTotalBytes: maxTotalPatchBytes,
161
+ sizeOf: (f) => (f.patch === null ? 0 : Buffer.byteLength(f.patch, "utf8")),
162
+ withhold: (f, spentBytes) => ({
163
+ ...f,
164
+ patch: null,
165
+ patchOmittedReason: `${TOTAL_PATCH_BUDGET_REASON_PREFIX} of ${maxTotalPatchBytes} bytes was already spent ` +
166
+ `(${spentBytes} bytes on earlier files) — narrow "files" to this one, or raise ` +
167
+ '"maxTotalPatchBytes"',
168
+ }),
169
+ }).items;
134
170
  }
135
171
  /**
136
172
  * `base...head` (three dots), matching `changedFiles`'s own reasoning: two
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Which commits in a history describe a failure, and how we know.
3
+ *
4
+ * Split out of `source.ts` when the answer stopped being one regex. The git
5
+ * source minted an `INCIDENT` for exactly one thing — a commit body carrying
6
+ * `This reverts commit <40 hex>` — and that produced **zero** incidents across
7
+ * three measured repositories (734/459/203 commits) and two in a real product
8
+ * repository. `similarIncidents` was therefore built, tested, correct and
9
+ * starved: its recall is not 0%, it is not computable, because it has never
10
+ * been given a real incident to find.
11
+ *
12
+ * ## The rule every mechanism here answers to
13
+ *
14
+ * `INCIDENT` is an EARNED node (DEC-017). It is never dropped, it survives
15
+ * every rebuild, and `INCIDENT_CORRELATED` biases diff-scoping traversal toward
16
+ * the files it names for as long as the store exists. A wrong incident is not a
17
+ * bad answer that a later run corrects — it is unrecoverable pollution in the
18
+ * one table that cannot be recomputed away. So:
19
+ *
20
+ * **Every mechanism rests on something a person declared. None rests on
21
+ * something Descry inferred.**
22
+ *
23
+ * A revert trailer is written by `git revert`. A `Revert "…"` subject is typed
24
+ * by a person or by GitHub's own button on their behalf. `Fixes #123` is a
25
+ * developer stating that something was broken and this commit fixed it. What is
26
+ * deliberately *not* here is any reading of "fix", "hotfix" or "bug" in prose —
27
+ * the original mechanism's own comment says why, and it applies unchanged to
28
+ * every mechanism added since: matching on the word "fix" would classify
29
+ * ordinary work as an incident.
30
+ *
31
+ * ## Refusals, not guesses
32
+ *
33
+ * A `Revert "X"` naming a subject this history does not contain, or containing
34
+ * twice, produces a `DetectedRefusal` rather than a ranked guess. Two candidates
35
+ * is a genuine ambiguity and picking the nearer one would be recency standing in
36
+ * for evidence — the same discipline every resolver in this project follows.
37
+ *
38
+ * See `DEC-NEXT-incident-minting-beyond-explicit-reverts`.
39
+ */
40
+ import type { Commit } from "./history.ts";
41
+ /**
42
+ * How an incident was established. Written to `attrs.detectedVia` on the
43
+ * `INCIDENT` node and on its `FIXED_BY` edge, so a consumer can tell a
44
+ * machine-written trailer from a developer's typed sentence.
45
+ */
46
+ export type IncidentMechanism = "explicit-revert" | "manual-revert" | "issue-reference";
47
+ export interface DetectedIncident {
48
+ readonly mechanism: IncidentMechanism;
49
+ /** The commit that resolved it. Always a real commit in the history read. */
50
+ readonly fix: Commit;
51
+ /**
52
+ * The commit that broke it, for the mechanisms that name one.
53
+ *
54
+ * `null` for `issue-reference`, where no bad commit is named at all — the
55
+ * incident is the issue, and that is a different shape rather than a missing
56
+ * field.
57
+ */
58
+ readonly incidentCommit: Commit | null;
59
+ /** `#123`, for `issue-reference`. `null` for the revert mechanisms. */
60
+ readonly issueRef: string | null;
61
+ /**
62
+ * The files to correlate the incident with, already deduplicated and sorted.
63
+ *
64
+ * For a revert these are the files the *reverted* commit touched — where the
65
+ * defect was. For an issue reference there is no such commit, so they are the
66
+ * files the *fix* touched, which is the same claim reached from the other
67
+ * side: this is where the broken code lived.
68
+ */
69
+ readonly correlatedPaths: readonly string[];
70
+ }
71
+ /** A revert whose target could not be established. Disclosed, never dropped. */
72
+ export interface DetectedRefusal {
73
+ readonly fixSha: string;
74
+ /** The subject the revert quoted. */
75
+ readonly rawTarget: string;
76
+ readonly reason: string;
77
+ }
78
+ /**
79
+ * A revert trailer naming a sha this history does not contain.
80
+ *
81
+ * Not a refusal yet, and the distinction is load-bearing: `readHistory` reads
82
+ * HEAD-reachable, non-merge commits within a window, and a squash-merged or
83
+ * branch-deleted target is genuinely absent from that view while still being
84
+ * present in the object database. The caller holds the git process and can go
85
+ * look; this function is synchronous and cannot. Resolved out of band by
86
+ * `source.ts`, and only *then* refused if the object is really gone.
87
+ */
88
+ export interface UnresolvedTrailer {
89
+ readonly fix: Commit;
90
+ readonly revertedSha: string;
91
+ }
92
+ export interface IncidentDetection {
93
+ readonly incidents: readonly DetectedIncident[];
94
+ readonly refusals: readonly DetectedRefusal[];
95
+ readonly unresolvedTrailers: readonly UnresolvedTrailer[];
96
+ }
97
+ /** `git revert` writes this. Machine-generated, and the strongest signal available. */
98
+ declare const REVERT_TRAILER: RegExp;
99
+ /**
100
+ * Every incident this history declares, plus every revert whose target it does
101
+ * not contain.
102
+ *
103
+ * `commits` is the history as `readHistory` returns it — newest first. Order is
104
+ * not relied on for correctness: a revert's target is found by timestamp, so
105
+ * the result is identical for any permutation of the same commits.
106
+ */
107
+ export declare function detectIncidents(commits: readonly Commit[]): IncidentDetection;
108
+ export { REVERT_TRAILER };
109
+ //# sourceMappingURL=incidents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incidents.d.ts","sourceRoot":"","sources":["../../../src/sources/git/incidents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,kBAAkB,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC3D;AAED,uFAAuF;AACvF,QAAA,MAAM,cAAc,QAAuC,CAAC;AAkD5D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CAgG7E;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Which commits in a history describe a failure, and how we know.
3
+ *
4
+ * Split out of `source.ts` when the answer stopped being one regex. The git
5
+ * source minted an `INCIDENT` for exactly one thing — a commit body carrying
6
+ * `This reverts commit <40 hex>` — and that produced **zero** incidents across
7
+ * three measured repositories (734/459/203 commits) and two in a real product
8
+ * repository. `similarIncidents` was therefore built, tested, correct and
9
+ * starved: its recall is not 0%, it is not computable, because it has never
10
+ * been given a real incident to find.
11
+ *
12
+ * ## The rule every mechanism here answers to
13
+ *
14
+ * `INCIDENT` is an EARNED node (DEC-017). It is never dropped, it survives
15
+ * every rebuild, and `INCIDENT_CORRELATED` biases diff-scoping traversal toward
16
+ * the files it names for as long as the store exists. A wrong incident is not a
17
+ * bad answer that a later run corrects — it is unrecoverable pollution in the
18
+ * one table that cannot be recomputed away. So:
19
+ *
20
+ * **Every mechanism rests on something a person declared. None rests on
21
+ * something Descry inferred.**
22
+ *
23
+ * A revert trailer is written by `git revert`. A `Revert "…"` subject is typed
24
+ * by a person or by GitHub's own button on their behalf. `Fixes #123` is a
25
+ * developer stating that something was broken and this commit fixed it. What is
26
+ * deliberately *not* here is any reading of "fix", "hotfix" or "bug" in prose —
27
+ * the original mechanism's own comment says why, and it applies unchanged to
28
+ * every mechanism added since: matching on the word "fix" would classify
29
+ * ordinary work as an incident.
30
+ *
31
+ * ## Refusals, not guesses
32
+ *
33
+ * A `Revert "X"` naming a subject this history does not contain, or containing
34
+ * twice, produces a `DetectedRefusal` rather than a ranked guess. Two candidates
35
+ * is a genuine ambiguity and picking the nearer one would be recency standing in
36
+ * for evidence — the same discipline every resolver in this project follows.
37
+ *
38
+ * See `DEC-NEXT-incident-minting-beyond-explicit-reverts`.
39
+ */
40
+ /** `git revert` writes this. Machine-generated, and the strongest signal available. */
41
+ const REVERT_TRAILER = /This reverts commit ([0-9a-f]{40})/;
42
+ /**
43
+ * `Revert "the original subject"` — the subject form, written by a person or by
44
+ * GitHub's Revert button, often with no sha anywhere in the commit.
45
+ *
46
+ * Anchored at the start and requiring the quotes: `Revert the auth change` is
47
+ * prose about what the commit does, not a reference to a specific commit, and
48
+ * reading it as one would be exactly the inference this module refuses to make.
49
+ */
50
+ const REVERT_SUBJECT = /^Revert\s+"(.+)"\s*$/;
51
+ /**
52
+ * A closed list of fix-shaped verbs, and only immediately before the reference.
53
+ *
54
+ * `Fixes #123` is a developer stating that something was broken. `See #123` and
55
+ * `Part of #124` are not — they relate a commit to an issue and say nothing
56
+ * about a defect. The verb is what carries the claim, so the verb is what is
57
+ * matched; a bare `#123` mints nothing.
58
+ *
59
+ * `GH-123` is GitHub's own cross-reference form and means the same thing.
60
+ */
61
+ const ISSUE_VERBS = "fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved";
62
+ /** In the subject, anywhere: a subject is a claim about the commit itself. */
63
+ const SUBJECT_ISSUE = new RegExp(`\\b(?:${ISSUE_VERBS})\\s+(?:#|GH-)(\\d+)\\b`, "gi");
64
+ /**
65
+ * In the body, **only in trailer position** — the start of a line, with nothing
66
+ * but whitespace before it.
67
+ *
68
+ * Measured, not supposed. Run over this repository's own history, the
69
+ * anywhere-in-the-body form matched three commits, and all three were prose
70
+ * *about* the convention rather than uses of it: a sentence quoting
71
+ * `"Fixes #123"` as an example, and a bullet reading
72
+ * `- issue-reference: \`Fixes #123\` and its closed list…`. A commit message
73
+ * discussing the convention is not a commit message using it, and an EARNED
74
+ * node minted from one is permanent.
75
+ *
76
+ * The trailer position is where the convention actually lives — it is what
77
+ * GitHub's own tooling emits and what a template puts there — so this costs
78
+ * almost no real recall and removes a whole class of false positive. Rule 2:
79
+ * when in doubt, omit.
80
+ */
81
+ const BODY_ISSUE = new RegExp(`^[ \\t]*(?:${ISSUE_VERBS})\\s+(?:#|GH-)(\\d+)\\b`, "gim");
82
+ function pathsOf(commit) {
83
+ return [...new Set(commit.changes.map((c) => c.path))].sort();
84
+ }
85
+ /**
86
+ * Every incident this history declares, plus every revert whose target it does
87
+ * not contain.
88
+ *
89
+ * `commits` is the history as `readHistory` returns it — newest first. Order is
90
+ * not relied on for correctness: a revert's target is found by timestamp, so
91
+ * the result is identical for any permutation of the same commits.
92
+ */
93
+ export function detectIncidents(commits) {
94
+ const incidents = [];
95
+ const refusals = [];
96
+ const unresolvedTrailers = [];
97
+ const bySha = new Map(commits.map((c) => [c.sha, c]));
98
+ const bySubject = new Map();
99
+ for (const commit of commits) {
100
+ const existing = bySubject.get(commit.subject);
101
+ if (existing === undefined)
102
+ bySubject.set(commit.subject, [commit]);
103
+ else
104
+ existing.push(commit);
105
+ }
106
+ for (const commit of commits) {
107
+ // The trailer first, and exclusively: `git revert` writes both the trailer
108
+ // and the `Revert "…"` subject, so reading the subject as an independent
109
+ // mechanism would mint one incident twice under two different names.
110
+ const revertedSha = REVERT_TRAILER.exec(commit.body)?.[1];
111
+ if (revertedSha !== undefined) {
112
+ const target = bySha.get(revertedSha);
113
+ if (target === undefined)
114
+ unresolvedTrailers.push({ fix: commit, revertedSha });
115
+ else {
116
+ incidents.push({
117
+ mechanism: "explicit-revert",
118
+ fix: commit,
119
+ incidentCommit: target,
120
+ issueRef: null,
121
+ correlatedPaths: pathsOf(target),
122
+ });
123
+ }
124
+ continue;
125
+ }
126
+ const quoted = REVERT_SUBJECT.exec(commit.subject)?.[1];
127
+ if (quoted !== undefined) {
128
+ // Backwards only. A commit cannot revert something that had not happened
129
+ // when it was written, and a same-subject commit landing afterwards is a
130
+ // different commit that happens to share a name.
131
+ const candidates = (bySubject.get(quoted) ?? []).filter((c) => c.timestamp < commit.timestamp);
132
+ if (candidates.length === 1) {
133
+ const target = candidates[0];
134
+ incidents.push({
135
+ mechanism: "manual-revert",
136
+ fix: commit,
137
+ incidentCommit: target,
138
+ issueRef: null,
139
+ correlatedPaths: pathsOf(target),
140
+ });
141
+ }
142
+ else if (candidates.length === 0) {
143
+ refusals.push({
144
+ fixSha: commit.sha,
145
+ rawTarget: quoted,
146
+ reason: 'the commit quotes a subject it reverts, but no commit in the history read here has ' +
147
+ "that subject — it may predate the read window, or live on a branch this read cannot see",
148
+ });
149
+ }
150
+ else {
151
+ refusals.push({
152
+ fixSha: commit.sha,
153
+ rawTarget: quoted,
154
+ reason: `${candidates.length} commits share that subject, so the revert does not identify one. ` +
155
+ "Choosing the nearest would be recency standing in for evidence",
156
+ });
157
+ }
158
+ continue;
159
+ }
160
+ // An issue reference is a claim about a *defect*, made by the person who
161
+ // fixed it. Read from the subject anywhere and from the body only in
162
+ // trailer position — see `BODY_ISSUE` for the three false positives that
163
+ // distinction was measured against.
164
+ const refs = new Set();
165
+ for (const match of commit.subject.matchAll(SUBJECT_ISSUE))
166
+ refs.add(`#${match[1]}`);
167
+ for (const match of commit.body.matchAll(BODY_ISSUE))
168
+ refs.add(`#${match[1]}`);
169
+ for (const issueRef of [...refs].sort()) {
170
+ incidents.push({
171
+ mechanism: "issue-reference",
172
+ fix: commit,
173
+ incidentCommit: null,
174
+ issueRef,
175
+ correlatedPaths: pathsOf(commit),
176
+ });
177
+ }
178
+ }
179
+ // Deterministic: two runs over the same history must produce a byte-identical
180
+ // batch, or the store's content hash moves for no reason.
181
+ incidents.sort((a, b) => a.fix.sha.localeCompare(b.fix.sha) || (a.issueRef ?? "").localeCompare(b.issueRef ?? ""));
182
+ refusals.sort((a, b) => a.fixSha.localeCompare(b.fixSha) || a.rawTarget.localeCompare(b.rawTarget));
183
+ unresolvedTrailers.sort((a, b) => a.fix.sha.localeCompare(b.fix.sha));
184
+ return { incidents, refusals, unresolvedTrailers };
185
+ }
186
+ export { REVERT_TRAILER };
187
+ //# sourceMappingURL=incidents.js.map