@deftai/directive-core 0.89.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/doctor/constants.d.ts +1 -1
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/flags.js +21 -1
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +22 -0
- package/dist/doctor/openclaw-skills.d.ts +143 -0
- package/dist/doctor/openclaw-skills.js +706 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- package/dist/hooks/dispatcher.js +20 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/hygiene.js +3 -0
- package/dist/init-deposit/init-deposit.d.ts +5 -0
- package/dist/init-deposit/init-deposit.js +37 -2
- package/dist/intake/github-auth-modes.d.ts +1 -1
- package/dist/intake/github-auth-modes.js +25 -3
- package/dist/intake/issue-ingest-cli.js +13 -0
- package/dist/intake/reconcile-issues-cli.js +13 -0
- package/dist/lifecycle/events.js +2 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/stats.d.ts +53 -0
- package/dist/lifecycle/stats.js +286 -0
- package/dist/policy/plan-extensions.d.ts +10 -0
- package/dist/policy/plan-extensions.js +16 -0
- package/dist/pr-closing-keywords/detect.d.ts +13 -0
- package/dist/pr-closing-keywords/detect.js +73 -8
- package/dist/pr-closing-keywords/index.d.ts +2 -2
- package/dist/pr-closing-keywords/index.js +1 -1
- package/dist/pr-closing-keywords/main.js +119 -90
- package/dist/pr-closing-keywords/types.d.ts +10 -0
- package/dist/release/spawn.js +13 -0
- package/dist/release-e2e/git-ops.d.ts +7 -0
- package/dist/release-e2e/git-ops.js +35 -2
- package/dist/render/constants.d.ts +16 -0
- package/dist/render/constants.js +16 -1
- package/dist/render/export-spec.d.ts +7 -1
- package/dist/render/export-spec.js +51 -5
- package/dist/render/index.d.ts +3 -2
- package/dist/render/index.js +2 -2
- package/dist/render/roadmap-render.js +164 -60
- package/dist/render/scope-outlook.d.ts +5 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.d.ts +28 -2
- package/dist/render/spec-render.js +111 -14
- package/dist/render/text-utils.d.ts +6 -0
- package/dist/render/text-utils.js +23 -0
- package/dist/scm/binary.d.ts +3 -0
- package/dist/scm/binary.js +10 -2
- package/dist/scm/call.d.ts +5 -0
- package/dist/scm/call.js +5 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +5 -0
- package/dist/scm/main.js +27 -0
- package/dist/scm/readiness-cli.d.ts +24 -0
- package/dist/scm/readiness-cli.js +85 -0
- package/dist/scm/readiness.d.ts +104 -0
- package/dist/scm/readiness.js +421 -0
- package/dist/scope/batch-promote.d.ts +26 -0
- package/dist/scope/batch-promote.js +167 -0
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.d.ts +2 -0
- package/dist/scope/main.js +58 -7
- package/dist/scope/wip-cap-check.d.ts +6 -0
- package/dist/scope/wip-cap-check.js +14 -2
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/process-cost-constants.d.ts +9 -0
- package/dist/session/process-cost-constants.js +11 -0
- package/dist/session/process-cost.d.ts +53 -0
- package/dist/session/process-cost.js +82 -0
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +12 -1
- package/dist/session/session-ready.d.ts +67 -0
- package/dist/session/session-ready.js +264 -0
- package/dist/session/session-start.d.ts +63 -1
- package/dist/session/session-start.js +432 -24
- package/dist/session/verify-session-ritual.d.ts +8 -0
- package/dist/session/verify-session-ritual.js +95 -35
- package/dist/tool-events/classify.d.ts +20 -0
- package/dist/tool-events/classify.js +634 -0
- package/dist/tool-events/index.d.ts +10 -0
- package/dist/tool-events/index.js +10 -0
- package/dist/tool-events/summarize.d.ts +34 -0
- package/dist/tool-events/summarize.js +93 -0
- package/dist/tool-events/types.d.ts +52 -0
- package/dist/tool-events/types.js +13 -0
- package/dist/triage/welcome/index.d.ts +1 -1
- package/dist/triage/welcome/index.js +1 -1
- package/dist/triage/welcome/onboard.js +7 -1
- package/dist/triage/welcome/prior-state.d.ts +7 -0
- package/dist/triage/welcome/prior-state.js +16 -2
- package/dist/triage/welcome/writers.d.ts +10 -0
- package/dist/triage/welcome/writers.js +72 -2
- package/package.json +7 -3
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local xBRIEF lifecycle folder stats for weekly process rollups (#2995).
|
|
3
|
+
*
|
|
4
|
+
* Filesystem-only inventory of `xbrief/{proposed,pending,active,completed,cancelled}/`.
|
|
5
|
+
* No network calls.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
|
|
10
|
+
import { parseDurationMs } from "../triage/scope/duration.js";
|
|
11
|
+
/** Lifecycle folders scanned for stats. */
|
|
12
|
+
export const STATS_LIFECYCLE_FOLDERS = [
|
|
13
|
+
"proposed",
|
|
14
|
+
"pending",
|
|
15
|
+
"active",
|
|
16
|
+
"completed",
|
|
17
|
+
"cancelled",
|
|
18
|
+
];
|
|
19
|
+
/** Count semantics for WWYSYDH / process forms (stable strings for --json). */
|
|
20
|
+
export const LIFECYCLE_STATS_SEMANTICS = {
|
|
21
|
+
promoted: "xbriefs currently in pending/ whose event time falls inside the --since window " +
|
|
22
|
+
"(promoted and still pending; later-activated work is not double-counted here).",
|
|
23
|
+
activated: "xbriefs currently in active/ whose event time falls inside the --since window " +
|
|
24
|
+
"(activated and still active).",
|
|
25
|
+
completed: "xbriefs currently in completed/ with plan.status completed (or unset, treated as completed) " +
|
|
26
|
+
"whose event time falls inside the --since window.",
|
|
27
|
+
cancelled_or_failed: "xbriefs currently in cancelled/, or in completed/ with plan.status failed, " +
|
|
28
|
+
"whose event time falls inside the --since window.",
|
|
29
|
+
still_active: "snapshot count of all xbriefs currently in active/ (not filtered by --since).",
|
|
30
|
+
event_time: "event time = most recent of plan.metadata.completedAt, plan.updated, and xBRIEFInfo/vBRIEFInfo.updated; " +
|
|
31
|
+
"else file mtime. Window is [as_of - since, as_of] inclusive of as_of.",
|
|
32
|
+
note: "Counts are current-folder membership, not full transition history. A brief that was " +
|
|
33
|
+
"promoted then activated in the same week appears under activated (and still_active), not promoted.",
|
|
34
|
+
};
|
|
35
|
+
function parseIso(value) {
|
|
36
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const text = value.trim().endsWith("Z") ? `${value.trim().slice(0, -1)}+00:00` : value.trim();
|
|
40
|
+
const parsed = new Date(text);
|
|
41
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
function readJsonObject(path) {
|
|
47
|
+
try {
|
|
48
|
+
const raw = readFileSync(path, "utf8");
|
|
49
|
+
const data = JSON.parse(raw);
|
|
50
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function planOf(data) {
|
|
60
|
+
const plan = data.plan;
|
|
61
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return plan;
|
|
65
|
+
}
|
|
66
|
+
function infoUpdated(data) {
|
|
67
|
+
for (const key of ["xBRIEFInfo", "vBRIEFInfo"]) {
|
|
68
|
+
const info = data[key];
|
|
69
|
+
if (typeof info === "object" && info !== null && !Array.isArray(info)) {
|
|
70
|
+
const stamp = parseIso(info.updated);
|
|
71
|
+
if (stamp !== null) {
|
|
72
|
+
return stamp;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function completedAt(plan) {
|
|
79
|
+
const metadata = plan.metadata;
|
|
80
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return parseIso(metadata.completedAt);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Event time for window membership: most recent of completedAt / plan.updated /
|
|
87
|
+
* info.updated, else mtime. Preferring max (not completedAt-first) so a later
|
|
88
|
+
* cancel/restore transition is not stuck on a stale completion stamp.
|
|
89
|
+
*/
|
|
90
|
+
function eventTimeFor(data, plan, path) {
|
|
91
|
+
const candidates = [];
|
|
92
|
+
const completed = completedAt(plan);
|
|
93
|
+
if (completed !== null) {
|
|
94
|
+
candidates.push(completed);
|
|
95
|
+
}
|
|
96
|
+
const planUpdated = parseIso(plan.updated);
|
|
97
|
+
if (planUpdated !== null) {
|
|
98
|
+
candidates.push(planUpdated);
|
|
99
|
+
}
|
|
100
|
+
const info = infoUpdated(data);
|
|
101
|
+
if (info !== null) {
|
|
102
|
+
candidates.push(info);
|
|
103
|
+
}
|
|
104
|
+
if (candidates.length > 0) {
|
|
105
|
+
return new Date(Math.max(...candidates.map((d) => d.getTime())));
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
return new Date(statSync(path).mtimeMs);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Stats root: xbrief/ if present, else legacy vbrief/ (read-only inventory), else canonical xbrief path. */
|
|
115
|
+
function resolveStatsLifecycleRoot(projectRoot) {
|
|
116
|
+
const migrated = join(projectRoot, MIGRATED_ARTIFACT_DIR);
|
|
117
|
+
if (existsSync(migrated)) {
|
|
118
|
+
try {
|
|
119
|
+
if (statSync(migrated).isDirectory()) {
|
|
120
|
+
return migrated;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// fall through
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const legacy = join(projectRoot, LEGACY_ARTIFACT_DIR);
|
|
128
|
+
if (existsSync(legacy)) {
|
|
129
|
+
try {
|
|
130
|
+
if (statSync(legacy).isDirectory()) {
|
|
131
|
+
return legacy;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// fall through
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return migrated;
|
|
139
|
+
}
|
|
140
|
+
function statusOf(plan, folder) {
|
|
141
|
+
const raw = plan.status;
|
|
142
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
143
|
+
return raw.trim();
|
|
144
|
+
}
|
|
145
|
+
if (folder === "completed") {
|
|
146
|
+
return "completed";
|
|
147
|
+
}
|
|
148
|
+
if (folder === "cancelled") {
|
|
149
|
+
return "cancelled";
|
|
150
|
+
}
|
|
151
|
+
if (folder === "pending") {
|
|
152
|
+
return "pending";
|
|
153
|
+
}
|
|
154
|
+
if (folder === "active") {
|
|
155
|
+
return "running";
|
|
156
|
+
}
|
|
157
|
+
return "proposed";
|
|
158
|
+
}
|
|
159
|
+
function inWindow(eventTime, windowStart, asOf) {
|
|
160
|
+
if (eventTime === null) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const t = eventTime.getTime();
|
|
164
|
+
return t >= windowStart.getTime() && t <= asOf.getTime();
|
|
165
|
+
}
|
|
166
|
+
function emptyFolderTotals() {
|
|
167
|
+
return { proposed: 0, pending: 0, active: 0, completed: 0, cancelled: 0 };
|
|
168
|
+
}
|
|
169
|
+
function scanArtifacts(lifecycleRoot) {
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const folder of STATS_LIFECYCLE_FOLDERS) {
|
|
172
|
+
const dir = join(lifecycleRoot, folder);
|
|
173
|
+
if (!existsSync(dir)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
let names;
|
|
177
|
+
try {
|
|
178
|
+
names = readdirSync(dir)
|
|
179
|
+
.filter((name) => hasArtifactSuffix(name))
|
|
180
|
+
.sort();
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
for (const name of names) {
|
|
186
|
+
const path = join(dir, name);
|
|
187
|
+
const data = readJsonObject(path);
|
|
188
|
+
if (data === null) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const plan = planOf(data);
|
|
192
|
+
if (plan === null) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
out.push({
|
|
196
|
+
folder,
|
|
197
|
+
status: statusOf(plan, folder),
|
|
198
|
+
eventTime: eventTimeFor(data, plan, path),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
function utcIso(dt) {
|
|
205
|
+
return `${dt.toISOString().slice(0, 19)}Z`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Collect lifecycle folder stats for a project root.
|
|
209
|
+
* Offline / filesystem only.
|
|
210
|
+
*/
|
|
211
|
+
export function collectLifecycleStats(options) {
|
|
212
|
+
const sinceRaw = (options.since ?? "7d").trim() || "7d";
|
|
213
|
+
const sinceMs = parseDurationMs(sinceRaw);
|
|
214
|
+
const asOf = options.now ?? new Date();
|
|
215
|
+
const windowStart = new Date(asOf.getTime() - sinceMs);
|
|
216
|
+
const projectRoot = resolve(options.projectRoot);
|
|
217
|
+
const lifecycleRoot = resolveStatsLifecycleRoot(projectRoot);
|
|
218
|
+
const folder_totals = emptyFolderTotals();
|
|
219
|
+
let promoted = 0;
|
|
220
|
+
let activated = 0;
|
|
221
|
+
let completed = 0;
|
|
222
|
+
let cancelled_or_failed = 0;
|
|
223
|
+
let still_active = 0;
|
|
224
|
+
const records = existsSync(lifecycleRoot) ? scanArtifacts(lifecycleRoot) : [];
|
|
225
|
+
for (const rec of records) {
|
|
226
|
+
folder_totals[rec.folder] += 1;
|
|
227
|
+
if (rec.folder === "active") {
|
|
228
|
+
still_active += 1;
|
|
229
|
+
}
|
|
230
|
+
const win = inWindow(rec.eventTime, windowStart, asOf);
|
|
231
|
+
if (!win) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (rec.folder === "pending") {
|
|
235
|
+
promoted += 1;
|
|
236
|
+
}
|
|
237
|
+
else if (rec.folder === "active") {
|
|
238
|
+
activated += 1;
|
|
239
|
+
}
|
|
240
|
+
else if (rec.folder === "cancelled") {
|
|
241
|
+
cancelled_or_failed += 1;
|
|
242
|
+
}
|
|
243
|
+
else if (rec.folder === "completed") {
|
|
244
|
+
if (rec.status === "failed") {
|
|
245
|
+
cancelled_or_failed += 1;
|
|
246
|
+
}
|
|
247
|
+
else if (rec.status === "completed") {
|
|
248
|
+
completed += 1;
|
|
249
|
+
}
|
|
250
|
+
// Inconsistent statuses under completed/ (e.g. stale "running") are
|
|
251
|
+
// counted in folder_totals only — not in window metrics.
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
since: sinceRaw,
|
|
256
|
+
since_ms: sinceMs,
|
|
257
|
+
as_of: utcIso(asOf),
|
|
258
|
+
window_start: utcIso(windowStart),
|
|
259
|
+
project_root: projectRoot,
|
|
260
|
+
lifecycle_root: lifecycleRoot,
|
|
261
|
+
promoted,
|
|
262
|
+
activated,
|
|
263
|
+
completed,
|
|
264
|
+
cancelled_or_failed,
|
|
265
|
+
still_active,
|
|
266
|
+
folder_totals: { ...folder_totals },
|
|
267
|
+
semantics: LIFECYCLE_STATS_SEMANTICS,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Format human-readable lifecycle stats text. */
|
|
271
|
+
export function formatLifecycleStatsText(stats) {
|
|
272
|
+
const lines = [
|
|
273
|
+
`lifecycle:stats (since ${stats.since}, as of ${stats.as_of})`,
|
|
274
|
+
` window: ${stats.window_start} → ${stats.as_of}`,
|
|
275
|
+
` promoted: ${stats.promoted}`,
|
|
276
|
+
` activated: ${stats.activated}`,
|
|
277
|
+
` completed: ${stats.completed}`,
|
|
278
|
+
` cancelled_or_failed: ${stats.cancelled_or_failed}`,
|
|
279
|
+
` still_active: ${stats.still_active}`,
|
|
280
|
+
` folder_totals: proposed=${stats.folder_totals.proposed} pending=${stats.folder_totals.pending} active=${stats.folder_totals.active} completed=${stats.folder_totals.completed} cancelled=${stats.folder_totals.cancelled}`,
|
|
281
|
+
];
|
|
282
|
+
return `${lines.join("\n")}\n`;
|
|
283
|
+
}
|
|
284
|
+
/** Re-export duration parse for CLI error messaging. */
|
|
285
|
+
export { parseDurationMs };
|
|
286
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
export declare const PLAN_POLICY_KEY = "x-directive/policy";
|
|
18
18
|
/** Namespaced directive scope-completion note key. */
|
|
19
19
|
export declare const PLAN_COMPLETED_NOTE_KEY = "x-directive/completedNote";
|
|
20
|
+
/**
|
|
21
|
+
* Namespaced onboarding decision-provenance block (#1694 / #1695).
|
|
22
|
+
*
|
|
23
|
+
* Holds facts that must NOT be smuggled through policy value fields (e.g.
|
|
24
|
+
* `plan.policy.wipCap` presence must not mean "operator decided a WIP cap").
|
|
25
|
+
* Out-of-band from `x-directive/policy` so value and decision stay orthogonal.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PLAN_ONBOARDING_KEY = "x-directive/onboarding";
|
|
20
28
|
/** Legacy bare policy key (pre-#1650); read-side fallback / write-side migration source. */
|
|
21
29
|
export declare const LEGACY_PLAN_POLICY_KEY = "policy";
|
|
22
30
|
/** Legacy bare completedNote key (pre-#1650); read-side fallback / write-side migration source. */
|
|
@@ -25,6 +33,8 @@ export declare const LEGACY_PLAN_COMPLETED_NOTE_KEY = "completedNote";
|
|
|
25
33
|
export declare function readPlanPolicy(plan: unknown): unknown;
|
|
26
34
|
/** Read the directive completedNote from a plan (namespaced first, bare fallback). */
|
|
27
35
|
export declare function readPlanCompletedNote(plan: unknown): unknown;
|
|
36
|
+
/** Read the namespaced onboarding decision-provenance block (#1694). */
|
|
37
|
+
export declare function readPlanOnboarding(plan: unknown): unknown;
|
|
28
38
|
/**
|
|
29
39
|
* Rename a legacy bare `policy` key to the namespaced key in place when only
|
|
30
40
|
* the bare key is present. No-op when the namespaced key already exists or no
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
export const PLAN_POLICY_KEY = "x-directive/policy";
|
|
18
18
|
/** Namespaced directive scope-completion note key. */
|
|
19
19
|
export const PLAN_COMPLETED_NOTE_KEY = "x-directive/completedNote";
|
|
20
|
+
/**
|
|
21
|
+
* Namespaced onboarding decision-provenance block (#1694 / #1695).
|
|
22
|
+
*
|
|
23
|
+
* Holds facts that must NOT be smuggled through policy value fields (e.g.
|
|
24
|
+
* `plan.policy.wipCap` presence must not mean "operator decided a WIP cap").
|
|
25
|
+
* Out-of-band from `x-directive/policy` so value and decision stay orthogonal.
|
|
26
|
+
*/
|
|
27
|
+
export const PLAN_ONBOARDING_KEY = "x-directive/onboarding";
|
|
20
28
|
/** Legacy bare policy key (pre-#1650); read-side fallback / write-side migration source. */
|
|
21
29
|
export const LEGACY_PLAN_POLICY_KEY = "policy";
|
|
22
30
|
/** Legacy bare completedNote key (pre-#1650); read-side fallback / write-side migration source. */
|
|
@@ -42,6 +50,14 @@ export function readPlanPolicy(plan) {
|
|
|
42
50
|
export function readPlanCompletedNote(plan) {
|
|
43
51
|
return readPlanExtension(plan, PLAN_COMPLETED_NOTE_KEY, LEGACY_PLAN_COMPLETED_NOTE_KEY);
|
|
44
52
|
}
|
|
53
|
+
/** Read the namespaced onboarding decision-provenance block (#1694). */
|
|
54
|
+
export function readPlanOnboarding(plan) {
|
|
55
|
+
const planObj = asPlanObject(plan);
|
|
56
|
+
if (planObj === null) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return planObj[PLAN_ONBOARDING_KEY];
|
|
60
|
+
}
|
|
45
61
|
/**
|
|
46
62
|
* Rename a legacy bare `policy` key to the namespaced key in place when only
|
|
47
63
|
* the bare key is present. No-op when the namespaced key already exists or no
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { Hit } from "./types.js";
|
|
2
2
|
export declare const CLOSING_KEYWORD_RE: RegExp;
|
|
3
3
|
export declare function renderHit(hit: Hit): string;
|
|
4
|
+
/** Layer 0 FP hits only (#737): keyword in negation / quotation / example / code-block / blockquote. */
|
|
4
5
|
export declare function findHits(text: string, source: string): Hit[];
|
|
6
|
+
/**
|
|
7
|
+
* Intent-mode hits (#3015 class D): every closing-keyword + `#N` match, regardless of
|
|
8
|
+
* surrounding prose. GitHub closes on token presence; conditional English is ignored.
|
|
9
|
+
* reason is the FP category when present, otherwise `intent`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findAllClosingKeywordHits(text: string, source: string): Hit[];
|
|
12
|
+
/**
|
|
13
|
+
* Body trailer `deft-close-intent: full` is intentionally NOT an authorization path.
|
|
14
|
+
* Intent-mode allowlist is CLI `--allow-close N,M` only (#3015 / Greptile class-D gate).
|
|
15
|
+
* Markdown examples must never suppress real closing-keyword findings.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasFullCloseIntent(_text: string): boolean;
|
|
5
18
|
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -21,7 +21,8 @@ const EXAMPLE_MARKERS = [
|
|
|
21
21
|
/\blike\b/i,
|
|
22
22
|
];
|
|
23
23
|
const BLOCKQUOTE_RE = /^\s*>\s/m;
|
|
24
|
-
|
|
24
|
+
/** CommonMark fenced code opener/closer at line start: 3+ backticks or 3+ tildes. */
|
|
25
|
+
const CODE_FENCE_LINE_RE = /^(?:`{3,}|~{3,})/;
|
|
25
26
|
function findAllMatches(text, re) {
|
|
26
27
|
const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
|
|
27
28
|
const pattern = new RegExp(re.source, flags);
|
|
@@ -41,10 +42,41 @@ function lineStartingAt(text, offset) {
|
|
|
41
42
|
}
|
|
42
43
|
return text.slice(lineStart, lineEnd);
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* CommonMark fence stack: open and close must use the same character, and the
|
|
47
|
+
* closer length must be >= opener length. Mixed ``` / ~~~ or shorter closers
|
|
48
|
+
* do not pop the open fence.
|
|
49
|
+
*/
|
|
44
50
|
function isInsideCodeFence(text, offset) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
let openChar = null;
|
|
52
|
+
let openLen = 0;
|
|
53
|
+
let lineStart = 0;
|
|
54
|
+
while (lineStart < offset) {
|
|
55
|
+
let lineEnd = text.indexOf("\n", lineStart);
|
|
56
|
+
if (lineEnd === -1 || lineEnd > offset) {
|
|
57
|
+
lineEnd = Math.min(text.length, offset);
|
|
58
|
+
}
|
|
59
|
+
const line = text.slice(lineStart, lineEnd);
|
|
60
|
+
const m = CODE_FENCE_LINE_RE.exec(line);
|
|
61
|
+
if (m !== null) {
|
|
62
|
+
const fence = m[0] ?? "";
|
|
63
|
+
const ch = fence[0];
|
|
64
|
+
const len = fence.length;
|
|
65
|
+
if (openChar === null) {
|
|
66
|
+
openChar = ch;
|
|
67
|
+
openLen = len;
|
|
68
|
+
}
|
|
69
|
+
else if (ch === openChar && len >= openLen) {
|
|
70
|
+
openChar = null;
|
|
71
|
+
openLen = 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (lineEnd >= offset) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
lineStart = lineEnd + 1;
|
|
78
|
+
}
|
|
79
|
+
return openChar !== null;
|
|
48
80
|
}
|
|
49
81
|
function classifyHit(text, match) {
|
|
50
82
|
const start = match.index ?? 0;
|
|
@@ -88,6 +120,12 @@ export function renderHit(hit) {
|
|
|
88
120
|
return (` [${hit.source}] ${hit.reason}: ` +
|
|
89
121
|
`"...${hit.context}..." -> ${hit.keyword} #${hit.issueNumber}`);
|
|
90
122
|
}
|
|
123
|
+
function snippetAround(text, match) {
|
|
124
|
+
const snippetStart = Math.max(0, (match.index ?? 0) - 30);
|
|
125
|
+
const snippetEnd = Math.min(text.length, (match.index ?? 0) + match[0].length + 30);
|
|
126
|
+
return text.slice(snippetStart, snippetEnd).replace(/\n/g, " ");
|
|
127
|
+
}
|
|
128
|
+
/** Layer 0 FP hits only (#737): keyword in negation / quotation / example / code-block / blockquote. */
|
|
91
129
|
export function findHits(text, source) {
|
|
92
130
|
const hits = [];
|
|
93
131
|
const re = new RegExp(CLOSING_KEYWORD_RE.source, CLOSING_KEYWORD_RE.flags);
|
|
@@ -95,14 +133,11 @@ export function findHits(text, source) {
|
|
|
95
133
|
while (match !== null) {
|
|
96
134
|
const category = classifyHit(text, match);
|
|
97
135
|
if (category !== null) {
|
|
98
|
-
const snippetStart = Math.max(0, (match.index ?? 0) - 30);
|
|
99
|
-
const snippetEnd = Math.min(text.length, (match.index ?? 0) + match[0].length + 30);
|
|
100
|
-
const context = text.slice(snippetStart, snippetEnd).replace(/\n/g, " ");
|
|
101
136
|
hits.push({
|
|
102
137
|
source,
|
|
103
138
|
keyword: match[1] ?? "",
|
|
104
139
|
issueNumber: Number(match[2]),
|
|
105
|
-
context,
|
|
140
|
+
context: snippetAround(text, match),
|
|
106
141
|
reason: category,
|
|
107
142
|
});
|
|
108
143
|
}
|
|
@@ -110,4 +145,34 @@ export function findHits(text, source) {
|
|
|
110
145
|
}
|
|
111
146
|
return hits;
|
|
112
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Intent-mode hits (#3015 class D): every closing-keyword + `#N` match, regardless of
|
|
150
|
+
* surrounding prose. GitHub closes on token presence; conditional English is ignored.
|
|
151
|
+
* reason is the FP category when present, otherwise `intent`.
|
|
152
|
+
*/
|
|
153
|
+
export function findAllClosingKeywordHits(text, source) {
|
|
154
|
+
const hits = [];
|
|
155
|
+
const re = new RegExp(CLOSING_KEYWORD_RE.source, CLOSING_KEYWORD_RE.flags);
|
|
156
|
+
let match = re.exec(text);
|
|
157
|
+
while (match !== null) {
|
|
158
|
+
const category = classifyHit(text, match);
|
|
159
|
+
hits.push({
|
|
160
|
+
source,
|
|
161
|
+
keyword: match[1] ?? "",
|
|
162
|
+
issueNumber: Number(match[2]),
|
|
163
|
+
context: snippetAround(text, match),
|
|
164
|
+
reason: category ?? "intent",
|
|
165
|
+
});
|
|
166
|
+
match = re.exec(text);
|
|
167
|
+
}
|
|
168
|
+
return hits;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Body trailer `deft-close-intent: full` is intentionally NOT an authorization path.
|
|
172
|
+
* Intent-mode allowlist is CLI `--allow-close N,M` only (#3015 / Greptile class-D gate).
|
|
173
|
+
* Markdown examples must never suppress real closing-keyword findings.
|
|
174
|
+
*/
|
|
175
|
+
export function hasFullCloseIntent(_text) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
113
178
|
//# sourceMappingURL=detect.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { EXIT_CONFIG_ERROR, EXIT_HITS_FOUND, EXIT_OK, GH_TIMEOUT_S, WINDOW_RADIUS, } from "./constants.js";
|
|
2
|
-
export { findHits, renderHit } from "./detect.js";
|
|
2
|
+
export { findAllClosingKeywordHits, findHits, hasFullCloseIntent, renderHit, } from "./detect.js";
|
|
3
3
|
export { defaultRunGh, fetchPrBody, fetchPrCommitMessages } from "./gh.js";
|
|
4
4
|
export { readCommitsFile, readTextFile } from "./io.js";
|
|
5
5
|
export { cmdPrCheckClosingKeywords, parseAllowList, parseArgs, run } from "./main.js";
|
|
6
|
-
export type { Hit, ParsedArgs, RunGhFn, RunGhResult } from "./types.js";
|
|
6
|
+
export type { ClosingKeywordMode, Hit, ParsedArgs, RunGhFn, RunGhResult } from "./types.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { EXIT_CONFIG_ERROR, EXIT_HITS_FOUND, EXIT_OK, GH_TIMEOUT_S, WINDOW_RADIUS, } from "./constants.js";
|
|
2
|
-
export { findHits, renderHit } from "./detect.js";
|
|
2
|
+
export { findAllClosingKeywordHits, findHits, hasFullCloseIntent, renderHit, } from "./detect.js";
|
|
3
3
|
export { defaultRunGh, fetchPrBody, fetchPrCommitMessages } from "./gh.js";
|
|
4
4
|
export { readCommitsFile, readTextFile } from "./io.js";
|
|
5
5
|
export { cmdPrCheckClosingKeywords, parseAllowList, parseArgs, run } from "./main.js";
|