@deftai/directive-core 0.90.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/openclaw-skills.d.ts +42 -8
- package/dist/doctor/openclaw-skills.js +281 -38
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- 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/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/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/session-start.d.ts +7 -0
- package/dist/session/session-start.js +54 -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 +3 -3
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
9
|
import { homedir, platform } from "node:os";
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
10
|
+
import { basename, join, resolve } from "node:path";
|
|
11
11
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
12
12
|
import { copyTree } from "../deposit/copy-tree.js";
|
|
13
13
|
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
14
14
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
15
15
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
16
|
+
import { renderProjectDefinition } from "../render/project-render.js";
|
|
17
|
+
import { removeStaleMigratedFrameworkNarrative } from "../xbrief-migrate/migrate-project.js";
|
|
16
18
|
import { writeAgentHookDeposit } from "./agent-hooks.js";
|
|
17
19
|
import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./gitignore.js";
|
|
18
20
|
import { depositStagePaths, printCommitGuidance } from "./hygiene.js";
|
|
@@ -20,6 +22,7 @@ import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout,
|
|
|
20
22
|
import { printMigrateNudgeIfNeeded } from "./migrate.js";
|
|
21
23
|
import { ensurePrettierIgnoreLines } from "./prettierignore.js";
|
|
22
24
|
import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
|
|
25
|
+
import { syncBareVersionMarker } from "./xbrief-projections.js";
|
|
23
26
|
export function parseInitArgv(canonicalArgv, userArgv = []) {
|
|
24
27
|
const args = [...canonicalArgv, ...userArgv];
|
|
25
28
|
let projectDir = process.cwd();
|
|
@@ -113,10 +116,36 @@ export function printNextSteps(result, io) {
|
|
|
113
116
|
io.printf(` 1. Open your AI coding assistant in ${result.projectDir}\n`);
|
|
114
117
|
io.printf(" 2. Deft skill auto-discovery is partially implemented — if your agent doesn't\n");
|
|
115
118
|
io.printf(' start setup automatically, tell it: "Use AGENTS.md"\n');
|
|
116
|
-
io.printf(" 3. On first session, the agent will guide you through
|
|
119
|
+
io.printf(" 3. On first session, the agent will guide you through USER.md (if missing).\n");
|
|
120
|
+
io.printf(" 4. PROJECT-DEFINITION is seeded at init (#3013) — run `task project:render` once to refresh items from lifecycle folders; do not multi-turn invent project identity.\n");
|
|
117
121
|
printMigrateNudgeIfNeeded(result.projectDir, io);
|
|
118
122
|
io.printf("\n");
|
|
119
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Ensure a minimal render-ready PROJECT-DEFINITION exists after lifecycle dirs
|
|
126
|
+
* are deposited (#3013 / epic #3009). Idempotent: never overwrites existing identity.
|
|
127
|
+
*/
|
|
128
|
+
export function seedMinimalProjectDefinition(projectDir, io) {
|
|
129
|
+
const xbriefDir = join(projectDir, "xbrief");
|
|
130
|
+
const legacyDir = join(projectDir, "vbrief");
|
|
131
|
+
const root = existsSync(xbriefDir) ? xbriefDir : existsSync(legacyDir) ? legacyDir : null;
|
|
132
|
+
if (root === null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const isXbrief = basename(root) === "xbrief";
|
|
136
|
+
const projectDefPath = join(root, isXbrief ? "PROJECT-DEFINITION.xbrief.json" : "PROJECT-DEFINITION.vbrief.json");
|
|
137
|
+
if (existsSync(projectDefPath)) {
|
|
138
|
+
io.printf("PROJECT-DEFINITION already present — leaving identity intact (#3013).\n");
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const [ok, message] = renderProjectDefinition(root);
|
|
142
|
+
if (ok) {
|
|
143
|
+
io.printf(`${message} (minimal seed for one-shot project:render; #3013)\n`);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
io.printf(`PROJECT-DEFINITION seed skipped: ${message}\n`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
120
149
|
export async function runInitDeposit(args, io, seams = {}) {
|
|
121
150
|
const projectDir = args.projectDir;
|
|
122
151
|
const deftDir = join(projectDir, CANONICAL_INSTALL_ROOT);
|
|
@@ -154,6 +183,12 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
154
183
|
const skillsCreated = writeAgentsSkills(projectDir, io);
|
|
155
184
|
await depositNeutralization(projectDir, io);
|
|
156
185
|
await writeConsumerVbrief(projectDir, deftDir, io);
|
|
186
|
+
seedMinimalProjectDefinition(projectDir, io);
|
|
187
|
+
// Seeding PROJECT-DEFINITION.xbrief.json makes resolveLifecycleRoot succeed.
|
|
188
|
+
// Align the same consumer derivatives update repairs (#2595 / #2806) so
|
|
189
|
+
// init + git add -A is already clean under core.autocrlf=true (#2118 / #3013).
|
|
190
|
+
syncBareVersionMarker(projectDir, version);
|
|
191
|
+
removeStaleMigratedFrameworkNarrative(projectDir);
|
|
157
192
|
writeConsumerGitHooks(projectDir, deftDir, io, seams.gitHooks);
|
|
158
193
|
writeAgentHookDeposit(projectDir, io);
|
|
159
194
|
let taskfileWired = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CompletedProcess } from "../scm/call.js";
|
|
2
2
|
import { probeRuntimeCapabilities, type RuntimeCapabilityReport } from "./platform-capabilities.js";
|
|
3
3
|
export declare const GITHUB_AUTH_MODE_INJECTED_TOKEN = "injected-token";
|
|
4
4
|
export declare const GITHUB_AUTH_MODE_HOST_GH = "host-gh";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { pyRepr } from "../scm/py-format.js";
|
|
3
3
|
import { getPlatformCapabilities, probeRuntimeCapabilities, RUNTIME_MODE_CLOUD_HEADLESS, RUNTIME_MODE_CURSOR_NATIVE_SANDBOX, } from "./platform-capabilities.js";
|
|
4
4
|
export const GITHUB_AUTH_MODE_INJECTED_TOKEN = "injected-token";
|
|
@@ -37,9 +37,31 @@ export function inferGithubAuthMode(runtimeReport) {
|
|
|
37
37
|
}
|
|
38
38
|
return GITHUB_AUTH_MODE_HOST_GH;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Prefer live `gh` for auth/API validation. Do not route through scm.call /
|
|
42
|
+
* BINARY_PREFERENCE (ghx-first): ghx is a cached GET proxy and rejects multi-arg
|
|
43
|
+
* `api user --jq .login` forms used here (#2275 Greptile P1 / #954).
|
|
44
|
+
*/
|
|
40
45
|
function defaultRunGh(args, environ) {
|
|
41
|
-
const
|
|
42
|
-
|
|
46
|
+
const binary = "gh";
|
|
47
|
+
try {
|
|
48
|
+
const result = spawnSync(binary, [...args], {
|
|
49
|
+
env: environ,
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
timeout: 30_000,
|
|
52
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
args: [binary, ...args],
|
|
56
|
+
returncode: result.status ?? 1,
|
|
57
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
58
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
return { args: [binary, ...args], returncode: 1, stdout: "", stderr: message };
|
|
64
|
+
}
|
|
43
65
|
}
|
|
44
66
|
function splitRepo(repo) {
|
|
45
67
|
const idx = repo.indexOf("/");
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { ScmStubError } from "../scm/errors.js";
|
|
4
|
+
import { requireScmReady } from "../scm/readiness.js";
|
|
3
5
|
import { issueIngestMain } from "./issue-ingest.js";
|
|
4
6
|
function parseArgs(argv) {
|
|
5
7
|
const out = {};
|
|
@@ -25,6 +27,17 @@ function parseArgs(argv) {
|
|
|
25
27
|
return out;
|
|
26
28
|
}
|
|
27
29
|
export function mainEntry(argv = process.argv.slice(2)) {
|
|
30
|
+
// #2275: fail loud when gh/auth is missing in this execution env.
|
|
31
|
+
try {
|
|
32
|
+
requireScmReady();
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (err instanceof ScmStubError) {
|
|
36
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
28
41
|
return issueIngestMain(parseArgs(argv));
|
|
29
42
|
}
|
|
30
43
|
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { ScmStubError } from "../scm/errors.js";
|
|
4
|
+
import { requireScmReady } from "../scm/readiness.js";
|
|
3
5
|
import { reconcileMain } from "./reconcile-issues.js";
|
|
4
6
|
function parseArgs(argv) {
|
|
5
7
|
const out = {};
|
|
@@ -23,6 +25,17 @@ function parseArgs(argv) {
|
|
|
23
25
|
return out;
|
|
24
26
|
}
|
|
25
27
|
export function mainEntry(argv = process.argv.slice(2)) {
|
|
28
|
+
// #2275: fail loud when gh/auth is missing in this execution env.
|
|
29
|
+
try {
|
|
30
|
+
requireScmReady();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof ScmStubError) {
|
|
34
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
26
39
|
return reconcileMain(parseArgs(argv));
|
|
27
40
|
}
|
|
28
41
|
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
@@ -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";
|