@aligndottech/cli 0.65.0 → 0.65.2
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/commands/check.d.ts +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +57 -14
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/connect-agents.d.ts +1 -0
- package/dist/commands/connect-agents.d.ts.map +1 -1
- package/dist/commands/connect-agents.js +15 -8
- package/dist/commands/connect-agents.js.map +1 -1
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +6 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/commands/why.js +20 -20
- package/dist/commands/why.js.map +1 -1
- package/dist/detector/relationship-contract.d.ts.map +1 -1
- package/dist/detector/relationship-contract.js +15 -7
- package/dist/detector/relationship-contract.js.map +1 -1
- package/dist/detector/relationship-contract.v1.json +3 -2
- package/dist/lib/ask-listing.d.ts +14 -0
- package/dist/lib/ask-listing.d.ts.map +1 -1
- package/dist/lib/ask-listing.js +38 -0
- package/dist/lib/ask-listing.js.map +1 -1
- package/dist/lib/blame-attribution.d.ts +62 -0
- package/dist/lib/blame-attribution.d.ts.map +1 -0
- package/dist/lib/blame-attribution.js +163 -0
- package/dist/lib/blame-attribution.js.map +1 -0
- package/dist/lib/blame-candidates.d.ts +27 -0
- package/dist/lib/blame-candidates.d.ts.map +1 -0
- package/dist/lib/blame-candidates.js +113 -0
- package/dist/lib/blame-candidates.js.map +1 -0
- package/dist/lib/commit-shape.d.ts +22 -0
- package/dist/lib/commit-shape.d.ts.map +1 -1
- package/dist/lib/commit-shape.js +52 -0
- package/dist/lib/commit-shape.js.map +1 -1
- package/dist/lib/connect-prompt.d.ts +9 -0
- package/dist/lib/connect-prompt.d.ts.map +1 -1
- package/dist/lib/connect-prompt.js +13 -0
- package/dist/lib/connect-prompt.js.map +1 -1
- package/dist/lib/fetchers/git.js +1 -1
- package/dist/lib/fetchers/git.js.map +1 -1
- package/dist/lib/first-run-pr-import.d.ts +2 -21
- package/dist/lib/first-run-pr-import.d.ts.map +1 -1
- package/dist/lib/first-run-pr-import.js +29 -2
- package/dist/lib/first-run-pr-import.js.map +1 -1
- package/dist/lib/found-summary.d.ts.map +1 -1
- package/dist/lib/found-summary.js +5 -10
- package/dist/lib/found-summary.js.map +1 -1
- package/dist/lib/git.js +2 -2
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/local-db.d.ts +6 -0
- package/dist/lib/local-db.d.ts.map +1 -1
- package/dist/lib/local-db.js +14 -0
- package/dist/lib/local-db.js.map +1 -1
- package/dist/lib/local-gateway-client.d.ts +10 -0
- package/dist/lib/local-gateway-client.d.ts.map +1 -1
- package/dist/lib/local-gateway-client.js +24 -2
- package/dist/lib/local-gateway-client.js.map +1 -1
- package/dist/lib/next-step.d.ts.map +1 -1
- package/dist/lib/next-step.js +6 -4
- package/dist/lib/next-step.js.map +1 -1
- package/dist/lib/reason-extract.d.ts +1 -0
- package/dist/lib/reason-extract.d.ts.map +1 -1
- package/dist/lib/reason-extract.js +1 -1
- package/dist/lib/reason-extract.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/ask-listing.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* trailers. Printed whole, one `ask` on a 4,906-commit repo was 516 lines. The list is for
|
|
8
8
|
* scanning: title, a short excerpt, where it came from.
|
|
9
9
|
*/
|
|
10
|
+
import { stripImporterMetadata, TRAILER } from './reason-extract.js';
|
|
10
11
|
import { RELATES_THRESHOLD } from './local-thresholds.js';
|
|
11
12
|
/** Characters of summary shown per match. A display choice, not a calibrated number. */
|
|
12
13
|
export const EXCERPT_MAX_CHARS = 200;
|
|
@@ -57,4 +58,41 @@ export function withoutTitle(summary, title) {
|
|
|
57
58
|
const t = title.replace(/\s+/g, ' ').trim();
|
|
58
59
|
return t && flat.startsWith(`${t} `) ? flat.slice(t.length + 1) : flat;
|
|
59
60
|
}
|
|
61
|
+
// The dashed rule a squash merge puts above its trailers. The trailer keys themselves come from
|
|
62
|
+
// reason-extract's TRAILER, the one list, anchored to the line start.
|
|
63
|
+
const SEPARATOR_LINE = /^\s*-{3,}\s*$/;
|
|
64
|
+
/**
|
|
65
|
+
* A commit body without its TRAILING block of git trailers (reason-extract's TRAILER list) and
|
|
66
|
+
* the `---------` rule squash merges add above them. Only the end is stripped, as git defines
|
|
67
|
+
* trailers: a mid-body or opening "Issue: ..." line, or a markdown rule, stays. Line-based, so
|
|
68
|
+
* it must run BEFORE whitespace is flattened. Display only: the stored summary keeps its
|
|
69
|
+
* trailers, which are provenance and still feed search.
|
|
70
|
+
*/
|
|
71
|
+
export function withoutTrailers(summary) {
|
|
72
|
+
const lines = summary.split('\n');
|
|
73
|
+
let end = lines.length;
|
|
74
|
+
// Walk up from the end over trailer lines, blanks and dashed rules; the first other line ends the block.
|
|
75
|
+
while (end > 0 && (TRAILER.test(lines[end - 1]) || SEPARATOR_LINE.test(lines[end - 1]) || !lines[end - 1].trim()))
|
|
76
|
+
end -= 1;
|
|
77
|
+
if (end > 0)
|
|
78
|
+
return lines.slice(0, end).join('\n').trim();
|
|
79
|
+
// The walk reached the top: the whole body looks like trailers. A dashed rule marks a squash
|
|
80
|
+
// tail, so cut at the first one. Otherwise the first paragraph is the body ("Issue: <reason>"
|
|
81
|
+
// is the reason, not a trailer) and only what follows the first blank line is the block.
|
|
82
|
+
const rule = lines.findIndex((l) => SEPARATOR_LINE.test(l));
|
|
83
|
+
if (rule >= 0)
|
|
84
|
+
return lines.slice(0, rule).join('\n').trim();
|
|
85
|
+
const blank = lines.findIndex((l, i) => i > 0 && !l.trim());
|
|
86
|
+
return (blank < 0 ? lines : lines.slice(0, blank)).join('\n').trim();
|
|
87
|
+
}
|
|
88
|
+
const normTitle = (t) => t.replace(/\s+/g, ' ').trim().toLowerCase().replace(/\s*\(#\d+\)$/, '');
|
|
89
|
+
/**
|
|
90
|
+
* What a ranked-list row quotes under its title: the body with importer metadata, trailers
|
|
91
|
+
* and the echoed title removed. Empty when nothing is left, or what is left only repeats the
|
|
92
|
+
* title (whitespace, case and a trailing "(#123)" ignored): the caller then prints no quote.
|
|
93
|
+
*/
|
|
94
|
+
export function excerptBody(summary, title) {
|
|
95
|
+
const body = withoutTitle(withoutTrailers(stripImporterMetadata(summary)), title);
|
|
96
|
+
return normTitle(body) === normTitle(title) ? '' : body;
|
|
97
|
+
}
|
|
60
98
|
//# sourceMappingURL=ask-listing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-listing.js","sourceRoot":"","sources":["../../src/lib/ask-listing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,wFAAwF;AACxF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,MAAc,iBAAiB;IACxE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACpC,mGAAmG;IACnG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC/F,mGAAmG;IACnG,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,GAAG,IAAI,KAAK,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,UAA8B;IACnD,OAAO,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG,gBAAgB,CAAC;AACnE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,OAAO,CAAC,YAAuC;IAC7D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,GAAG,KAAK,oBAAoB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAa;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC"}
|
|
1
|
+
{"version":3,"file":"ask-listing.js","sourceRoot":"","sources":["../../src/lib/ask-listing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HookToolInput } from './hook-payload.js';
|
|
2
|
+
/**
|
|
3
|
+
* The decisions that WROTE the lines an agent is about to change.
|
|
4
|
+
*
|
|
5
|
+
* Content search finds decisions on the same subject; it cannot say which decision put these
|
|
6
|
+
* exact lines here. `git blame` can, deterministically and locally, so the advisory hook asks
|
|
7
|
+
* it first. No AI call, no network. Everything here fails open to `null`: no repo, an
|
|
8
|
+
* untracked or missing file, text not found, a blame error or a timeout all mean "no
|
|
9
|
+
* attribution", and the hook carries on exactly as it did before.
|
|
10
|
+
*/
|
|
11
|
+
export interface LineWriters {
|
|
12
|
+
/** Full SHAs of the commits that wrote the lines the edit replaces. */
|
|
13
|
+
commitShas: string[];
|
|
14
|
+
/** Lower-cased `https://github.com/<owner>/<repo>/pull/<n>` for squash-merged PRs. */
|
|
15
|
+
prUrls: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Inside the hook's 10s timeout and the 2.5s retrieval race it runs beside. */
|
|
18
|
+
export declare const BLAME_TIMEOUT_MS = 1500;
|
|
19
|
+
export type Range = [start: number, end: number];
|
|
20
|
+
/**
|
|
21
|
+
* Files whose lines carry no decision an edit could violate: prose documents, licence and changelog
|
|
22
|
+
* style files, and generated lockfiles. On a repo whose history is not conventional-commit typed, a
|
|
23
|
+
* prose edit would otherwise be pinned on whatever untyped commit last touched the line. Checked before
|
|
24
|
+
* any git call, so these edits cost nothing and keep the content-search related list as it was.
|
|
25
|
+
* Its complement is everything else, still blamed: source, .json/.yaml/.toml config, .sql, Dockerfiles,
|
|
26
|
+
* extensionless scripts, and a code file that merely starts like a licence ("LICENSE-tool.js").
|
|
27
|
+
*/
|
|
28
|
+
export declare function isProseOrGeneratedFile(file: string): boolean;
|
|
29
|
+
export interface BlameOpts {
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** Test seam: runs one git command. Defaults to the real git; the budget is enforced around it either way. */
|
|
32
|
+
runGit?: (args: string[], cwd: string, timeoutMs: number) => Promise<{
|
|
33
|
+
stdout: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export type GitRun = (args: string[]) => Promise<{
|
|
37
|
+
stdout: string;
|
|
38
|
+
}>;
|
|
39
|
+
/** One git runner whose budget holds for any underlying runner, not only execa's own kill. */
|
|
40
|
+
export declare function makeGitRunner(opts: BlameOpts, cwd: string): GitRun;
|
|
41
|
+
export interface BlamedCommit {
|
|
42
|
+
sha: string;
|
|
43
|
+
subject: string;
|
|
44
|
+
/** Committer time, epoch seconds (0 when git did not say). */
|
|
45
|
+
time: number;
|
|
46
|
+
}
|
|
47
|
+
/** `git blame -w -M --porcelain` over the given 1-based line ranges; commits in first-seen order, uncommitted lines dropped. */
|
|
48
|
+
export declare function blameCommits(file: string, ranges: Range[], run: GitRun): Promise<BlamedCommit[]>;
|
|
49
|
+
/** PR urls for squash-merged commits, from "(#N)" / "Merge pull request #N" subjects and the origin remote. No origin only costs the mapping. */
|
|
50
|
+
export declare function prUrlsFor(commits: BlamedCommit[], run: GitRun): Promise<string[]>;
|
|
51
|
+
export declare function findLineWriters(input: HookToolInput | undefined, cwd: string, opts?: BlameOpts): Promise<LineWriters | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a decision may be shown as "the decision that wrote these lines". Routine conventional-commit
|
|
54
|
+
* types (docs, style, test, chore, ci, build, release) decide nothing an edit could violate, and a
|
|
55
|
+
* refactor rewrites lines without deciding what they do, so naming it would hide the real writer.
|
|
56
|
+
* Naming any of them would fire on most routine edits and teach people to ignore the hook. They stay
|
|
57
|
+
* eligible for the content-search related list. fix, feat, perf and untyped titles qualify.
|
|
58
|
+
* The shapes come from commit-shape.ts (non-behavioural types, plus the housekeeping shapes: merges,
|
|
59
|
+
* drafts, backports, version bumps), so there is one list of what a mechanical subject looks like.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isWriterTitle(title: string): boolean;
|
|
62
|
+
//# sourceMappingURL=blame-attribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blame-attribution.d.ts","sourceRoot":"","sources":["../../src/lib/blame-attribution.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sFAAsF;IACtF,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAKrC,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;AA6DjD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1F;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAErE,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQlE;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gIAAgI;AAChI,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBtG;AAED,iJAAiJ;AACjJ,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKvF;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAwB7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { isHousekeepingTitle, isNonBehaviouralType } from './commit-shape.js';
|
|
5
|
+
/** Inside the hook's 10s timeout and the 2.5s retrieval race it runs beside. */
|
|
6
|
+
export const BLAME_TIMEOUT_MS = 1500;
|
|
7
|
+
/** A rewrite of a huge file would blame thousands of lines for no extra signal. */
|
|
8
|
+
const MAX_BLAME_LINES = 400;
|
|
9
|
+
const MAX_OCCURRENCES = 3;
|
|
10
|
+
const lineOf = (text, index) => {
|
|
11
|
+
let n = 1;
|
|
12
|
+
for (let i = text.indexOf('\n'); i !== -1 && i < index; i = text.indexOf('\n', i + 1))
|
|
13
|
+
n++;
|
|
14
|
+
return n;
|
|
15
|
+
};
|
|
16
|
+
/** 1-based line ranges in `current` covered by each occurrence of `needle`. */
|
|
17
|
+
function rangesOfText(current, needle) {
|
|
18
|
+
const out = [];
|
|
19
|
+
const trimmed = needle.replace(/\n+$/, '');
|
|
20
|
+
if (!trimmed)
|
|
21
|
+
return out;
|
|
22
|
+
const span = trimmed.split('\n').length - 1;
|
|
23
|
+
for (let at = current.indexOf(trimmed); at !== -1 && out.length < MAX_OCCURRENCES; at = current.indexOf(trimmed, at + trimmed.length)) {
|
|
24
|
+
const start = lineOf(current, at);
|
|
25
|
+
out.push([start, start + span]);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/** For a Write: the lines of the current file the new content does not keep (common prefix and suffix trimmed). */
|
|
30
|
+
function rangesChangedBy(current, next) {
|
|
31
|
+
const a = current.split('\n');
|
|
32
|
+
const b = next.split('\n');
|
|
33
|
+
let head = 0;
|
|
34
|
+
while (head < a.length && head < b.length && a[head] === b[head])
|
|
35
|
+
head++;
|
|
36
|
+
let tail = 0;
|
|
37
|
+
while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail])
|
|
38
|
+
tail++;
|
|
39
|
+
const end = a.length - tail; // exclusive, 0-based
|
|
40
|
+
return end > head ? [[head + 1, end]] : [];
|
|
41
|
+
}
|
|
42
|
+
function capped(ranges) {
|
|
43
|
+
let budget = MAX_BLAME_LINES;
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const [s, e] of ranges) {
|
|
46
|
+
if (budget <= 0)
|
|
47
|
+
break;
|
|
48
|
+
const end = Math.min(e, s + budget - 1);
|
|
49
|
+
out.push([s, end]);
|
|
50
|
+
budget -= end - s + 1;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** owner/repo from a GitHub remote url (https or ssh), lower-cased; null for anything else. */
|
|
55
|
+
function githubRepoOf(remote) {
|
|
56
|
+
const m = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(remote.trim());
|
|
57
|
+
return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
|
|
58
|
+
}
|
|
59
|
+
const prNumberOf = (subject) => /\(#(\d+)\)\s*$/.exec(subject)?.[1] ?? /^Merge pull request #(\d+)\b/.exec(subject)?.[1] ?? null;
|
|
60
|
+
const PROSE_EXTENSION = /\.(?:md|mdx|markdown|txt|rst|adoc)$/i;
|
|
61
|
+
// LICENSE, LICENCE, CHANGELOG, CHANGES, AUTHORS, CONTRIBUTORS, NOTICE and their variants (LICENSE-MIT, LICENSE-APACHE-2.0).
|
|
62
|
+
const PROSE_BASENAME = /^(?:licen[sc]e|changelog|changes|authors|contributors|notice)/i;
|
|
63
|
+
// A real extension starts with a letter: ".js" is one, the ".0" of "LICENSE-APACHE-2.0" is not.
|
|
64
|
+
const CODE_EXTENSION = /\.[a-z][a-z0-9]*$/i;
|
|
65
|
+
const LOCKFILES = new Set(['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'cargo.lock', 'gemfile.lock', 'poetry.lock', 'composer.lock', 'go.sum']);
|
|
66
|
+
/**
|
|
67
|
+
* Files whose lines carry no decision an edit could violate: prose documents, licence and changelog
|
|
68
|
+
* style files, and generated lockfiles. On a repo whose history is not conventional-commit typed, a
|
|
69
|
+
* prose edit would otherwise be pinned on whatever untyped commit last touched the line. Checked before
|
|
70
|
+
* any git call, so these edits cost nothing and keep the content-search related list as it was.
|
|
71
|
+
* Its complement is everything else, still blamed: source, .json/.yaml/.toml config, .sql, Dockerfiles,
|
|
72
|
+
* extensionless scripts, and a code file that merely starts like a licence ("LICENSE-tool.js").
|
|
73
|
+
*/
|
|
74
|
+
export function isProseOrGeneratedFile(file) {
|
|
75
|
+
const base = path.basename(file);
|
|
76
|
+
const lower = base.toLowerCase();
|
|
77
|
+
if (PROSE_EXTENSION.test(base) || LOCKFILES.has(lower) || lower.endsWith('.lock'))
|
|
78
|
+
return true;
|
|
79
|
+
return PROSE_BASENAME.test(base) && !CODE_EXTENSION.test(base);
|
|
80
|
+
}
|
|
81
|
+
/** One git runner whose budget holds for any underlying runner, not only execa's own kill. */
|
|
82
|
+
export function makeGitRunner(opts, cwd) {
|
|
83
|
+
const timeout = opts.timeoutMs ?? BLAME_TIMEOUT_MS;
|
|
84
|
+
const runGit = opts.runGit ?? ((args, dir, ms) => execa('git', args, { cwd: dir, timeout: ms, reject: true, stdin: 'ignore' }));
|
|
85
|
+
return (args) => {
|
|
86
|
+
let timer;
|
|
87
|
+
const expired = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('blame budget exceeded')), timeout); });
|
|
88
|
+
return Promise.race([runGit(args, cwd, timeout), expired]).finally(() => clearTimeout(timer));
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** `git blame -w -M --porcelain` over the given 1-based line ranges; commits in first-seen order, uncommitted lines dropped. */
|
|
92
|
+
export async function blameCommits(file, ranges, run) {
|
|
93
|
+
const blame = await run(['blame', '-w', '-M', '--porcelain', ...ranges.flatMap(([s, e]) => ['-L', `${s},${e}`]), '--', path.basename(file)]);
|
|
94
|
+
const commits = new Map();
|
|
95
|
+
let current = null;
|
|
96
|
+
for (const line of blame.stdout.split('\n')) {
|
|
97
|
+
const head = /^([0-9a-f]{40}) \d+ \d+/.exec(line);
|
|
98
|
+
if (head) {
|
|
99
|
+
current = /^0+$/.test(head[1]) ? null : (commits.get(head[1]) ?? { sha: head[1], subject: '', time: 0 });
|
|
100
|
+
if (current)
|
|
101
|
+
commits.set(current.sha, current);
|
|
102
|
+
}
|
|
103
|
+
else if (current && line.startsWith('summary ')) {
|
|
104
|
+
current.subject = line.slice('summary '.length);
|
|
105
|
+
}
|
|
106
|
+
else if (current && line.startsWith('committer-time ')) {
|
|
107
|
+
current.time = Number(line.slice('committer-time '.length)) || 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...commits.values()];
|
|
111
|
+
}
|
|
112
|
+
/** PR urls for squash-merged commits, from "(#N)" / "Merge pull request #N" subjects and the origin remote. No origin only costs the mapping. */
|
|
113
|
+
export async function prUrlsFor(commits, run) {
|
|
114
|
+
const prNumbers = new Set(commits.flatMap((c) => prNumberOf(c.subject) ?? []));
|
|
115
|
+
if (!prNumbers.size)
|
|
116
|
+
return [];
|
|
117
|
+
const repo = githubRepoOf(await run(['remote', 'get-url', 'origin']).then((r) => r.stdout, () => ''));
|
|
118
|
+
return repo ? [...prNumbers].map((n) => `https://github.com/${repo}/pull/${n}`) : [];
|
|
119
|
+
}
|
|
120
|
+
export async function findLineWriters(input, cwd, opts = {}) {
|
|
121
|
+
try {
|
|
122
|
+
if (!input || typeof input.file_path !== 'string' || !input.file_path)
|
|
123
|
+
return null;
|
|
124
|
+
if (isProseOrGeneratedFile(input.file_path))
|
|
125
|
+
return null;
|
|
126
|
+
const file = path.resolve(cwd, input.file_path);
|
|
127
|
+
const current = await readFile(file, 'utf8');
|
|
128
|
+
let ranges = [];
|
|
129
|
+
if (typeof input.content === 'string') {
|
|
130
|
+
ranges = rangesChangedBy(current, input.content);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const olds = Array.isArray(input.edits) ? input.edits.map((e) => e.old_string) : [input.old_string];
|
|
134
|
+
for (const old of olds)
|
|
135
|
+
if (typeof old === 'string')
|
|
136
|
+
ranges.push(...rangesOfText(current, old));
|
|
137
|
+
}
|
|
138
|
+
ranges = capped(ranges);
|
|
139
|
+
if (!ranges.length)
|
|
140
|
+
return null;
|
|
141
|
+
const run = makeGitRunner(opts, path.dirname(file));
|
|
142
|
+
const commits = await blameCommits(file, ranges, run);
|
|
143
|
+
if (!commits.length)
|
|
144
|
+
return null;
|
|
145
|
+
return { commitShas: commits.map((c) => c.sha), prUrls: await prUrlsFor(commits, run) };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Whether a decision may be shown as "the decision that wrote these lines". Routine conventional-commit
|
|
153
|
+
* types (docs, style, test, chore, ci, build, release) decide nothing an edit could violate, and a
|
|
154
|
+
* refactor rewrites lines without deciding what they do, so naming it would hide the real writer.
|
|
155
|
+
* Naming any of them would fire on most routine edits and teach people to ignore the hook. They stay
|
|
156
|
+
* eligible for the content-search related list. fix, feat, perf and untyped titles qualify.
|
|
157
|
+
* The shapes come from commit-shape.ts (non-behavioural types, plus the housekeeping shapes: merges,
|
|
158
|
+
* drafts, backports, version bumps), so there is one list of what a mechanical subject looks like.
|
|
159
|
+
*/
|
|
160
|
+
export function isWriterTitle(title) {
|
|
161
|
+
return !isNonBehaviouralType(title) && !isHousekeepingTitle(title);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=blame-attribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blame-attribution.js","sourceRoot":"","sources":["../../src/lib/blame-attribution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type BlameOpts, type Range } from './blame-attribution.js';
|
|
2
|
+
import type { DecisionRow, LocalDb } from './local-db.js';
|
|
3
|
+
/**
|
|
4
|
+
* Candidates for the planning-time check (`align_check_alignment`): the decisions that wrote the code
|
|
5
|
+
* the change is about to touch, found with `git blame` and a keyed lookup, no model.
|
|
6
|
+
*
|
|
7
|
+
* - the input is a unified diff: blame each hunk's OLD-side lines (`blame-lines`);
|
|
8
|
+
* - otherwise, if the text names repo files that exist: the most recent decision writers of each file,
|
|
9
|
+
* capped at 3 per file (`blame-file`);
|
|
10
|
+
* - otherwise nothing.
|
|
11
|
+
*
|
|
12
|
+
* They are CANDIDATES. The existing judge still decides whether any of them is a conflict; being the
|
|
13
|
+
* writer of a line is never a conflict by itself. Same writer filters as the edit hook (isWriterTitle, and
|
|
14
|
+
* no blame for prose or lockfiles). The whole thing shares one time budget and fails open to [].
|
|
15
|
+
*/
|
|
16
|
+
export type BlameVia = 'blame-lines' | 'blame-file';
|
|
17
|
+
export interface BlameCandidate {
|
|
18
|
+
row: DecisionRow;
|
|
19
|
+
via: BlameVia;
|
|
20
|
+
}
|
|
21
|
+
export declare const CANDIDATE_BUDGET_MS = 2000;
|
|
22
|
+
/** Old-side line ranges per file of a unified diff. Pure insertions (old count 0) have no old lines to blame. */
|
|
23
|
+
export declare function parseDiffOldRanges(text: string): Map<string, Range[]>;
|
|
24
|
+
/** File-looking tokens in prose, in order, unique. A token only counts later if it is a real file inside the repo. */
|
|
25
|
+
export declare function pathsNamedIn(text: string): string[];
|
|
26
|
+
export declare function blameCandidates(text: string, cwd: string, db: Pick<LocalDb, 'findBySourceRefs'>, opts?: BlameOpts): Promise<BlameCandidate[]>;
|
|
27
|
+
//# sourceMappingURL=blame-candidates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blame-candidates.d.ts","sourceRoot":"","sources":["../../src/lib/blame-candidates.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,KAAK,SAAS,EAAmE,KAAK,KAAK,EAAE,MAAM,wBAAwB,CAAC;AACtK,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC;AACpD,MAAM,WAAW,cAAc;IAAG,GAAG,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,QAAQ,CAAA;CAAE;AAEnE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAQxC,iHAAiH;AACjH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAcrE;AAID,sHAAsH;AACtH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnD;AA4BD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,EACrC,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,cAAc,EAAE,CAAC,CAsB3B"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { blameCommits, isProseOrGeneratedFile, isWriterTitle, makeGitRunner, prUrlsFor } from './blame-attribution.js';
|
|
4
|
+
export const CANDIDATE_BUDGET_MS = 2000;
|
|
5
|
+
const MAX_FILES = 3;
|
|
6
|
+
const MAX_WRITERS_PER_FILE = 3;
|
|
7
|
+
const MAX_FILE_LINES = 5000;
|
|
8
|
+
const MAX_HUNK_LINES = 400;
|
|
9
|
+
/** Old-side line ranges per file of a unified diff. Pure insertions (old count 0) have no old lines to blame. */
|
|
10
|
+
export function parseDiffOldRanges(text) {
|
|
11
|
+
const out = new Map();
|
|
12
|
+
let file = null;
|
|
13
|
+
for (const line of text.split('\n')) {
|
|
14
|
+
const from = /^--- (?:a\/)?(\S.*?)(?:\t.*)?$/.exec(line);
|
|
15
|
+
if (from) {
|
|
16
|
+
file = from[1] === '/dev/null' ? null : from[1];
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const hunk = /^@@ -(\d+)(?:,(\d+))? \+\d+(?:,\d+)? @@/.exec(line);
|
|
20
|
+
if (hunk && file) {
|
|
21
|
+
const start = Number(hunk[1]);
|
|
22
|
+
const count = hunk[2] === undefined ? 1 : Number(hunk[2]);
|
|
23
|
+
if (count > 0)
|
|
24
|
+
out.set(file, [...(out.get(file) ?? []), [start, start + Math.min(count, MAX_HUNK_LINES) - 1]]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
const FILE_LIKE = /(?<![\w./@-])((?:[\w@.-]+\/)*[\w@-][\w@.-]*\.[A-Za-z][A-Za-z0-9]*)(?![\w-])/g;
|
|
30
|
+
/** File-looking tokens in prose, in order, unique. A token only counts later if it is a real file inside the repo. */
|
|
31
|
+
export function pathsNamedIn(text) {
|
|
32
|
+
return [...new Set([...text.matchAll(FILE_LIKE)].map((m) => m[1]))];
|
|
33
|
+
}
|
|
34
|
+
async function insideRepoFile(cwd, name) {
|
|
35
|
+
const abs = path.resolve(cwd, name);
|
|
36
|
+
const rel = path.relative(cwd, abs);
|
|
37
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
return (await stat(abs)).isFile() ? abs : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function targetsFor(text, cwd) {
|
|
47
|
+
const targets = [];
|
|
48
|
+
const diff = parseDiffOldRanges(text);
|
|
49
|
+
if (diff.size) {
|
|
50
|
+
for (const [name, ranges] of diff) {
|
|
51
|
+
if (targets.length >= MAX_FILES)
|
|
52
|
+
break;
|
|
53
|
+
const abs = isProseOrGeneratedFile(name) ? null : await insideRepoFile(cwd, name);
|
|
54
|
+
if (abs)
|
|
55
|
+
targets.push({ file: abs, ranges, via: 'blame-lines' });
|
|
56
|
+
}
|
|
57
|
+
return targets;
|
|
58
|
+
}
|
|
59
|
+
for (const name of pathsNamedIn(text)) {
|
|
60
|
+
if (targets.length >= MAX_FILES)
|
|
61
|
+
break;
|
|
62
|
+
const abs = isProseOrGeneratedFile(name) ? null : await insideRepoFile(cwd, name);
|
|
63
|
+
if (abs)
|
|
64
|
+
targets.push({ file: abs, ranges: null, via: 'blame-file' });
|
|
65
|
+
}
|
|
66
|
+
return targets;
|
|
67
|
+
}
|
|
68
|
+
export async function blameCandidates(text, cwd, db, opts = {}) {
|
|
69
|
+
try {
|
|
70
|
+
const targets = await targetsFor(text, cwd);
|
|
71
|
+
if (!targets.length)
|
|
72
|
+
return [];
|
|
73
|
+
const deadline = Date.now() + (opts.timeoutMs ?? CANDIDATE_BUDGET_MS);
|
|
74
|
+
const perFile = await Promise.all(targets.map(async (t) => {
|
|
75
|
+
try {
|
|
76
|
+
const run = (args) => makeGitRunner({ ...opts, timeoutMs: Math.max(1, deadline - Date.now()) }, path.dirname(t.file))(args);
|
|
77
|
+
const ranges = t.ranges ?? [[1, Math.min(MAX_FILE_LINES, (await readFile(t.file, 'utf8')).split('\n').length)]];
|
|
78
|
+
const commits = (await blameCommits(t.file, ranges, run)).sort((a, b) => b.time - a.time);
|
|
79
|
+
if (!commits.length)
|
|
80
|
+
return [];
|
|
81
|
+
const prUrls = await prUrlsFor(commits, run);
|
|
82
|
+
return rowsFor(commits, prUrls, db, t.via);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}));
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
return perFile.flat().filter((c) => (seen.has(c.row.id) ? false : (seen.add(c.row.id), true)));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Newest-first writers: each commit's decision (by sha, or by its PR url), filtered like the hook, capped per file for a whole-file blame. */
|
|
96
|
+
function rowsFor(commits, prUrls, db, via) {
|
|
97
|
+
const prFor = (c) => {
|
|
98
|
+
const n = /\(#(\d+)\)\s*$/.exec(c.subject)?.[1] ?? /^Merge pull request #(\d+)\b/.exec(c.subject)?.[1];
|
|
99
|
+
return n ? prUrls.filter((u) => u.endsWith(`/pull/${n}`)) : [];
|
|
100
|
+
};
|
|
101
|
+
const out = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const c of commits) {
|
|
104
|
+
for (const row of db.findBySourceRefs([c.sha], prFor(c))) {
|
|
105
|
+
if (seen.has(row.id) || !isWriterTitle(row.title))
|
|
106
|
+
continue;
|
|
107
|
+
seen.add(row.id);
|
|
108
|
+
out.push({ row, via });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return via === 'blame-file' ? out.slice(0, MAX_WRITERS_PER_FILE) : out;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=blame-candidates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blame-candidates.js","sourceRoot":"","sources":["../../src/lib/blame-candidates.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,4 +15,26 @@ export declare const MIN_DECISION_SUBJECT_CHARS = 20;
|
|
|
15
15
|
* Anchored and case-insensitive, as the hand-written literal it replaced was.
|
|
16
16
|
*/
|
|
17
17
|
export declare function mechanicalSubjectRegex(prefixes: readonly string[]): RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* A release or version-bump subject with no stated reason. In words, after any `type(scope):` and
|
|
20
|
+
* `(#N)` are removed it is housekeeping when it is only a version ("v5.9.0"), or the word
|
|
21
|
+
* bump/release alone or followed by a dotted version ("Bumped v5.9.0", "Release 5.9.0"), or a
|
|
22
|
+
* bump/update/upgrade of something FROM a version or TO a version ("Bump x from 1 to 2", "Update
|
|
23
|
+
* dependency x to v3") - unless it also states why (because, to avoid, drops, a CVE ...). Its
|
|
24
|
+
* complement keeps: a `release` scope ("feat(release): publish with provenance"), a bump with a
|
|
25
|
+
* reason, "Release 2FA support", "feat!: v6.0.0 drops Node 18", and Revert "Bump x" (the prefix list handles that).
|
|
26
|
+
* Dependency REPLACEMENTS ("replace a with b") are decisions and never match.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isReleaseOrBumpSubject(subject: string): boolean;
|
|
29
|
+
/** What the first-run box and the starter prompts must not show as a decision. */
|
|
30
|
+
export declare function isHousekeepingTitle(title: string): boolean;
|
|
31
|
+
/** The graph keeps a title as its first 80 characters (local-gateway-client.ts), so one this long may be cut mid-word. */
|
|
32
|
+
export declare const TITLE_CUT_AT = 80;
|
|
33
|
+
export declare function isCutTitle(title: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* A conventional-commit TYPE whose commits do not decide what the lines they touch do: docs, style,
|
|
36
|
+
* test, chore, ci, build, release and refactor. Separate from `isHousekeepingTitle` on purpose: that one
|
|
37
|
+
* feeds the first-run box and starter prompts and must not start hiding refactors there.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isNonBehaviouralType(title: string): boolean;
|
|
18
40
|
//# sourceMappingURL=commit-shape.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commit-shape.d.ts","sourceRoot":"","sources":["../../src/lib/commit-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,wFAE9B,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG1E"}
|
|
1
|
+
{"version":3,"file":"commit-shape.d.ts","sourceRoot":"","sources":["../../src/lib/commit-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,wFAE9B,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG1E;AAeD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAI/D;AASD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,0HAA0H;AAC1H,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
package/dist/lib/commit-shape.js
CHANGED
|
@@ -20,4 +20,56 @@ export function mechanicalSubjectRegex(prefixes) {
|
|
|
20
20
|
const escaped = prefixes.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
21
21
|
return new RegExp(`^(${escaped.join('|')})`, 'i');
|
|
22
22
|
}
|
|
23
|
+
const CONVENTIONAL_PREFIX = /^[a-z]+(?:\([^)]*\))?!?:\s*/i;
|
|
24
|
+
const PR_REFERENCE = /\s*\(#\d+[^)]*\)/g;
|
|
25
|
+
// "v5.9.0", "5.11.2", "Version 6.0.0-alpha.1": a subject that is only a version.
|
|
26
|
+
const VERSION_ONLY = /^(?:v(?:ersion)?\s*)?\d+\.\d+(?:\.\d+)?(?:[-+.][\w.-]+)?$/i;
|
|
27
|
+
// "Bump", "Bumped v5.9.0", "Release 5.9.0": the word alone, or the word and a dotted version.
|
|
28
|
+
const BARE_BUMP_OR_RELEASE = /^(?:bump\w*|releas\w*)(?:\s*$|\s+v?\d+\.\d)/i;
|
|
29
|
+
// One package token moving to a version, and nothing else: "bump x from 1 to 2", "Update dependency x to v3". More words
|
|
30
|
+
// after the version ("... and fix type definition"), or a sentence in between ("Update docs for listening to 0.0.0.0"), is prose.
|
|
31
|
+
const DEPENDENCY_MOVE = /^(?:bump\w*|updat\w*|upgrad\w*)\s+(?:dependency\s+)?[@\w./-]+\s+(?:from\s+v?\d[\w.-]*\s+)?(?:to\s+v?\d[\w.-]*)(?:\s+in\s.*|\s*\(.*\))?[.]?$/i;
|
|
32
|
+
// A subject that says why is a decision whatever its shape: "bump timeout ... because the upstream is slow",
|
|
33
|
+
// "bump minimum Node to 20, drops 18", "bump lodash to 4.17.21 (CVE-2021-23337)".
|
|
34
|
+
const STATED_WHY = /\b(?:because|so that|in order to|to avoid|to prevent|due to|since|cve-\d+|drops?|dropping|security|breaking)\b/i;
|
|
35
|
+
/**
|
|
36
|
+
* A release or version-bump subject with no stated reason. In words, after any `type(scope):` and
|
|
37
|
+
* `(#N)` are removed it is housekeeping when it is only a version ("v5.9.0"), or the word
|
|
38
|
+
* bump/release alone or followed by a dotted version ("Bumped v5.9.0", "Release 5.9.0"), or a
|
|
39
|
+
* bump/update/upgrade of something FROM a version or TO a version ("Bump x from 1 to 2", "Update
|
|
40
|
+
* dependency x to v3") - unless it also states why (because, to avoid, drops, a CVE ...). Its
|
|
41
|
+
* complement keeps: a `release` scope ("feat(release): publish with provenance"), a bump with a
|
|
42
|
+
* reason, "Release 2FA support", "feat!: v6.0.0 drops Node 18", and Revert "Bump x" (the prefix list handles that).
|
|
43
|
+
* Dependency REPLACEMENTS ("replace a with b") are decisions and never match.
|
|
44
|
+
*/
|
|
45
|
+
export function isReleaseOrBumpSubject(subject) {
|
|
46
|
+
const body = subject.trim().replace(CONVENTIONAL_PREFIX, '').replace(PR_REFERENCE, '').trim();
|
|
47
|
+
if (STATED_WHY.test(body))
|
|
48
|
+
return false;
|
|
49
|
+
return VERSION_ONLY.test(body) || BARE_BUMP_OR_RELEASE.test(body) || DEPENDENCY_MOVE.test(body);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Subjects that read as housekeeping, not as a choice. Conventional `type:` / `type(scope):` shapes
|
|
53
|
+
* only, so prose like "document the retry policy" is not demoted; git's own Merge subjects (not
|
|
54
|
+
* prose like "Merge the two workers"); a `[DRAFT` / `[WIP` / `[Backport` marker (a backport repeats a decision made in the original).
|
|
55
|
+
*/
|
|
56
|
+
const HOUSEKEEPING_SHAPE = /^(?:(?:docs|chore|ci|test|style|build)(?:\([^)]*\))?!?:|merge\s+(?:pull request|branch|remote-tracking|tag)\b|\[(?:draft|wip|backport)\b)/i;
|
|
57
|
+
/** What the first-run box and the starter prompts must not show as a decision. */
|
|
58
|
+
export function isHousekeepingTitle(title) {
|
|
59
|
+
return HOUSEKEEPING_SHAPE.test(title.trim()) || isReleaseOrBumpSubject(title);
|
|
60
|
+
}
|
|
61
|
+
/** The graph keeps a title as its first 80 characters (local-gateway-client.ts), so one this long may be cut mid-word. */
|
|
62
|
+
export const TITLE_CUT_AT = 80;
|
|
63
|
+
export function isCutTitle(title) {
|
|
64
|
+
return title.trim().length >= TITLE_CUT_AT;
|
|
65
|
+
}
|
|
66
|
+
const NON_BEHAVIOURAL_TYPE = /^(?:docs|style|test|chore|ci|build|release|refactor)(?:\([^)]*\))?!?:/i;
|
|
67
|
+
/**
|
|
68
|
+
* A conventional-commit TYPE whose commits do not decide what the lines they touch do: docs, style,
|
|
69
|
+
* test, chore, ci, build, release and refactor. Separate from `isHousekeepingTitle` on purpose: that one
|
|
70
|
+
* feeds the first-run box and starter prompts and must not start hiding refactors there.
|
|
71
|
+
*/
|
|
72
|
+
export function isNonBehaviouralType(title) {
|
|
73
|
+
return NON_BEHAVIOURAL_TYPE.test(title.trim());
|
|
74
|
+
}
|
|
23
75
|
//# sourceMappingURL=commit-shape.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commit-shape.js","sourceRoot":"","sources":["../../src/lib/commit-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;CACnE,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA2B;IAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9E,OAAO,IAAI,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC"}
|
|
1
|
+
{"version":3,"file":"commit-shape.js","sourceRoot":"","sources":["../../src/lib/commit-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;CACnE,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA2B;IAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9E,OAAO,IAAI,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC;AACzC,iFAAiF;AACjF,MAAM,YAAY,GAAG,4DAA4D,CAAC;AAClF,8FAA8F;AAC9F,MAAM,oBAAoB,GAAG,8CAA8C,CAAC;AAC5E,yHAAyH;AACzH,kIAAkI;AAClI,MAAM,eAAe,GAAG,8IAA8I,CAAC;AACvK,6GAA6G;AAC7G,kFAAkF;AAClF,MAAM,UAAU,GAAG,iHAAiH,CAAC;AAErI;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9F,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClG,CAAC;AAED;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,4IAA4I,CAAC;AAExK,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC;AAED,0HAA0H;AAC1H,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,YAAY,CAAC;AAC7C,CAAC;AAED,MAAM,oBAAoB,GAAG,wEAAwE,CAAC;AAEtG;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -50,4 +50,13 @@ export declare function statusGapLine(gap: UnresolvedGap): string;
|
|
|
50
50
|
export declare function askTrailingLine(refs: Array<{
|
|
51
51
|
platform: string;
|
|
52
52
|
}>, isConnected: (connectorId: string) => boolean): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* The gap hint for a whole `align ask` result set, printed once after the rows rather than
|
|
55
|
+
* under each one. One line per distinct gap, each with how many rows carry it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function askGapHints(rows: Array<{
|
|
58
|
+
refs: Array<{
|
|
59
|
+
platform: string;
|
|
60
|
+
}>;
|
|
61
|
+
}>, isConnected: (connectorId: string) => boolean): string[];
|
|
53
62
|
//# sourceMappingURL=connect-prompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-prompt.d.ts","sourceRoot":"","sources":["../../src/lib/connect-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B;mFAC+E;IAC/E,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAClC;;uFAEmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB;;8CAE0C;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAgCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACrD,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,aAAa,EAAE,CAkBjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CASrE;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAKxD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACjC,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,MAAM,GAAG,IAAI,CASf"}
|
|
1
|
+
{"version":3,"file":"connect-prompt.d.ts","sourceRoot":"","sources":["../../src/lib/connect-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B;mFAC+E;IAC/E,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAClC;;uFAEmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB;;8CAE0C;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAgCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACrD,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,aAAa,EAAE,CAkBjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CASrE;AAED,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAKxD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACjC,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,MAAM,GAAG,IAAI,CASf;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,EAClD,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,MAAM,EAAE,CAOV"}
|
|
@@ -99,4 +99,17 @@ export function askTrailingLine(refs, isConnected) {
|
|
|
99
99
|
}
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The gap hint for a whole `align ask` result set, printed once after the rows rather than
|
|
104
|
+
* under each one. One line per distinct gap, each with how many rows carry it.
|
|
105
|
+
*/
|
|
106
|
+
export function askGapHints(rows, isConnected) {
|
|
107
|
+
const counts = new Map();
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
const line = askTrailingLine(row.refs, isConnected);
|
|
110
|
+
if (line)
|
|
111
|
+
counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
112
|
+
}
|
|
113
|
+
return [...counts].map(([line, n]) => `${n} of these ${n === 1 ? line : line.replace(/^cites /, 'cite ')}`);
|
|
114
|
+
}
|
|
102
115
|
//# sourceMappingURL=connect-prompt.js.map
|