@aligndottech/cli 0.66.0 → 0.67.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/README.md +33 -2
- package/dist/commands/check.d.ts +19 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +144 -29
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/import/docs.js +2 -2
- package/dist/commands/import/docs.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +3 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +3 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/rule.d.ts +28 -0
- package/dist/commands/rule.d.ts.map +1 -0
- package/dist/commands/rule.js +100 -0
- package/dist/commands/rule.js.map +1 -0
- package/dist/lib/advisory-verdict.d.ts +2 -0
- package/dist/lib/advisory-verdict.d.ts.map +1 -1
- package/dist/lib/advisory-verdict.js +2 -0
- package/dist/lib/advisory-verdict.js.map +1 -1
- package/dist/lib/co-change-check.d.ts +33 -0
- package/dist/lib/co-change-check.d.ts.map +1 -0
- package/dist/lib/co-change-check.js +95 -0
- package/dist/lib/co-change-check.js.map +1 -0
- package/dist/lib/co-change-rule.d.ts +11 -0
- package/dist/lib/co-change-rule.d.ts.map +1 -0
- package/dist/lib/co-change-rule.js +2 -0
- package/dist/lib/co-change-rule.js.map +1 -0
- package/dist/lib/co-change-store.d.ts +25 -0
- package/dist/lib/co-change-store.d.ts.map +1 -0
- package/dist/lib/co-change-store.js +142 -0
- package/dist/lib/co-change-store.js.map +1 -0
- package/dist/lib/co-change.d.ts +54 -0
- package/dist/lib/co-change.d.ts.map +1 -0
- package/dist/lib/co-change.js +120 -0
- package/dist/lib/co-change.js.map +1 -0
- package/dist/lib/commit-shape.d.ts +16 -0
- package/dist/lib/commit-shape.d.ts.map +1 -1
- package/dist/lib/commit-shape.js +34 -2
- package/dist/lib/commit-shape.js.map +1 -1
- package/dist/lib/decision-links.d.ts +24 -0
- package/dist/lib/decision-links.d.ts.map +1 -1
- package/dist/lib/decision-links.js +69 -1
- package/dist/lib/decision-links.js.map +1 -1
- package/dist/lib/fetchers/docs-conventions.d.ts +29 -0
- package/dist/lib/fetchers/docs-conventions.d.ts.map +1 -0
- package/dist/lib/fetchers/docs-conventions.js +95 -0
- package/dist/lib/fetchers/docs-conventions.js.map +1 -0
- package/dist/lib/fetchers/docs.d.ts +6 -0
- package/dist/lib/fetchers/docs.d.ts.map +1 -1
- package/dist/lib/fetchers/docs.js +79 -11
- package/dist/lib/fetchers/docs.js.map +1 -1
- package/dist/lib/gateway-client.d.ts +4 -0
- package/dist/lib/gateway-client.d.ts.map +1 -1
- package/dist/lib/gateway-client.js.map +1 -1
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +36 -10
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/local-db-cochange.d.ts +17 -0
- package/dist/lib/local-db-cochange.d.ts.map +1 -0
- package/dist/lib/local-db-cochange.js +23 -0
- package/dist/lib/local-db-cochange.js.map +1 -0
- package/dist/lib/local-db-migrate.d.ts.map +1 -1
- package/dist/lib/local-db-migrate.js +19 -0
- package/dist/lib/local-db-migrate.js.map +1 -1
- package/dist/lib/local-db.js +1 -1
- package/dist/lib/local-db.js.map +1 -1
- package/dist/lib/local-gateway-client.d.ts.map +1 -1
- package/dist/lib/local-gateway-client.js +20 -0
- package/dist/lib/local-gateway-client.js.map +1 -1
- package/dist/lib/local-ingest.d.ts +2 -0
- package/dist/lib/local-ingest.d.ts.map +1 -1
- package/dist/lib/local-ingest.js.map +1 -1
- package/dist/lib/mcp-instructions.shared.d.ts +1 -1
- package/dist/lib/mcp-instructions.shared.d.ts.map +1 -1
- package/dist/lib/mcp-instructions.shared.js +3 -3
- package/dist/lib/mcp-instructions.shared.js.map +1 -1
- package/dist/lib/mcp-timeline-tools.d.ts.map +1 -1
- package/dist/lib/mcp-timeline-tools.js +5 -3
- package/dist/lib/mcp-timeline-tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads and writes of `co_change_rules` (local-db-cochange.ts). Same short-lived-handle pattern as
|
|
3
|
+
* curation/judgements-db.ts, so the MCP server and a sync child can both be live. A reader never
|
|
4
|
+
* creates a missing graph.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
9
|
+
import { assertSchemaSupported } from './local-db-migrate.js';
|
|
10
|
+
import { tightenGraphFiles } from './private-files.js';
|
|
11
|
+
import { createLocalDb } from './local-db.js';
|
|
12
|
+
import { parseCoChangeRule } from './co-change.js';
|
|
13
|
+
/** A graph from before the repo column has the table but not the column. The write-lock migration lives in createLocalDb, so run that once and read again. */
|
|
14
|
+
function ensureCurrent(dbPath) {
|
|
15
|
+
const db = new DatabaseSync(dbPath);
|
|
16
|
+
let stale = false;
|
|
17
|
+
try {
|
|
18
|
+
stale = db.prepare('PRAGMA table_info(co_change_rules)').all().length > 0
|
|
19
|
+
&& !db.prepare('PRAGMA table_info(co_change_rules)').all().some((c) => c.name === 'repo');
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
db.close();
|
|
23
|
+
}
|
|
24
|
+
if (stale)
|
|
25
|
+
createLocalDb(dbPath).close();
|
|
26
|
+
}
|
|
27
|
+
function openRead(dbPath) {
|
|
28
|
+
ensureCurrent(dbPath);
|
|
29
|
+
tightenGraphFiles(dbPath);
|
|
30
|
+
const db = new DatabaseSync(dbPath);
|
|
31
|
+
db.exec('PRAGMA busy_timeout = 30000');
|
|
32
|
+
assertSchemaSupported(db.prepare('PRAGMA user_version').get().user_version, db);
|
|
33
|
+
return db;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The decision's identity is (source_url, title), so the url is derived from the rule: the same title
|
|
37
|
+
* and rule re-added is the same decision, and the same title with a different rule is another one.
|
|
38
|
+
* It is an opaque key, never a link, and the listing does not surface it as one.
|
|
39
|
+
*/
|
|
40
|
+
function ruleSourceKey(title, rule, repo) {
|
|
41
|
+
const digest = createHash('sha256').update(JSON.stringify([title, repo, rule.when_paths, rule.require_paths, rule.scope])).digest('hex').slice(0, 16);
|
|
42
|
+
return `align-rule://co-change/${digest}`;
|
|
43
|
+
}
|
|
44
|
+
export function addCoChangeRule(dbPath, input) {
|
|
45
|
+
const rule = parseCoChangeRule(input.rule);
|
|
46
|
+
const title = input.title.trim();
|
|
47
|
+
const repo = input.repo ?? null;
|
|
48
|
+
if (title === '')
|
|
49
|
+
throw new Error('A co-change rule needs a title: the decision it records');
|
|
50
|
+
const summary = input.summary?.trim() || `A change touching ${rule.when_paths.join(', ')} must also touch ${rule.require_paths.join(' or ')} in the same PR.`;
|
|
51
|
+
const store = createLocalDb(dbPath);
|
|
52
|
+
try {
|
|
53
|
+
const sourceUrl = ruleSourceKey(title, rule, repo);
|
|
54
|
+
const existing = store.findIdBySource(sourceUrl, title);
|
|
55
|
+
const decisionId = store.insertDecision({ title, summary, sourceUrl, platform: 'cli', repo, deciderKind: 'human' });
|
|
56
|
+
const db = new DatabaseSync(dbPath);
|
|
57
|
+
try {
|
|
58
|
+
db.exec('PRAGMA busy_timeout = 30000');
|
|
59
|
+
db.prepare(`INSERT OR IGNORE INTO co_change_rules (id, decision_id, repo, when_paths, require_paths, scope) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
60
|
+
.run(randomUUID(), decisionId, repo, JSON.stringify(rule.when_paths), JSON.stringify(rule.require_paths), rule.scope);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
db.close();
|
|
64
|
+
}
|
|
65
|
+
return { decisionId, created: existing === null };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
store.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The docs import's writer: the rules on `decisionId` become exactly `raw` (one rule, or none).
|
|
73
|
+
* Replacing rather than adding is what lets a README that drops a rule drop it here too. Only the
|
|
74
|
+
* imported decision's own rules are touched; a hand-added rule lives on its own decision
|
|
75
|
+
* (addCoChangeRule). A rule that does not parse is skipped, never thrown: one bad section must not
|
|
76
|
+
* fail the import of every other one. True when a rule was stored.
|
|
77
|
+
*/
|
|
78
|
+
export function syncImportedCoChangeRule(dbPath, decisionId, repo, raw) {
|
|
79
|
+
let rule;
|
|
80
|
+
try {
|
|
81
|
+
rule = raw === undefined ? undefined : parseCoChangeRule(raw);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
rule = undefined;
|
|
85
|
+
}
|
|
86
|
+
const db = new DatabaseSync(dbPath);
|
|
87
|
+
try {
|
|
88
|
+
db.exec('PRAGMA busy_timeout = 30000');
|
|
89
|
+
const stored = db.prepare('SELECT repo, when_paths AS w, require_paths AS r, scope FROM co_change_rules WHERE decision_id = ?')
|
|
90
|
+
.all(decisionId);
|
|
91
|
+
// The same rule imported again keeps its row, so its id (what `align rule remove` takes) is stable.
|
|
92
|
+
if (rule !== undefined && stored.length === 1 && stored[0].repo === repo && stored[0].w === JSON.stringify(rule.when_paths)
|
|
93
|
+
&& stored[0].r === JSON.stringify(rule.require_paths) && stored[0].scope === rule.scope)
|
|
94
|
+
return true;
|
|
95
|
+
db.prepare('DELETE FROM co_change_rules WHERE decision_id = ?').run(decisionId);
|
|
96
|
+
if (rule === undefined)
|
|
97
|
+
return false;
|
|
98
|
+
db.prepare(`INSERT INTO co_change_rules (id, decision_id, repo, when_paths, require_paths, scope) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
99
|
+
.run(randomUUID(), decisionId, repo, JSON.stringify(rule.when_paths), JSON.stringify(rule.require_paths), rule.scope);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
db.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Every rule whose decision still exists. A missing graph, or one that predates the table, lists nothing. */
|
|
107
|
+
export function listCoChangeRules(dbPath, scope = {}) {
|
|
108
|
+
if (dbPath === '' || !fs.existsSync(dbPath))
|
|
109
|
+
return [];
|
|
110
|
+
const db = openRead(dbPath);
|
|
111
|
+
try {
|
|
112
|
+
const rows = db.prepare(`SELECT r.id AS ruleId, r.repo AS repo, d.id AS id, d.title AS title, d.summary AS summary, r.when_paths AS whenPaths, r.require_paths AS requirePaths, r.scope AS scope
|
|
113
|
+
FROM co_change_rules r JOIN decisions d ON d.id = r.decision_id ORDER BY r.created_at, r.rowid`).all();
|
|
114
|
+
// Asking for a repo keeps that repo's rules and the ones with no repo; asking for none keeps all.
|
|
115
|
+
const wanted = scope.repo === undefined ? rows : rows.filter((r) => r.repo === null || r.repo === scope.repo);
|
|
116
|
+
return wanted.map((r) => ({
|
|
117
|
+
ruleId: r.ruleId, repo: r.repo, decisionId: r.id, title: r.title, summary: r.summary,
|
|
118
|
+
rule: parseCoChangeRule({ when_paths: JSON.parse(r.whenPaths), require_paths: JSON.parse(r.requirePaths), scope: r.scope }),
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
if (/no such table/i.test(e.message))
|
|
123
|
+
return [];
|
|
124
|
+
throw e;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
db.close();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Deletes the rule and keeps its decision. False when no rule has that id. */
|
|
131
|
+
export function removeCoChangeRule(dbPath, ruleId) {
|
|
132
|
+
if (dbPath === '' || !fs.existsSync(dbPath))
|
|
133
|
+
return false;
|
|
134
|
+
const db = openRead(dbPath);
|
|
135
|
+
try {
|
|
136
|
+
return Number(db.prepare('DELETE FROM co_change_rules WHERE id = ?').run(ruleId).changes) > 0;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
db.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=co-change-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"co-change-store.js","sourceRoot":"","sources":["../../src/lib/co-change-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAqB,iBAAiB,EAA2B,MAAM,gBAAgB,CAAC;AAE/F,8JAA8J;AAC9J,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAI,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,EAA8B,CAAC,MAAM,GAAG,CAAC;eACjG,CAAE,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,EAA8B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC3H,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;IACD,IAAI,KAAK;QAAE,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc;IAC9B,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACvC,qBAAqB,CAAE,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,EAA+B,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC9G,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAa,EAAE,IAAkB,EAAE,IAAmB;IAC3E,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtJ,OAAO,0BAA0B,MAAM,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,KAAoF;IAClI,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC;IAChC,IAAI,KAAK,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7F,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,qBAAqB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;IAC9J,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACpH,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YACvC,EAAE,CAAC,OAAO,CAAC,2HAA2H,CAAC;iBACpI,GAAG,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1H,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI,EAAE,CAAC;IACpD,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,UAAkB,EAAE,IAAmB,EAAE,GAAY;IAC5G,IAAI,IAA8B,CAAC;IACnC,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,oGAAoG,CAAC;aAC5H,GAAG,CAAC,UAAU,CAAwE,CAAC;QAC1F,oGAAoG;QACpG,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC;eACtH,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACvG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChF,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACrC,EAAE,CAAC,OAAO,CAAC,iHAAiH,CAAC;aAC1H,GAAG,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxH,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,QAAkC,EAAE;IACpF,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACvD,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB;wGACkG,CACnG,CAAC,GAAG,EAAwJ,CAAC;QAC9J,kGAAkG;QAClG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9G,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO;YACpF,IAAI,EAAE,iBAAiB,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;SAC5H,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,gBAAgB,CAAC,IAAI,CAAE,CAAW,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAC3D,MAAM,CAAC,CAAC;IACV,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,MAAc;IAC/D,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChG,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-change rules: "a change that touches X must also touch Y, in the same PR".
|
|
3
|
+
*
|
|
4
|
+
* This is a question about WHICH FILES a change touches, so it has one right answer and is computed
|
|
5
|
+
* from the changed-file list, never judged. The LLM check cannot do it: it reads a slice of the diff,
|
|
6
|
+
* and a file a PR forgot to touch is not in a diff at all.
|
|
7
|
+
*
|
|
8
|
+
* The rule shape is shared with the import lane (README rules) and the hosted gateway. Do not rename
|
|
9
|
+
* its fields.
|
|
10
|
+
*/
|
|
11
|
+
export type CoChangeScope = 'same_pr';
|
|
12
|
+
export interface CoChangeRule {
|
|
13
|
+
/** Globs. The rule applies when the change touches any of them. */
|
|
14
|
+
when_paths: string[];
|
|
15
|
+
/** Globs. The rule is met when the change touches any of them. */
|
|
16
|
+
require_paths: string[];
|
|
17
|
+
scope: CoChangeScope;
|
|
18
|
+
}
|
|
19
|
+
/** A rule with the decision it belongs to, as the local graph returns it. */
|
|
20
|
+
export interface StoredCoChangeRule {
|
|
21
|
+
ruleId: string;
|
|
22
|
+
repo: string | null;
|
|
23
|
+
decisionId: string;
|
|
24
|
+
title: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
rule: CoChangeRule;
|
|
28
|
+
}
|
|
29
|
+
export interface CoChangeViolation {
|
|
30
|
+
decisionId: string;
|
|
31
|
+
title: string;
|
|
32
|
+
summary: string;
|
|
33
|
+
url?: string;
|
|
34
|
+
/** The changed files that matched a when path. */
|
|
35
|
+
triggeredBy: string[];
|
|
36
|
+
/** The require globs, none of which matched a changed file. */
|
|
37
|
+
missing: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Not a RegExp on purpose. `**a**a**a...**b` compiled to `.*a.*a.*a...b` backtracks exponentially
|
|
41
|
+
* (68s measured at eight repeats against sixty characters), and a rule is data a person or another
|
|
42
|
+
* lane wrote. This walks the tokens once, keeping the set of path positions still reachable, so the
|
|
43
|
+
* cost is tokens x path length whatever the glob looks like.
|
|
44
|
+
*/
|
|
45
|
+
export declare function pathMatches(glob: string, path: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a rule from outside (a flag, a stored row, another lane's writer). An empty list is
|
|
48
|
+
* refused rather than stored: empty require_paths could never be met, so the rule would flag every
|
|
49
|
+
* matching PR forever, and empty when_paths could never trigger.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseCoChangeRule(raw: unknown): CoChangeRule;
|
|
52
|
+
/** Every rule the change violates: it touches a when path and touches none of the require paths. */
|
|
53
|
+
export declare function evaluateCoChange(rules: StoredCoChangeRule[], changed: string[]): CoChangeViolation[];
|
|
54
|
+
//# sourceMappingURL=co-change.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"co-change.d.ts","sourceRoot":"","sources":["../../src/lib/co-change.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC;AAEtC,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE;AAEjK,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAkCD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CA0B/D;AAeD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAK5D;AAED,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,iBAAiB,EAAE,CASpG"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
function normalise(p) {
|
|
2
|
+
return p.replace(/^(?:\.\/)+/, '').replace(/^\/+/, '');
|
|
3
|
+
}
|
|
4
|
+
const tokenCache = new Map();
|
|
5
|
+
/** `*` and `?` stay inside one path segment; `**` crosses segments; a trailing `/` means everything under it. */
|
|
6
|
+
function tokenise(glob) {
|
|
7
|
+
const cached = tokenCache.get(glob);
|
|
8
|
+
if (cached)
|
|
9
|
+
return cached;
|
|
10
|
+
let g = normalise(glob);
|
|
11
|
+
if (g.endsWith('/'))
|
|
12
|
+
g += '**';
|
|
13
|
+
const tokens = [];
|
|
14
|
+
for (let i = 0; i < g.length; i++) {
|
|
15
|
+
const c = g[i];
|
|
16
|
+
if (c === '*' && g[i + 1] === '*') {
|
|
17
|
+
if (g[i + 2] === '/') {
|
|
18
|
+
tokens.push({ kind: 'dirs' });
|
|
19
|
+
i += 2;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
tokens.push({ kind: 'globstar' });
|
|
23
|
+
i += 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (c === '*')
|
|
27
|
+
tokens.push({ kind: 'star' });
|
|
28
|
+
else if (c === '?')
|
|
29
|
+
tokens.push({ kind: 'one' });
|
|
30
|
+
else
|
|
31
|
+
tokens.push({ kind: 'lit', ch: c });
|
|
32
|
+
}
|
|
33
|
+
tokenCache.set(glob, tokens);
|
|
34
|
+
return tokens;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Not a RegExp on purpose. `**a**a**a...**b` compiled to `.*a.*a.*a...b` backtracks exponentially
|
|
38
|
+
* (68s measured at eight repeats against sixty characters), and a rule is data a person or another
|
|
39
|
+
* lane wrote. This walks the tokens once, keeping the set of path positions still reachable, so the
|
|
40
|
+
* cost is tokens x path length whatever the glob looks like.
|
|
41
|
+
*/
|
|
42
|
+
export function pathMatches(glob, path) {
|
|
43
|
+
const p = normalise(path);
|
|
44
|
+
const n = p.length;
|
|
45
|
+
let cur = new Uint8Array(n + 1);
|
|
46
|
+
cur[0] = 1;
|
|
47
|
+
for (const t of tokenise(glob)) {
|
|
48
|
+
const next = new Uint8Array(n + 1);
|
|
49
|
+
if (t.kind === 'lit') {
|
|
50
|
+
for (let j = 0; j < n; j++)
|
|
51
|
+
if (cur[j] && p[j] === t.ch)
|
|
52
|
+
next[j + 1] = 1;
|
|
53
|
+
}
|
|
54
|
+
else if (t.kind === 'one') {
|
|
55
|
+
for (let j = 0; j < n; j++)
|
|
56
|
+
if (cur[j] && p[j] !== '/')
|
|
57
|
+
next[j + 1] = 1;
|
|
58
|
+
}
|
|
59
|
+
else if (t.kind === 'star') {
|
|
60
|
+
for (let j = 0; j <= n; j++)
|
|
61
|
+
next[j] = cur[j] || (j > 0 && next[j - 1] && p[j - 1] !== '/') ? 1 : 0;
|
|
62
|
+
}
|
|
63
|
+
else if (t.kind === 'globstar') {
|
|
64
|
+
for (let j = 0; j <= n; j++)
|
|
65
|
+
next[j] = cur[j] || (j > 0 && next[j - 1]) ? 1 : 0;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// zero directories, or any run of characters that ends in a slash
|
|
69
|
+
let seen = false; // some cur[k] with k <= j - 1
|
|
70
|
+
for (let j = 0; j <= n; j++) {
|
|
71
|
+
if (j > 0 && cur[j - 1])
|
|
72
|
+
seen = true;
|
|
73
|
+
next[j] = cur[j] || (j > 0 && p[j - 1] === '/' && seen) ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
cur = next;
|
|
77
|
+
}
|
|
78
|
+
return cur[n] === 1;
|
|
79
|
+
}
|
|
80
|
+
/** Syntax from fuller glob dialects. The matcher would treat each as a literal character, so the rule would never trigger or never be met. */
|
|
81
|
+
const UNSUPPORTED_GLOB = /[{}[\]!\\]/;
|
|
82
|
+
function pathList(raw, field) {
|
|
83
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
84
|
+
throw new Error(`${field} must be a non-empty list of paths or globs`);
|
|
85
|
+
for (const p of raw) {
|
|
86
|
+
if (typeof p !== 'string' || p.trim() === '')
|
|
87
|
+
throw new Error(`${field} must contain only non-blank strings`);
|
|
88
|
+
const bad = UNSUPPORTED_GLOB.exec(p);
|
|
89
|
+
if (bad)
|
|
90
|
+
throw new Error(`${field}: "${p}" uses ${bad[0] === '\\' ? 'a backslash' : `"${bad[0]}"`}, which is not supported. Only *, ** and ? are: no {a,b} lists, [abc] classes, ! negation or escapes. Write one path per alternative.`);
|
|
91
|
+
}
|
|
92
|
+
return raw;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validates a rule from outside (a flag, a stored row, another lane's writer). An empty list is
|
|
96
|
+
* refused rather than stored: empty require_paths could never be met, so the rule would flag every
|
|
97
|
+
* matching PR forever, and empty when_paths could never trigger.
|
|
98
|
+
*/
|
|
99
|
+
export function parseCoChangeRule(raw) {
|
|
100
|
+
if (typeof raw !== 'object' || raw === null)
|
|
101
|
+
throw new Error('A co-change rule must be an object');
|
|
102
|
+
const r = raw;
|
|
103
|
+
if (r['scope'] !== 'same_pr')
|
|
104
|
+
throw new Error("scope must be 'same_pr'");
|
|
105
|
+
return { when_paths: pathList(r['when_paths'], 'when_paths'), require_paths: pathList(r['require_paths'], 'require_paths'), scope: 'same_pr' };
|
|
106
|
+
}
|
|
107
|
+
/** Every rule the change violates: it touches a when path and touches none of the require paths. */
|
|
108
|
+
export function evaluateCoChange(rules, changed) {
|
|
109
|
+
const violations = [];
|
|
110
|
+
for (const { decisionId, title, summary, url, rule } of rules) {
|
|
111
|
+
const triggeredBy = changed.filter((f) => rule.when_paths.some((g) => pathMatches(g, f)));
|
|
112
|
+
if (triggeredBy.length === 0)
|
|
113
|
+
continue;
|
|
114
|
+
if (changed.some((f) => rule.require_paths.some((g) => pathMatches(g, f))))
|
|
115
|
+
continue;
|
|
116
|
+
violations.push({ decisionId, title, summary, ...(url ? { url } : {}), triggeredBy, missing: [...rule.require_paths] });
|
|
117
|
+
}
|
|
118
|
+
return violations;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=co-change.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"co-change.js","sourceRoot":"","sources":["../../src/lib/co-change.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@ 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
|
+
export declare const CONVENTIONAL_PREFIX: RegExp;
|
|
19
|
+
export declare const STATED_WHY: RegExp;
|
|
18
20
|
/**
|
|
19
21
|
* A release or version-bump subject with no stated reason. In words, after any `type(scope):` and
|
|
20
22
|
* `(#N)` are removed it is housekeeping when it is only a version ("v5.9.0"), or the word
|
|
@@ -37,4 +39,18 @@ export declare function isCutTitle(title: string): boolean;
|
|
|
37
39
|
* feeds the first-run box and starter prompts and must not start hiding refactors there.
|
|
38
40
|
*/
|
|
39
41
|
export declare function isNonBehaviouralType(title: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Is this a title-only change note: it leads with a one-off verb (add, fix, rename, update,
|
|
44
|
+
* remove, bump, refactor, document, typo, tidy), or a one-off conventional type (fix, test,
|
|
45
|
+
* refactor, chore, ci, docs, style, build), and carries nothing that makes it a decision? A PR
|
|
46
|
+
* title taken from a merge commit has no body to say why, so this is the only thing standing
|
|
47
|
+
* between a "rename abandonment endpoint" and the graph.
|
|
48
|
+
*
|
|
49
|
+
* Anything that does not lead like that is NOT one-off: decision verbs (use, switch, migrate,
|
|
50
|
+
* replace, drop, stop, move, change, make), `feat`, an `RFC:` prefix, and any title in a language
|
|
51
|
+
* whose verbs this list does not know. A one-off lead is rescued by a stated purpose
|
|
52
|
+
* (STATED_WHY: "so that", "to keep", "because" ...), a policy word or modal (adopt, require,
|
|
53
|
+
* never, should not ...), or a sweeping quantifier (all, every).
|
|
54
|
+
*/
|
|
55
|
+
export declare function isOneOffChangeTitle(title: string): boolean;
|
|
40
56
|
//# 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,eAAO,MAAM,mBAAmB,QAAiC,CAAC;AAWlE,eAAO,MAAM,UAAU,QAA+J,CAAC;AAEvL;;;;;;;;;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;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAS1D"}
|
package/dist/lib/commit-shape.js
CHANGED
|
@@ -20,7 +20,7 @@ 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;
|
|
23
|
+
export const CONVENTIONAL_PREFIX = /^[a-z]+(?:\([^)]*\))?!?:\s*/i;
|
|
24
24
|
const PR_REFERENCE = /\s*\(#\d+[^)]*\)/g;
|
|
25
25
|
// "v5.9.0", "5.11.2", "Version 6.0.0-alpha.1": a subject that is only a version.
|
|
26
26
|
const VERSION_ONLY = /^(?:v(?:ersion)?\s*)?\d+\.\d+(?:\.\d+)?(?:[-+.][\w.-]+)?$/i;
|
|
@@ -31,7 +31,7 @@ const BARE_BUMP_OR_RELEASE = /^(?:bump\w*|releas\w*)(?:\s*$|\s+v?\d+\.\d)/i;
|
|
|
31
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
32
|
// A subject that says why is a decision whatever its shape: "bump timeout ... because the upstream is slow",
|
|
33
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;
|
|
34
|
+
export const STATED_WHY = /\b(?:because|so that|so we|so it|in order to|to keep|to avoid|to prevent|to ensure|to make sure|due to|since|cve-\d+|drops?|dropping|security|breaking)\b/i;
|
|
35
35
|
/**
|
|
36
36
|
* A release or version-bump subject with no stated reason. In words, after any `type(scope):` and
|
|
37
37
|
* `(#N)` are removed it is housekeeping when it is only a version ("v5.9.0"), or the word
|
|
@@ -72,4 +72,36 @@ const NON_BEHAVIOURAL_TYPE = /^(?:docs|style|test|chore|ci|build|release|refacto
|
|
|
72
72
|
export function isNonBehaviouralType(title) {
|
|
73
73
|
return NON_BEHAVIOURAL_TYPE.test(title.trim());
|
|
74
74
|
}
|
|
75
|
+
const POLICY_WORD = /\b(?:adopt|requires?|standardi[sz]e[sd]?|enforce[sd]?|mandate[sd]?|deprecate[sd]?|always|never|must|only|all|every|no longer|should(?: not)?|should(?:n't)?|do not|don't)\b/i;
|
|
76
|
+
const ONE_OFF_VERBS = /^(?:add|fix|rename|update|remove|bump|refactor|document|extend|post|put|modify|typo|tidy)\b/i;
|
|
77
|
+
const ONE_OFF_TYPES = new Set(['fix', 'test', 'refactor', 'chore', 'ci', 'docs', 'style', 'build']);
|
|
78
|
+
const TYPO = /\btypos?\b/i;
|
|
79
|
+
const SWEEPING_QUANTIFIER = /\b(?:all|every)\b/i;
|
|
80
|
+
const TYPE_PREFIX = /^([a-z]+)(?:\([^)]*\))?!?:\s*/i;
|
|
81
|
+
/**
|
|
82
|
+
* Is this a title-only change note: it leads with a one-off verb (add, fix, rename, update,
|
|
83
|
+
* remove, bump, refactor, document, typo, tidy), or a one-off conventional type (fix, test,
|
|
84
|
+
* refactor, chore, ci, docs, style, build), and carries nothing that makes it a decision? A PR
|
|
85
|
+
* title taken from a merge commit has no body to say why, so this is the only thing standing
|
|
86
|
+
* between a "rename abandonment endpoint" and the graph.
|
|
87
|
+
*
|
|
88
|
+
* Anything that does not lead like that is NOT one-off: decision verbs (use, switch, migrate,
|
|
89
|
+
* replace, drop, stop, move, change, make), `feat`, an `RFC:` prefix, and any title in a language
|
|
90
|
+
* whose verbs this list does not know. A one-off lead is rescued by a stated purpose
|
|
91
|
+
* (STATED_WHY: "so that", "to keep", "because" ...), a policy word or modal (adopt, require,
|
|
92
|
+
* never, should not ...), or a sweeping quantifier (all, every).
|
|
93
|
+
*/
|
|
94
|
+
export function isOneOffChangeTitle(title) {
|
|
95
|
+
const t = title.trim().replace(PR_REFERENCE, '').trim();
|
|
96
|
+
const prefix = t.match(TYPE_PREFIX);
|
|
97
|
+
let oneOff;
|
|
98
|
+
if (prefix)
|
|
99
|
+
oneOff = ONE_OFF_TYPES.has(prefix[1].toLowerCase()) || TYPO.test(t);
|
|
100
|
+
else
|
|
101
|
+
oneOff = ONE_OFF_VERBS.test(t) || TYPO.test(t);
|
|
102
|
+
if (!oneOff)
|
|
103
|
+
return false;
|
|
104
|
+
const body = prefix ? t.slice(prefix[0].length) : t;
|
|
105
|
+
return !(STATED_WHY.test(body) || POLICY_WORD.test(body) || SWEEPING_QUANTIFIER.test(body));
|
|
106
|
+
}
|
|
75
107
|
//# 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;AAED,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"commit-shape.js","sourceRoot":"","sources":["../../src/lib/commit-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,4 +67,28 @@ export declare function isSyntheticSource(sourceUrl: string | undefined | null):
|
|
|
67
67
|
* inventing a substitute link here; that is the caller's call (e.g. a decision-page URL).
|
|
68
68
|
*/
|
|
69
69
|
export declare function navigableSourceUrl(sourceUrl: string | undefined | null): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* The link to print under a decision: where it was decided when that is somewhere a person can go, else the link
|
|
72
|
+
* that opens it in Align (`decision_url`). Never an align:// value. A seeded decision has no source to open, so
|
|
73
|
+
* without the fallback `align check` printed nothing under it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function decisionLinkFor(row: {
|
|
76
|
+
url?: string | null;
|
|
77
|
+
decision_url?: string | null;
|
|
78
|
+
}): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The one guard between a tool result and the model that reads it. Every MCP result is the gateway's JSON,
|
|
81
|
+
* forwarded as it came, so an align:// value in `url` or `source_url` reached the agent and was printed as a link
|
|
82
|
+
* ("Decision link: align://demo/..."), and a claimed or unsourced identity was handed over as if it were the
|
|
83
|
+
* origin. This is the CLI's copy of the hosted connector's withSourceProvenance and withoutUnopenableUrl
|
|
84
|
+
* (align-stack connectors/mcp-align format.ts), applied at any depth because the url key differs per payload:
|
|
85
|
+
*
|
|
86
|
+
* - a claimed or unsourced identity is dropped and replaced by `source_unverified: true`, with the model's claim
|
|
87
|
+
* as `claimed_source` when `decision_json` carries one (read here, before serializeMcpResult omits it);
|
|
88
|
+
* - any other align:// value (seeded demo and starter content, override asks) is dropped, nothing added: it is
|
|
89
|
+
* not a claim, only not a place. The decision's `decision_url` is the link that opens.
|
|
90
|
+
*
|
|
91
|
+
* Only plain objects and arrays are walked, so a Date or a class instance reaches JSON.stringify unchanged.
|
|
92
|
+
*/
|
|
93
|
+
export declare function withoutUnopenableSources<T>(value: T): T;
|
|
70
94
|
//# sourceMappingURL=decision-links.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-links.d.ts","sourceRoot":"","sources":["../../src/lib/decision-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAkBH,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIrF;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMpF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAGtD,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAI/E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"decision-links.d.ts","sourceRoot":"","sources":["../../src/lib/decision-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAkBH,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIrF;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMpF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAGtD,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAI/E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAK3F;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAK9G;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAiBvD"}
|
|
@@ -116,6 +116,74 @@ export function isSyntheticSource(sourceUrl) {
|
|
|
116
116
|
export function navigableSourceUrl(sourceUrl) {
|
|
117
117
|
if (typeof sourceUrl !== 'string' || sourceUrl.length === 0)
|
|
118
118
|
return undefined;
|
|
119
|
-
|
|
119
|
+
// startsWith, never includes: a real page may carry the text in its path. Every align:// value is an id Align
|
|
120
|
+
// minted or seeded (claimed, unsourced, demo, starter-template, starter-intent, override-ask), never a place.
|
|
121
|
+
return sourceUrl.startsWith('align://') ? undefined : sourceUrl;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The link to print under a decision: where it was decided when that is somewhere a person can go, else the link
|
|
125
|
+
* that opens it in Align (`decision_url`). Never an align:// value. A seeded decision has no source to open, so
|
|
126
|
+
* without the fallback `align check` printed nothing under it.
|
|
127
|
+
*/
|
|
128
|
+
export function decisionLinkFor(row) {
|
|
129
|
+
// Only an http(s) decision_url is a link: it is printed into a coding agent's context, and a stored verdict file is
|
|
130
|
+
// one other processes can write.
|
|
131
|
+
const decisionUrl = typeof row.decision_url === 'string' && /^https?:\/\//i.test(row.decision_url) ? row.decision_url : undefined;
|
|
132
|
+
return navigableSourceUrl(row.url) ?? decisionUrl;
|
|
133
|
+
}
|
|
134
|
+
const SOURCE_URL_KEYS = ['url', 'source_url'];
|
|
135
|
+
/** The model's unverified claim, out of a payload that carries `decision_json`. */
|
|
136
|
+
const MAX_CLAIM_CHARS = 200;
|
|
137
|
+
function claimedSourceOf(decisionJson) {
|
|
138
|
+
const claim = decisionJson?.claimed_source;
|
|
139
|
+
if (typeof claim !== 'string')
|
|
140
|
+
return undefined;
|
|
141
|
+
// The model's own text about where a decision came from, captured from a conversation, and now handed to an agent
|
|
142
|
+
// (decision_json used to be omitted whole): one line, control characters gone, short.
|
|
143
|
+
// eslint-disable-next-line no-control-regex
|
|
144
|
+
const oneLine = claim.replace(/[\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
145
|
+
return oneLine.length > 0 ? oneLine.slice(0, MAX_CLAIM_CHARS) : undefined;
|
|
146
|
+
}
|
|
147
|
+
function isPlainObject(v) {
|
|
148
|
+
if (v === null || typeof v !== 'object')
|
|
149
|
+
return false;
|
|
150
|
+
const proto = Object.getPrototypeOf(v);
|
|
151
|
+
return proto === Object.prototype || proto === null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The one guard between a tool result and the model that reads it. Every MCP result is the gateway's JSON,
|
|
155
|
+
* forwarded as it came, so an align:// value in `url` or `source_url` reached the agent and was printed as a link
|
|
156
|
+
* ("Decision link: align://demo/..."), and a claimed or unsourced identity was handed over as if it were the
|
|
157
|
+
* origin. This is the CLI's copy of the hosted connector's withSourceProvenance and withoutUnopenableUrl
|
|
158
|
+
* (align-stack connectors/mcp-align format.ts), applied at any depth because the url key differs per payload:
|
|
159
|
+
*
|
|
160
|
+
* - a claimed or unsourced identity is dropped and replaced by `source_unverified: true`, with the model's claim
|
|
161
|
+
* as `claimed_source` when `decision_json` carries one (read here, before serializeMcpResult omits it);
|
|
162
|
+
* - any other align:// value (seeded demo and starter content, override asks) is dropped, nothing added: it is
|
|
163
|
+
* not a claim, only not a place. The decision's `decision_url` is the link that opens.
|
|
164
|
+
*
|
|
165
|
+
* Only plain objects and arrays are walked, so a Date or a class instance reaches JSON.stringify unchanged.
|
|
166
|
+
*/
|
|
167
|
+
export function withoutUnopenableSources(value) {
|
|
168
|
+
if (Array.isArray(value))
|
|
169
|
+
return value.map((v) => withoutUnopenableSources(v));
|
|
170
|
+
if (!isPlainObject(value))
|
|
171
|
+
return value;
|
|
172
|
+
// fromEntries defines own properties: assigning out['__proto__'] would set the prototype and drop a key that
|
|
173
|
+
// JSON.parse legitimately produced.
|
|
174
|
+
const out = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, withoutUnopenableSources(v)]));
|
|
175
|
+
for (const key of SOURCE_URL_KEYS) {
|
|
176
|
+
const src = out[key];
|
|
177
|
+
if (typeof src !== 'string' || !src.startsWith('align://'))
|
|
178
|
+
continue;
|
|
179
|
+
delete out[key];
|
|
180
|
+
if (isSyntheticSource(src)) {
|
|
181
|
+
out['source_unverified'] = true;
|
|
182
|
+
const claim = claimedSourceOf(value['decision_json']);
|
|
183
|
+
if (claim)
|
|
184
|
+
out['claimed_source'] = claim;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
120
188
|
}
|
|
121
189
|
//# sourceMappingURL=decision-links.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-links.js","sourceRoot":"","sources":["../../src/lib/decision-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,QAAQ,GAAG,8EAA8E,CAAC;AAEhG,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,SAAoC;IAC/D,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAG,0EAA0E,CAAC;AAChG,MAAM,UAAU,GAAG,8DAA8D,CAAC;AAElF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,SAAoC;IAC9D,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1E,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,kBAAkB;IAClB,oBAAoB;CACrB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,SAAoC;IACpE,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChD,0EAA0E;IAC1E,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAoC;IACrE,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9E,OAAO,
|
|
1
|
+
{"version":3,"file":"decision-links.js","sourceRoot":"","sources":["../../src/lib/decision-links.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CoChangeRule } from '../co-change-rule.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a repo writes its working rules when it has no ADRs and no CLAUDE.md. Measured on
|
|
4
|
+
* recart/openapi: the rule "every notable API change adds an entry to docs/changelog.md in the
|
|
5
|
+
* same PR" lived in README.md, and the versioning policy in the changelog's own Versioning
|
|
6
|
+
* section, so `connect docs` imported nothing.
|
|
7
|
+
*
|
|
8
|
+
* Only rule-bearing SECTIONS are taken. A README is mostly install and usage steps, and a
|
|
9
|
+
* changelog is mostly release entries ("what changed in 1.2.0"), which are history and not
|
|
10
|
+
* decisions about how to work. Both are left out.
|
|
11
|
+
*/
|
|
12
|
+
export declare const README_FILES: string[];
|
|
13
|
+
export declare const CONTRIBUTING_FILES: string[];
|
|
14
|
+
export declare const CHANGELOG_FILES: string[];
|
|
15
|
+
export type DocKind = 'readme' | 'contributing' | 'changelog';
|
|
16
|
+
/** A changelog that uses `# 1.2.0` as a release heading puts the whole release inside the
|
|
17
|
+
* previous section's text: cut the body where the next top-level heading starts. */
|
|
18
|
+
export declare function sectionBody(kind: DocKind, body: string): string;
|
|
19
|
+
export declare function isRuleSection(kind: DocKind, heading: string, body: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The structured form of "add an entry to <changelog> in the same PR", when the text says
|
|
22
|
+
* both halves. `require_paths` is the changelog the text names. `when_paths` is a directory
|
|
23
|
+
* named in backticks in the SAME sentence as the "same PR" clause, else - when the rule is
|
|
24
|
+
* about "the API" - the repo's top-level openapi/api/swagger directory that actually holds a
|
|
25
|
+
* spec file. When neither is clear there is NO rule: an empty or guessed trigger would demand
|
|
26
|
+
* the file on PRs the text never mentioned. The prose decision is still imported.
|
|
27
|
+
*/
|
|
28
|
+
export declare function inferCoChangeRule(body: string, repoRoot: string): Promise<CoChangeRule | undefined>;
|
|
29
|
+
//# sourceMappingURL=docs-conventions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-conventions.d.ts","sourceRoot":"","sources":["../../../src/lib/fetchers/docs-conventions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;;;;;;GASG;AAEH,eAAO,MAAM,YAAY,UAAgB,CAAC;AAC1C,eAAO,MAAM,kBAAkB,UAAyE,CAAC;AACzG,eAAO,MAAM,eAAe,UAA6E,CAAC;AAkB1G,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,cAAc,GAAG,WAAW,CAAC;AAE9D;qFACqF;AACrF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASnF;AA0BD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAezG"}
|