@descryy/core 0.1.0 → 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.
- package/dist/budget/index.d.ts +3 -0
- package/dist/budget/index.d.ts.map +1 -0
- package/dist/budget/index.js +2 -0
- package/dist/budget/index.js.map +1 -0
- package/dist/budget/size.d.ts +43 -0
- package/dist/budget/size.d.ts.map +1 -0
- package/dist/budget/size.js +52 -0
- package/dist/budget/size.js.map +1 -0
- package/dist/contracts/engine.d.ts.map +1 -1
- package/dist/contracts/engine.js +16 -9
- package/dist/contracts/engine.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/multipr/mechanical.d.ts.map +1 -1
- package/dist/multipr/mechanical.js +1 -0
- package/dist/multipr/mechanical.js.map +1 -1
- package/dist/query/confirmed-facts.d.ts +21 -5
- package/dist/query/confirmed-facts.d.ts.map +1 -1
- package/dist/query/confirmed-facts.js +30 -7
- package/dist/query/confirmed-facts.js.map +1 -1
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +1 -0
- package/dist/query/index.js.map +1 -1
- package/dist/query/memory.d.ts.map +1 -1
- package/dist/query/memory.js +4 -0
- package/dist/query/memory.js.map +1 -1
- package/dist/query/provider.d.ts +7 -0
- package/dist/query/provider.d.ts.map +1 -1
- package/dist/query/provider.js.map +1 -1
- package/dist/query/queries.d.ts.map +1 -1
- package/dist/query/queries.js +39 -15
- package/dist/query/queries.js.map +1 -1
- package/dist/query/resolution-floor.d.ts +36 -0
- package/dist/query/resolution-floor.d.ts.map +1 -0
- package/dist/query/resolution-floor.js +39 -0
- package/dist/query/resolution-floor.js.map +1 -0
- package/dist/query/sqlite.d.ts.map +1 -1
- package/dist/query/sqlite.js +1 -0
- package/dist/query/sqlite.js.map +1 -1
- package/dist/query/traverse.d.ts.map +1 -1
- package/dist/query/traverse.js +0 -0
- package/dist/query/traverse.js.map +1 -1
- package/dist/scoping/traverse.d.ts.map +1 -1
- package/dist/scoping/traverse.js +18 -4
- package/dist/scoping/traverse.js.map +1 -1
- package/dist/sources/confirmed/incidents.d.ts +47 -0
- package/dist/sources/confirmed/incidents.d.ts.map +1 -0
- package/dist/sources/confirmed/incidents.js +173 -0
- package/dist/sources/confirmed/incidents.js.map +1 -0
- package/dist/sources/confirmed/index.d.ts +3 -0
- package/dist/sources/confirmed/index.d.ts.map +1 -0
- package/dist/sources/confirmed/index.js +2 -0
- package/dist/sources/confirmed/index.js.map +1 -0
- package/dist/sources/git/diff.d.ts +37 -0
- package/dist/sources/git/diff.d.ts.map +1 -1
- package/dist/sources/git/diff.js +59 -8
- package/dist/sources/git/diff.js.map +1 -1
- package/dist/sources/git/incidents.d.ts +109 -0
- package/dist/sources/git/incidents.d.ts.map +1 -0
- package/dist/sources/git/incidents.js +187 -0
- package/dist/sources/git/incidents.js.map +1 -0
- package/dist/sources/git/index.d.ts +4 -2
- package/dist/sources/git/index.d.ts.map +1 -1
- package/dist/sources/git/index.js +2 -1
- package/dist/sources/git/index.js.map +1 -1
- package/dist/sources/git/source.d.ts.map +1 -1
- package/dist/sources/git/source.js +0 -0
- package/dist/sources/git/source.js.map +1 -1
- package/dist/tiers/certify.d.ts.map +1 -1
- package/dist/tiers/certify.js +47 -3
- package/dist/tiers/certify.js.map +1 -1
- 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 @@
|
|
|
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 @@
|
|
|
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
|
|
@@ -50,4 +68,23 @@ export interface GitDiffOptions {
|
|
|
50
68
|
* misattributing someone else's history to this diff.
|
|
51
69
|
*/
|
|
52
70
|
export declare function gitDiff(cwd: string, base: string, head?: string, options?: GitDiffOptions): Promise<readonly GitDiffFile[]>;
|
|
71
|
+
export interface GitWorkingTreeDiffOptions extends GitDiffOptions {
|
|
72
|
+
/**
|
|
73
|
+
* `"unstaged"` (default) — the working tree against the index (`git diff -M`),
|
|
74
|
+
* i.e. edits not yet `git add`-ed.
|
|
75
|
+
* `"staged"` — the index against `HEAD` (`git diff -M --cached`), i.e. what
|
|
76
|
+
* a commit right now would contain.
|
|
77
|
+
* `"all"` — the working tree against `HEAD` (`git diff -M HEAD`), staged and
|
|
78
|
+
* unstaged combined — everything not yet committed.
|
|
79
|
+
*/
|
|
80
|
+
readonly scope?: "unstaged" | "staged" | "all";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The working-tree counterpart to {@link gitDiff}: a live diff against
|
|
84
|
+
* uncommitted state, which no two-revision form of `git diff` can express
|
|
85
|
+
* (`HEAD...HEAD` is empty by construction). Reuses the exact same block
|
|
86
|
+
* parser, rename detection and patch cap `gitDiff` does — see
|
|
87
|
+
* `DEC-NEXT-working-tree-diff-in-core` for why a second parser was rejected.
|
|
88
|
+
*/
|
|
89
|
+
export declare function gitWorkingTreeDiff(cwd: string, options?: GitWorkingTreeDiffOptions): Promise<readonly GitDiffFile[]>;
|
|
53
90
|
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/sources/git/diff.js
CHANGED
|
@@ -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
|
}
|
|
@@ -96,14 +118,10 @@ function parseDiffBlock(block) {
|
|
|
96
118
|
deletions: binary ? 0 : deletions,
|
|
97
119
|
};
|
|
98
120
|
}
|
|
99
|
-
|
|
100
|
-
* `base...head` (three dots), matching `changedFiles`'s own reasoning: two
|
|
101
|
-
* dots would include everything changed on `base` since the branch point,
|
|
102
|
-
* misattributing someone else's history to this diff.
|
|
103
|
-
*/
|
|
104
|
-
export async function gitDiff(cwd, base, head = "HEAD", options = {}) {
|
|
121
|
+
async function runGitDiff(cwd, revisionArgs, options) {
|
|
105
122
|
const maxPatchBytes = options.maxPatchBytes ?? DEFAULT_MAX_PATCH_BYTES;
|
|
106
|
-
const
|
|
123
|
+
const maxTotalPatchBytes = options.maxTotalPatchBytes ?? DEFAULT_MAX_TOTAL_PATCH_BYTES;
|
|
124
|
+
const args = ["diff", "-M", ...revisionArgs];
|
|
107
125
|
if (options.files !== undefined && options.files.length > 0)
|
|
108
126
|
args.push("--", ...options.files);
|
|
109
127
|
const { stdout } = await run("git", args, { cwd, maxBuffer: 256 * 1024 * 1024 });
|
|
@@ -135,6 +153,39 @@ export async function gitDiff(cwd, base, head = "HEAD", options = {}) {
|
|
|
135
153
|
: null,
|
|
136
154
|
});
|
|
137
155
|
}
|
|
138
|
-
|
|
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;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* `base...head` (three dots), matching `changedFiles`'s own reasoning: two
|
|
173
|
+
* dots would include everything changed on `base` since the branch point,
|
|
174
|
+
* misattributing someone else's history to this diff.
|
|
175
|
+
*/
|
|
176
|
+
export async function gitDiff(cwd, base, head = "HEAD", options = {}) {
|
|
177
|
+
return runGitDiff(cwd, [`${base}...${head}`], options);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The working-tree counterpart to {@link gitDiff}: a live diff against
|
|
181
|
+
* uncommitted state, which no two-revision form of `git diff` can express
|
|
182
|
+
* (`HEAD...HEAD` is empty by construction). Reuses the exact same block
|
|
183
|
+
* parser, rename detection and patch cap `gitDiff` does — see
|
|
184
|
+
* `DEC-NEXT-working-tree-diff-in-core` for why a second parser was rejected.
|
|
185
|
+
*/
|
|
186
|
+
export async function gitWorkingTreeDiff(cwd, options = {}) {
|
|
187
|
+
const scope = options.scope ?? "unstaged";
|
|
188
|
+
const revisionArgs = scope === "staged" ? ["--cached"] : scope === "all" ? ["HEAD"] : [];
|
|
189
|
+
return runGitDiff(cwd, revisionArgs, options);
|
|
139
190
|
}
|
|
140
191
|
//# sourceMappingURL=diff.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/sources/git/diff.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|