@deftai/directive-core 0.108.0 → 0.109.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/cache/operations.js +1 -1
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +7 -0
- package/dist/design-critique/citation-grammar.js +1 -1
- package/dist/design-critique/completed-arc-record.d.ts +1 -1
- package/dist/design-critique/completed-arc-record.js +59 -14
- package/dist/hooks/classify/host-session-identity.d.ts +24 -11
- package/dist/hooks/classify/host-session-identity.js +46 -49
- package/dist/hooks/classify/index.d.ts +2 -2
- package/dist/hooks/classify/index.js +2 -2
- package/dist/hooks/classify/paths.d.ts +2 -0
- package/dist/hooks/classify/paths.js +8 -4
- package/dist/hooks/classify/stdin.js +69 -3
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +6 -2
- package/dist/hooks/dispatcher.js +258 -62
- package/dist/hooks/fixtures/cases.d.ts +6 -0
- package/dist/hooks/fixtures/cases.js +53 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +20 -2
- package/dist/hooks/tools.js +30 -1
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +21 -2
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +27 -0
- package/dist/orphan-active/evaluate.js +63 -6
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.js +36 -9
- package/dist/pr-wait-mergeable/wrappers.js +3 -3
- package/dist/product-first-done-gate/evaluate.js +6 -2
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/pipeline.js +55 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +6 -0
- package/dist/review-monitor/github-lease.js +4 -3
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +41 -11
- package/dist/scm/gh-rest.d.ts +25 -12
- package/dist/scm/gh-rest.js +65 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/host-session-owner.d.ts +53 -0
- package/dist/session/host-session-owner.js +89 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/occupancy.d.ts +21 -1
- package/dist/session/occupancy.js +70 -12
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +3 -3
package/dist/hooks/index.d.ts
CHANGED
|
@@ -3,5 +3,7 @@ export * from "./cursor-hooks.js";
|
|
|
3
3
|
export * from "./dest-form.js";
|
|
4
4
|
export * from "./dispatcher.js";
|
|
5
5
|
export * from "./fixtures/index.js";
|
|
6
|
+
export * from "./git-destructive-log.js";
|
|
6
7
|
export * from "./scope.js";
|
|
8
|
+
export * from "./shell-write-targets.js";
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/hooks/index.js
CHANGED
|
@@ -3,5 +3,7 @@ export * from "./cursor-hooks.js";
|
|
|
3
3
|
export * from "./dest-form.js";
|
|
4
4
|
export * from "./dispatcher.js";
|
|
5
5
|
export * from "./fixtures/index.js";
|
|
6
|
+
export * from "./git-destructive-log.js";
|
|
6
7
|
export * from "./scope.js";
|
|
8
|
+
export * from "./shell-write-targets.js";
|
|
7
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const SHELL_WRITE_KINDS: readonly ["set-content", "out-file", "add-content", "python-pathlib", "io-writealltext"];
|
|
2
|
+
export type ShellWriteKind = (typeof SHELL_WRITE_KINDS)[number];
|
|
3
|
+
export interface ShellWriteTarget {
|
|
4
|
+
readonly kind: ShellWriteKind;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly unprovable?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function classifyShellWriteTargets(command: string): ShellWriteTarget[];
|
|
9
|
+
export declare function isInRepoShellWritePath(projectRoot: string, dest: string): boolean;
|
|
10
|
+
//# sourceMappingURL=shell-write-targets.d.ts.map
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/** Recognized in-repo Shell file-write dests (#3983 / #3987). */
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
export const SHELL_WRITE_KINDS = [
|
|
4
|
+
"set-content",
|
|
5
|
+
"out-file",
|
|
6
|
+
"add-content",
|
|
7
|
+
"python-pathlib",
|
|
8
|
+
"io-writealltext",
|
|
9
|
+
];
|
|
10
|
+
const DQ = String.fromCharCode(34);
|
|
11
|
+
const AQ = String.fromCharCode(39);
|
|
12
|
+
function stripQuotes(token) {
|
|
13
|
+
const t = token.trim();
|
|
14
|
+
if (t.length < 2)
|
|
15
|
+
return t;
|
|
16
|
+
const a = t[0];
|
|
17
|
+
const b = t[t.length - 1];
|
|
18
|
+
if ((a === DQ && b === DQ) || (a === AQ && b === AQ))
|
|
19
|
+
return t.slice(1, -1);
|
|
20
|
+
return t;
|
|
21
|
+
}
|
|
22
|
+
function pushUnique(out, kind, raw) {
|
|
23
|
+
const path = stripQuotes(raw).trim();
|
|
24
|
+
if (path.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
if (out.some((d) => d.kind === kind && d.path === path))
|
|
27
|
+
return;
|
|
28
|
+
out.push({ kind, path });
|
|
29
|
+
}
|
|
30
|
+
function extractQuoted(command, start) {
|
|
31
|
+
if (start >= command.length)
|
|
32
|
+
return { value: "", next: start };
|
|
33
|
+
const q = command[start];
|
|
34
|
+
if (q !== DQ && q !== AQ) {
|
|
35
|
+
let i = start;
|
|
36
|
+
while (i < command.length) {
|
|
37
|
+
const c = command[i];
|
|
38
|
+
if (c === undefined)
|
|
39
|
+
break;
|
|
40
|
+
if (c === " " || c === " " || c === "," || c === ")")
|
|
41
|
+
break;
|
|
42
|
+
i += 1;
|
|
43
|
+
}
|
|
44
|
+
return { value: command.slice(start, i), next: i };
|
|
45
|
+
}
|
|
46
|
+
let i = start + 1;
|
|
47
|
+
while (i < command.length && command[i] !== q)
|
|
48
|
+
i += 1;
|
|
49
|
+
return { value: command.slice(start + 1, i), next: i + 1 };
|
|
50
|
+
}
|
|
51
|
+
function skipWs(command, start) {
|
|
52
|
+
let i = start;
|
|
53
|
+
while (i < command.length) {
|
|
54
|
+
const code = command.charCodeAt(i);
|
|
55
|
+
if (code !== 32 && code !== 9 && code !== 10 && code !== 13)
|
|
56
|
+
break;
|
|
57
|
+
i += 1;
|
|
58
|
+
}
|
|
59
|
+
return i;
|
|
60
|
+
}
|
|
61
|
+
function startsWithVerb(segment, verb) {
|
|
62
|
+
const i = skipWs(segment, 0);
|
|
63
|
+
if (segment.slice(i, i + verb.length).toLowerCase() !== verb.toLowerCase())
|
|
64
|
+
return false;
|
|
65
|
+
const next = segment[i + verb.length];
|
|
66
|
+
return next === undefined || next === " " || next === " " || next === "-";
|
|
67
|
+
}
|
|
68
|
+
function splitWriteSegments(command) {
|
|
69
|
+
const segs = [];
|
|
70
|
+
let cur = "";
|
|
71
|
+
let quote = null;
|
|
72
|
+
for (let i = 0; i < command.length; i++) {
|
|
73
|
+
const c = command[i];
|
|
74
|
+
if (c === undefined)
|
|
75
|
+
break;
|
|
76
|
+
if (quote !== null) {
|
|
77
|
+
if (c === quote)
|
|
78
|
+
quote = null;
|
|
79
|
+
cur += c;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (c === DQ || c === AQ) {
|
|
83
|
+
quote = c;
|
|
84
|
+
cur += c;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const code = command.charCodeAt(i);
|
|
88
|
+
const next = command.charCodeAt(i + 1);
|
|
89
|
+
if ((code === 38 && next === 38) || (code === 124 && next === 124)) {
|
|
90
|
+
segs.push(cur);
|
|
91
|
+
cur = "";
|
|
92
|
+
i += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (code === 124 || code === 38 || code === 59 || code === 10 || code === 13) {
|
|
96
|
+
segs.push(cur);
|
|
97
|
+
cur = "";
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
cur += c;
|
|
101
|
+
}
|
|
102
|
+
segs.push(cur);
|
|
103
|
+
return segs.map((seg) => seg.trim()).filter((seg) => seg.length > 0);
|
|
104
|
+
}
|
|
105
|
+
const PATH_PARAM_NAMES = ["path", "literalpath", "filepath"];
|
|
106
|
+
function uniqueNamedParam(flag, names) {
|
|
107
|
+
const f = flag.replace(/^-+/, "").toLowerCase();
|
|
108
|
+
if (f.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
const hits = names.filter((n) => n.startsWith(f));
|
|
111
|
+
return hits.length === 1 ? (hits[0] ?? null) : null;
|
|
112
|
+
}
|
|
113
|
+
function parseFlag(token) {
|
|
114
|
+
if (!token.startsWith("-"))
|
|
115
|
+
return null;
|
|
116
|
+
const colon = token.indexOf(":");
|
|
117
|
+
if (colon <= 0)
|
|
118
|
+
return { flag: token, attached: null };
|
|
119
|
+
return { flag: token.slice(0, colon), attached: token.slice(colon + 1) };
|
|
120
|
+
}
|
|
121
|
+
function extractAfterVerb(command, verb, kind, out) {
|
|
122
|
+
if (!startsWithVerb(command, verb))
|
|
123
|
+
return;
|
|
124
|
+
const i = skipWs(command, 0);
|
|
125
|
+
let j = skipWs(command, i + verb.length);
|
|
126
|
+
let named = null;
|
|
127
|
+
let positional = null;
|
|
128
|
+
while (j < command.length) {
|
|
129
|
+
const extracted = extractQuoted(command, j);
|
|
130
|
+
if (extracted.value.length === 0)
|
|
131
|
+
break;
|
|
132
|
+
j = skipWs(command, extracted.next);
|
|
133
|
+
const parsed = parseFlag(extracted.value);
|
|
134
|
+
if (parsed === null) {
|
|
135
|
+
if (positional === null)
|
|
136
|
+
positional = extracted.value;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
let arg = parsed.attached;
|
|
140
|
+
if (arg === null || arg.length === 0) {
|
|
141
|
+
const next = extractQuoted(command, j);
|
|
142
|
+
if (next.value.length > 0 && !next.value.startsWith("-")) {
|
|
143
|
+
arg = next.value;
|
|
144
|
+
j = skipWs(command, next.next);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
arg = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (uniqueNamedParam(parsed.flag, PATH_PARAM_NAMES) !== null) {
|
|
151
|
+
if (arg !== null && arg.length > 0 && !arg.startsWith("-"))
|
|
152
|
+
named = arg;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const dest = named ?? positional;
|
|
156
|
+
if (dest !== null && !dest.startsWith("-"))
|
|
157
|
+
pushUnique(out, kind, dest);
|
|
158
|
+
}
|
|
159
|
+
function pathlibWriteFollows(command, afterPathArgs) {
|
|
160
|
+
let i = skipWs(command, afterPathArgs);
|
|
161
|
+
if (command[i] === ")")
|
|
162
|
+
i = skipWs(command, i + 1);
|
|
163
|
+
while (i < command.length) {
|
|
164
|
+
if (command[i] === ";")
|
|
165
|
+
return false;
|
|
166
|
+
i = skipWs(command, i);
|
|
167
|
+
if (command[i] !== ".")
|
|
168
|
+
return false;
|
|
169
|
+
i += 1;
|
|
170
|
+
const start = i;
|
|
171
|
+
while (i < command.length) {
|
|
172
|
+
const c = command.charCodeAt(i);
|
|
173
|
+
if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95) {
|
|
174
|
+
i += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
const name = command.slice(start, i);
|
|
180
|
+
i = skipWs(command, i);
|
|
181
|
+
if (command[i] !== "(")
|
|
182
|
+
return false;
|
|
183
|
+
if (name === "write_text")
|
|
184
|
+
return true;
|
|
185
|
+
if (name === "write_bytes")
|
|
186
|
+
return true;
|
|
187
|
+
i += 1;
|
|
188
|
+
let depth = 1;
|
|
189
|
+
while (i < command.length && depth > 0) {
|
|
190
|
+
const ch = command[i];
|
|
191
|
+
if (ch === "(")
|
|
192
|
+
depth += 1;
|
|
193
|
+
else if (ch === ")")
|
|
194
|
+
depth -= 1;
|
|
195
|
+
i += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
function extractPythonPathlib(command, out) {
|
|
201
|
+
const i = skipWs(command, 0);
|
|
202
|
+
const head = command.slice(i, i + 10).toLowerCase();
|
|
203
|
+
if (!head.startsWith("python") && !head.startsWith("py ") && !head.startsWith("py.exe"))
|
|
204
|
+
return;
|
|
205
|
+
const needle = "Path(";
|
|
206
|
+
let from = i;
|
|
207
|
+
while (from < command.length) {
|
|
208
|
+
const at = command.indexOf(needle, from);
|
|
209
|
+
if (at < 0)
|
|
210
|
+
return;
|
|
211
|
+
let j = skipWs(command, at + needle.length);
|
|
212
|
+
const ch = command[j];
|
|
213
|
+
if (ch === "r" || ch === "R")
|
|
214
|
+
j = skipWs(command, j + 1);
|
|
215
|
+
const extracted = extractQuoted(command, j);
|
|
216
|
+
if (pathlibWriteFollows(command, extracted.next)) {
|
|
217
|
+
pushUnique(out, "python-pathlib", extracted.value);
|
|
218
|
+
}
|
|
219
|
+
from = at + needle.length;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function extractIoWrite(command, out) {
|
|
223
|
+
const i = skipWs(command, 0);
|
|
224
|
+
if (command[i] !== "[")
|
|
225
|
+
return;
|
|
226
|
+
const needles = ["WriteAllText(", "WriteAllBytes("];
|
|
227
|
+
for (const needle of needles) {
|
|
228
|
+
const at = command.indexOf(needle, i);
|
|
229
|
+
if (at < 0)
|
|
230
|
+
continue;
|
|
231
|
+
const j = skipWs(command, at + needle.length);
|
|
232
|
+
const extracted = extractQuoted(command, j);
|
|
233
|
+
pushUnique(out, "io-writealltext", extracted.value);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function classifySimpleWrite(command) {
|
|
237
|
+
const out = [];
|
|
238
|
+
extractAfterVerb(command, "Set-Content", "set-content", out);
|
|
239
|
+
extractAfterVerb(command, "Add-Content", "add-content", out);
|
|
240
|
+
extractAfterVerb(command, "Out-File", "out-file", out);
|
|
241
|
+
extractPythonPathlib(command, out);
|
|
242
|
+
extractIoWrite(command, out);
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
export function classifyShellWriteTargets(command) {
|
|
246
|
+
const cmd = command.trim();
|
|
247
|
+
if (cmd.length === 0)
|
|
248
|
+
return [];
|
|
249
|
+
const segments = splitWriteSegments(cmd);
|
|
250
|
+
if (segments.length === 1)
|
|
251
|
+
return classifySimpleWrite(segments[0] ?? cmd);
|
|
252
|
+
const found = [];
|
|
253
|
+
for (const seg of segments)
|
|
254
|
+
found.push(...classifySimpleWrite(seg));
|
|
255
|
+
if (found.length === 0)
|
|
256
|
+
return [];
|
|
257
|
+
return found.map((d) => ({ ...d, unprovable: true }));
|
|
258
|
+
}
|
|
259
|
+
export function isInRepoShellWritePath(projectRoot, dest) {
|
|
260
|
+
const path = dest.trim();
|
|
261
|
+
if (path.length === 0)
|
|
262
|
+
return false;
|
|
263
|
+
if (path.includes("*") || path.includes("?") || path.includes("$"))
|
|
264
|
+
return false;
|
|
265
|
+
const root = resolve(projectRoot);
|
|
266
|
+
const abs = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
267
|
+
const rel = relative(root, abs);
|
|
268
|
+
if (rel.length === 0)
|
|
269
|
+
return false;
|
|
270
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
271
|
+
return false;
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=shell-write-targets.js.map
|
package/dist/hooks/tools.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/** Host spellings that install-time matchers and runtime classification share. */
|
|
2
|
-
export declare const DIRECT_WRITE_TOOL_NAMES: readonly ["Edit", "Write", "WriteFile", "CreateFile", "MultiEdit", "NotebookEdit", "StrReplace", "SearchReplace", "Delete", "DeleteFile", "ApplyPatch", "apply_patch"];
|
|
2
|
+
export declare const DIRECT_WRITE_TOOL_NAMES: readonly ["Edit", "Write", "WriteFile", "CreateFile", "MultiEdit", "NotebookEdit", "StrReplace", "SearchReplace", "Delete", "DeleteFile", "ApplyPatch", "apply_patch", "write", "search_replace"];
|
|
3
3
|
/** PreToolUse spawn / sub-agent dispatch tools (#1185 / #2437). */
|
|
4
4
|
export declare const SPAWN_TOOL_NAMES: readonly ["Task", "SubagentStart", "spawn_subagent", "start_agent", "CreateAgent"];
|
|
5
5
|
/**
|
|
6
6
|
* Host spellings for Shell/Bash execution tools (#2711).
|
|
7
7
|
* Used for runtimeAuthority scopes.push / scopes.merge classification.
|
|
8
8
|
*/
|
|
9
|
-
export declare const SHELL_TOOL_NAMES: readonly ["Shell", "Bash", "BashTool", "shell", "bash"];
|
|
9
|
+
export declare const SHELL_TOOL_NAMES: readonly ["Shell", "Bash", "BashTool", "shell", "bash", "run_terminal_command"];
|
|
10
10
|
/** Env override forcing hook-level read-only write denial (#1185). */
|
|
11
11
|
export declare const READ_ONLY_HOOK_ENV = "DEFT_HOOK_READ_ONLY";
|
|
12
12
|
export declare function isDirectWriteTool(toolName: string): boolean;
|
|
@@ -32,6 +32,24 @@ export declare const MCP_PUSH_MERGE_BARE_NAMES: readonly ["merge_pull_request",
|
|
|
32
32
|
export declare const DIRECT_WRITE_HOOK_MATCHER: string;
|
|
33
33
|
export declare const SPAWN_HOOK_MATCHER: string;
|
|
34
34
|
export declare const SHELL_HOOK_MATCHER: string;
|
|
35
|
+
/** Known mutation tool names per host. Deposited matchers must cover each name (#3987). */
|
|
36
|
+
export declare const GROK_MUTATION_TOOL_CATALOG: {
|
|
37
|
+
readonly directWrite: readonly ["write", "search_replace"];
|
|
38
|
+
readonly shell: readonly ["run_terminal_command"];
|
|
39
|
+
readonly spawn: readonly ["spawn_subagent"];
|
|
40
|
+
};
|
|
41
|
+
/** Tools on this host that are intentionally not mutation-gated. */
|
|
42
|
+
export declare const GROK_NON_MUTATION_TOOLS: {
|
|
43
|
+
readonly read_file: "read";
|
|
44
|
+
readonly grep: "read";
|
|
45
|
+
readonly list_dir: "read";
|
|
46
|
+
readonly todo_write: "session-local non-product scratch";
|
|
47
|
+
readonly get_command_or_subagent_output: "poll, not a mutation; elapsed bound is evaluateInFlight";
|
|
48
|
+
readonly kill_command_or_subagent: "process control";
|
|
49
|
+
readonly search_tool: "read";
|
|
50
|
+
readonly web_search: "read";
|
|
51
|
+
};
|
|
52
|
+
export declare function matcherHasLiteralToken(matcher: string, toolName: string): boolean;
|
|
35
53
|
/**
|
|
36
54
|
* PreToolUse matcher for MCP-class push/merge tools (#2711).
|
|
37
55
|
* Regex-friendly for Claude/nested hosts. Prefer broad install match +
|
package/dist/hooks/tools.js
CHANGED
|
@@ -12,6 +12,8 @@ export const DIRECT_WRITE_TOOL_NAMES = [
|
|
|
12
12
|
"DeleteFile",
|
|
13
13
|
"ApplyPatch",
|
|
14
14
|
"apply_patch",
|
|
15
|
+
"write",
|
|
16
|
+
"search_replace",
|
|
15
17
|
];
|
|
16
18
|
/** PreToolUse spawn / sub-agent dispatch tools (#1185 / #2437). */
|
|
17
19
|
export const SPAWN_TOOL_NAMES = [
|
|
@@ -25,7 +27,14 @@ export const SPAWN_TOOL_NAMES = [
|
|
|
25
27
|
* Host spellings for Shell/Bash execution tools (#2711).
|
|
26
28
|
* Used for runtimeAuthority scopes.push / scopes.merge classification.
|
|
27
29
|
*/
|
|
28
|
-
export const SHELL_TOOL_NAMES = [
|
|
30
|
+
export const SHELL_TOOL_NAMES = [
|
|
31
|
+
"Shell",
|
|
32
|
+
"Bash",
|
|
33
|
+
"BashTool",
|
|
34
|
+
"shell",
|
|
35
|
+
"bash",
|
|
36
|
+
"run_terminal_command",
|
|
37
|
+
];
|
|
29
38
|
/** Env override forcing hook-level read-only write denial (#1185). */
|
|
30
39
|
export const READ_ONLY_HOOK_ENV = "DEFT_HOOK_READ_ONLY";
|
|
31
40
|
function normalizedToolName(toolName) {
|
|
@@ -87,6 +96,26 @@ export const MCP_PUSH_MERGE_BARE_NAMES = [
|
|
|
87
96
|
export const DIRECT_WRITE_HOOK_MATCHER = DIRECT_WRITE_TOOL_NAMES.join("|");
|
|
88
97
|
export const SPAWN_HOOK_MATCHER = SPAWN_TOOL_NAMES.join("|");
|
|
89
98
|
export const SHELL_HOOK_MATCHER = SHELL_TOOL_NAMES.join("|");
|
|
99
|
+
/** Known mutation tool names per host. Deposited matchers must cover each name (#3987). */
|
|
100
|
+
export const GROK_MUTATION_TOOL_CATALOG = {
|
|
101
|
+
directWrite: ["write", "search_replace"],
|
|
102
|
+
shell: ["run_terminal_command"],
|
|
103
|
+
spawn: ["spawn_subagent"],
|
|
104
|
+
};
|
|
105
|
+
/** Tools on this host that are intentionally not mutation-gated. */
|
|
106
|
+
export const GROK_NON_MUTATION_TOOLS = {
|
|
107
|
+
read_file: "read",
|
|
108
|
+
grep: "read",
|
|
109
|
+
list_dir: "read",
|
|
110
|
+
todo_write: "session-local non-product scratch",
|
|
111
|
+
get_command_or_subagent_output: "poll, not a mutation; elapsed bound is evaluateInFlight",
|
|
112
|
+
kill_command_or_subagent: "process control",
|
|
113
|
+
search_tool: "read",
|
|
114
|
+
web_search: "read",
|
|
115
|
+
};
|
|
116
|
+
export function matcherHasLiteralToken(matcher, toolName) {
|
|
117
|
+
return matcher.split("|").includes(toolName);
|
|
118
|
+
}
|
|
90
119
|
/**
|
|
91
120
|
* PreToolUse matcher for MCP-class push/merge tools (#2711).
|
|
92
121
|
* Regex-friendly for Claude/nested hosts. Prefer broad install match +
|
|
@@ -25,6 +25,13 @@ export interface ReconstituteDepositResult {
|
|
|
25
25
|
readonly reconstituted: boolean;
|
|
26
26
|
}
|
|
27
27
|
export type GitLsFiles = (projectDir: string, paths: readonly string[]) => string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Strip leading/trailing slashes so `.deft/cache/` and `.deft/cache` match.
|
|
30
|
+
* Globs are not expanded: a writer covered only by a glob fails closed (#3612).
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeGitignorePath(p: string): string;
|
|
33
|
+
export declare function gitignoreRuleCoversPath(rule: string, relPath: string): boolean;
|
|
34
|
+
export declare function ignoreSetCoversPath(rules: readonly string[], relPath: string): boolean;
|
|
28
35
|
/**
|
|
29
36
|
* Reports whether `.deft/core` is tracked in git. Returns `null` when git is
|
|
30
37
|
* unavailable or the tree is not a repository (treated as greenfield).
|
|
@@ -31,6 +31,13 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
31
31
|
".deft/last-session.json",
|
|
32
32
|
".deft/occupancy.json",
|
|
33
33
|
".deft/routing.local.json",
|
|
34
|
+
// #3612: remaining runtime writer roots kept at historical names (on-disk
|
|
35
|
+
// contracts). Relocating them would break existing consumer state.
|
|
36
|
+
".deft/authz/",
|
|
37
|
+
".deft/delivery-attempts/",
|
|
38
|
+
".deft/metrics/",
|
|
39
|
+
".deft/escalations/",
|
|
40
|
+
".deft/approved-scope/",
|
|
34
41
|
// Agent-host working state (#3502). Selective, NEVER blanket `.claude/`:
|
|
35
42
|
// `.claude/settings.json`, `.claude/skills/` and `.claude/commands/` are
|
|
36
43
|
// MANAGED DEPOSITS (agent-hooks.ts / skill-discovery-hosts.ts) and must stay
|
|
@@ -105,6 +112,23 @@ function gitignoreCoversLine(present, line) {
|
|
|
105
112
|
}
|
|
106
113
|
return false;
|
|
107
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Strip leading/trailing slashes so `.deft/cache/` and `.deft/cache` match.
|
|
117
|
+
* Globs are not expanded: a writer covered only by a glob fails closed (#3612).
|
|
118
|
+
*/
|
|
119
|
+
export function normalizeGitignorePath(p) {
|
|
120
|
+
return p.replaceAll("\\", "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
121
|
+
}
|
|
122
|
+
export function gitignoreRuleCoversPath(rule, relPath) {
|
|
123
|
+
const r = normalizeGitignorePath(stripGitignoreInlineComment(rule));
|
|
124
|
+
const p = normalizeGitignorePath(relPath);
|
|
125
|
+
if (!r || r.startsWith("#"))
|
|
126
|
+
return false;
|
|
127
|
+
return r === p || p.startsWith(`${r}/`);
|
|
128
|
+
}
|
|
129
|
+
export function ignoreSetCoversPath(rules, relPath) {
|
|
130
|
+
return rules.some((rule) => gitignoreRuleCoversPath(rule, relPath));
|
|
131
|
+
}
|
|
108
132
|
function collectPresentGitignoreLines(existing) {
|
|
109
133
|
const present = new Set();
|
|
110
134
|
for (const raw of existing.split("\n")) {
|
|
@@ -10,6 +10,7 @@ import { homedir, platform } from "node:os";
|
|
|
10
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
|
+
import { assertLiveProcedureDepositClean } from "../deposit/live-procedure-targets.js";
|
|
13
14
|
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
14
15
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
15
16
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
@@ -173,8 +174,10 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
173
174
|
const resolveContent = seams.resolveContentRoot ?? resolveInstalledContentRoot;
|
|
174
175
|
const copyContent = seams.copyContent ?? copyTree;
|
|
175
176
|
const contentRoot = await resolveContent();
|
|
177
|
+
assertLiveProcedureDepositClean(contentRoot);
|
|
176
178
|
await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
|
|
177
179
|
await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
|
|
180
|
+
assertLiveProcedureDepositClean(deftDir);
|
|
178
181
|
ensureInitGitignoreLines(projectDir, io);
|
|
179
182
|
ensurePrettierIgnoreLines(projectDir, io);
|
|
180
183
|
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
@@ -13,6 +13,7 @@ import { platform as osPlatform } from "node:os";
|
|
|
13
13
|
import { join, resolve } from "node:path";
|
|
14
14
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
15
15
|
import { replaceTree } from "../deposit/copy-tree.js";
|
|
16
|
+
import { assertLiveProcedureDepositClean } from "../deposit/live-procedure-targets.js";
|
|
16
17
|
import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
17
18
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
18
19
|
import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
@@ -476,9 +477,12 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
476
477
|
const strategy = alreadyCurrent ? "no-op" : "file-swap";
|
|
477
478
|
if (alreadyCurrent) {
|
|
478
479
|
io.printf("[deft update] Framework payload already current; skipping payload copy.\n");
|
|
480
|
+
// C3 even when VERSION matches: skip-copy is not skip-validation (#3602).
|
|
481
|
+
assertLiveProcedureDepositClean(contentRoot);
|
|
479
482
|
// #2913: still fail-closed reconcile so dst-only leftovers cannot linger when
|
|
480
483
|
// VERSION already matches (e.g. pre-#2804 additive deposits). Does not re-stamp.
|
|
481
484
|
await reconcileDepositToContentPackage(deftDir, contentRoot, io);
|
|
485
|
+
assertLiveProcedureDepositClean(deftDir);
|
|
482
486
|
migrateLegacyInstallManifest(projectDir, join(deftDir, "VERSION"));
|
|
483
487
|
// #3117: ensure a readable live generation token exists without advancing
|
|
484
488
|
// when the payload did not swap (already-current). stampLiveGeneration is a
|
|
@@ -504,8 +508,11 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
504
508
|
}
|
|
505
509
|
else {
|
|
506
510
|
// Full-tree replace (or injected seam). Additive copy is no longer the default.
|
|
511
|
+
// C3 the incoming package BEFORE replace so a reject cannot leave a broken deposit.
|
|
512
|
+
assertLiveProcedureDepositClean(contentRoot);
|
|
507
513
|
await copyContent(contentRoot, deftDir);
|
|
508
514
|
await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
|
|
515
|
+
assertLiveProcedureDepositClean(deftDir);
|
|
509
516
|
// #2913 / #2804 / #2347: fail-closed delete-not-in-source BEFORE VERSION stamp.
|
|
510
517
|
// replaceTree already drops dst-only paths; reconcile verifies and covers
|
|
511
518
|
// additive seams. Throws => no VERSION rewrite (refuse stamp until clean).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime writer paths that land in a consumer working tree (#3612).
|
|
3
|
+
*
|
|
4
|
+
* The ignore baselines and this list are coupled: every exported writer path
|
|
5
|
+
* constant MUST appear here, and a fail-closed test asserts the ignore set
|
|
6
|
+
* covers each entry on both shipped surfaces. New writers join this registry;
|
|
7
|
+
* they do not get a silent extra .deft/ name.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Imported writer path constants. Order is the lockstep contract with
|
|
11
|
+
* runtime-writer-paths.json (Go reads that file).
|
|
12
|
+
*/
|
|
13
|
+
export declare const RUNTIME_WRITER_PATHS: readonly string[];
|
|
14
|
+
//# sourceMappingURL=runtime-writers.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime writer paths that land in a consumer working tree (#3612).
|
|
3
|
+
*
|
|
4
|
+
* The ignore baselines and this list are coupled: every exported writer path
|
|
5
|
+
* constant MUST appear here, and a fail-closed test asserts the ignore set
|
|
6
|
+
* covers each entry on both shipped surfaces. New writers join this registry;
|
|
7
|
+
* they do not get a silent extra .deft/ name.
|
|
8
|
+
*/
|
|
9
|
+
import { AUTHZ_DIR } from "../authz/types.js";
|
|
10
|
+
import { DEFAULT_TASK_CACHE_ROOT } from "../cache/task-cache/constants.js";
|
|
11
|
+
import { SESSION_COMPLETED_MARKER_REL } from "../check/session-completed-ac.js";
|
|
12
|
+
import { DELIVERY_ATTEMPT_DIR } from "../delivery-attempt/types.js";
|
|
13
|
+
import { ESCALATION_DIR } from "../escalation/types.js";
|
|
14
|
+
import { PROJECT_LOCAL_METRICS_DIR } from "../metrics/resolve-metrics-home.js";
|
|
15
|
+
import { APPROVED_SCOPE_DIR } from "../scope-provenance/digest.js";
|
|
16
|
+
import { AC_PASS_BANK_DIR } from "../session/ac-pass-banking.js";
|
|
17
|
+
import { VERIFY_AC_SESSION_CACHE_DIR } from "../session/verify-ac-session-cache.js";
|
|
18
|
+
/**
|
|
19
|
+
* Imported writer path constants. Order is the lockstep contract with
|
|
20
|
+
* runtime-writer-paths.json (Go reads that file).
|
|
21
|
+
*/
|
|
22
|
+
export const RUNTIME_WRITER_PATHS = Object.freeze([
|
|
23
|
+
AC_PASS_BANK_DIR,
|
|
24
|
+
VERIFY_AC_SESSION_CACHE_DIR,
|
|
25
|
+
SESSION_COMPLETED_MARKER_REL.join("/"),
|
|
26
|
+
AUTHZ_DIR,
|
|
27
|
+
DELIVERY_ATTEMPT_DIR,
|
|
28
|
+
PROJECT_LOCAL_METRICS_DIR,
|
|
29
|
+
ESCALATION_DIR,
|
|
30
|
+
APPROVED_SCOPE_DIR,
|
|
31
|
+
DEFAULT_TASK_CACHE_ROOT,
|
|
32
|
+
]);
|
|
33
|
+
//# sourceMappingURL=runtime-writers.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ScanFlag } from "../cache/scanner.js";
|
|
2
|
-
import { type IssueComment as ShapeIssueComment } from "../umbrella-current-shape/index.js";
|
|
2
|
+
import { type CurrentShapeNullReason, type IssueComment as ShapeIssueComment } from "../umbrella-current-shape/index.js";
|
|
3
3
|
import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
4
4
|
import { type ScmCallFn } from "./reconcile-issues.js";
|
|
5
5
|
/** Reference type pointing at the canonical current-shape comment permalink (#1870). */
|
|
@@ -42,6 +42,11 @@ export declare function mapIngestCommentsToShapeComments(comments: readonly Issu
|
|
|
42
42
|
export declare function selectIngestCurrentShape(comments: readonly IssueComment[]): (ShapeIssueComment & {
|
|
43
43
|
pass: number;
|
|
44
44
|
}) | null;
|
|
45
|
+
/**
|
|
46
|
+
* Classify why `selectIngestCurrentShape` returned null so ingest can report a
|
|
47
|
+
* reason instead of silently omitting the CurrentShape narrative (#3934).
|
|
48
|
+
*/
|
|
49
|
+
export declare function describeIngestCurrentShapeNull(comments: readonly IssueComment[]): CurrentShapeNullReason;
|
|
45
50
|
/** Count maintainer current-shape comments (lint surface for !=1). */
|
|
46
51
|
export declare function countIngestCurrentShapeComments(comments: readonly IssueComment[]): number;
|
|
47
52
|
export declare function bodyControlCharacterLabels(body: string): string[];
|
|
@@ -11,7 +11,7 @@ import { stampAcceptanceFromLiteralCapture } from "../product-first-done-gate/in
|
|
|
11
11
|
import { call } from "../scm/call.js";
|
|
12
12
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
13
13
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
14
|
-
import { countMaintainerCurrentShapeComments, mapIssueCommentEntry, RAW_ISSUE_COMMENTS_KEY, selectCurrentShapeComment, } from "../umbrella-current-shape/index.js";
|
|
14
|
+
import { countMaintainerCurrentShapeComments, describeCurrentShapeNull, mapIssueCommentEntry, RAW_ISSUE_COMMENTS_KEY, selectCurrentShapeComment, } from "../umbrella-current-shape/index.js";
|
|
15
15
|
import { slugify, TODAY } from "../vbrief-build/build.js";
|
|
16
16
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
17
17
|
import { stampDerivedClausesOnAcceptance } from "../verify-ac/clauses.js";
|
|
@@ -66,6 +66,13 @@ export function mapIngestCommentsToShapeComments(comments) {
|
|
|
66
66
|
export function selectIngestCurrentShape(comments) {
|
|
67
67
|
return selectCurrentShapeComment(mapIngestCommentsToShapeComments(comments));
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Classify why `selectIngestCurrentShape` returned null so ingest can report a
|
|
71
|
+
* reason instead of silently omitting the CurrentShape narrative (#3934).
|
|
72
|
+
*/
|
|
73
|
+
export function describeIngestCurrentShapeNull(comments) {
|
|
74
|
+
return describeCurrentShapeNull(mapIngestCommentsToShapeComments(comments));
|
|
75
|
+
}
|
|
69
76
|
/** Count maintainer current-shape comments (lint surface for !=1). */
|
|
70
77
|
export function countIngestCurrentShapeComments(comments) {
|
|
71
78
|
return countMaintainerCurrentShapeComments(mapIngestCommentsToShapeComments(comments));
|
|
@@ -409,6 +416,17 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
409
416
|
// Scan shape body under the same fail-closed contract as Overview (#2306).
|
|
410
417
|
narratives.CurrentShape = scanUntrustedIngestText(number, currentShape.body);
|
|
411
418
|
}
|
|
419
|
+
else {
|
|
420
|
+
// #3934: a thread whose only shape comments failed the #2307 authorship
|
|
421
|
+
// filter is otherwise indistinguishable from a thread that never had one,
|
|
422
|
+
// so a cache-reading agent falls back to the stale body. The note is
|
|
423
|
+
// framework-authored and carries comment ids plus normalized author
|
|
424
|
+
// associations only -- no comment text -- so it needs no quarantine scan.
|
|
425
|
+
const shapeNullReason = describeIngestCurrentShapeNull(commentThread);
|
|
426
|
+
if (shapeNullReason.kind === "non-maintainer-shape") {
|
|
427
|
+
narratives.CurrentShapeUnavailable = shapeNullReason.message;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
412
430
|
if (labelNames.length > 0) {
|
|
413
431
|
narratives.Labels = labelNames.join(", ");
|
|
414
432
|
}
|
|
@@ -640,7 +658,8 @@ export function fetchSingleIssue(repo, number, options = {}) {
|
|
|
640
658
|
return null;
|
|
641
659
|
}
|
|
642
660
|
if (result.returncode !== 0) {
|
|
643
|
-
|
|
661
|
+
const invoked = result.args?.[0] ?? "gh";
|
|
662
|
+
process.stderr.write(`Error: ${invoked} failed fetching #${number}: ${result.stderr.trim()}\n`);
|
|
644
663
|
return null;
|
|
645
664
|
}
|
|
646
665
|
try {
|