@deftai/directive-core 0.108.0 → 0.109.1
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 +8 -1
- package/dist/design-critique/completed-arc-record.js +98 -14
- package/dist/hooks/classify/host-session-identity.d.ts +25 -12
- package/dist/hooks/classify/host-session-identity.js +64 -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 +22 -2
- package/dist/hooks/dispatcher.js +338 -66
- 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 +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/owner-liveness.d.ts +92 -0
- package/dist/hooks/owner-liveness.js +103 -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 +49 -2
- package/dist/hooks/tools.js +96 -1
- package/dist/init-deposit/agent-hooks.d.ts +10 -0
- package/dist/init-deposit/agent-hooks.js +39 -0
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/host-tool-coverage.d.ts +53 -0
- package/dist/init-deposit/host-tool-coverage.js +150 -0
- package/dist/init-deposit/index.d.ts +1 -0
- package/dist/init-deposit/index.js +1 -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/orchestration/subagent-monitor.d.ts +6 -0
- package/dist/orchestration/subagent-monitor.js +23 -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/child-occupancy.d.ts +72 -0
- package/dist/session/child-occupancy.js +209 -0
- package/dist/session/host-session-owner.d.ts +93 -0
- package/dist/session/host-session-owner.js +148 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/occupancy.d.ts +107 -2
- package/dist/session/occupancy.js +272 -34
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/swarm/complete-cohort.js +2 -0
- package/dist/swarm/pre-dispatch.js +2 -0
- package/dist/swarm/subagent-status-dir.d.ts +2 -1
- package/dist/swarm/subagent-status-dir.js +11 -2
- package/dist/swarm/worktrees.js +2 -0
- 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-env/agent-hooks.d.ts +6 -1
- package/dist/verify-env/agent-hooks.js +28 -2
- 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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { containedRemove, containedWrite } from "../../fs/contained-write.js";
|
|
3
5
|
import { defaultGitRunner as swarmGitRunner, } from "../../swarm/worktrees.js";
|
|
4
6
|
import { evaluatorWorktreePath } from "./paths.js";
|
|
5
7
|
export class EvaluatorWorktreeError extends Error {
|
|
@@ -27,17 +29,162 @@ function forceDeleteWorktreeDir(worktreePath) {
|
|
|
27
29
|
}
|
|
28
30
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
29
31
|
}
|
|
32
|
+
const caseInsensitiveDirCache = new Map();
|
|
33
|
+
function slashResolve(path) {
|
|
34
|
+
return resolve(path).replace(/\\/g, "/");
|
|
35
|
+
}
|
|
36
|
+
function directoryIgnoresCase(dir) {
|
|
37
|
+
let existing = resolve(dir);
|
|
38
|
+
while (!existsSync(existing)) {
|
|
39
|
+
const parent = dirname(existing);
|
|
40
|
+
if (parent === existing) {
|
|
41
|
+
return process.platform === "win32";
|
|
42
|
+
}
|
|
43
|
+
existing = parent;
|
|
44
|
+
}
|
|
45
|
+
const cached = caseInsensitiveDirCache.get(existing);
|
|
46
|
+
if (cached !== undefined) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
const tag = randomBytes(6).toString("hex");
|
|
50
|
+
const lower = join(existing, `.deft-cs-${tag}a`);
|
|
51
|
+
const upper = join(existing, `.deft-cs-${tag}A`);
|
|
52
|
+
let ignores = false;
|
|
53
|
+
let probed = false;
|
|
54
|
+
try {
|
|
55
|
+
containedWrite({
|
|
56
|
+
root: existing,
|
|
57
|
+
target: lower,
|
|
58
|
+
data: "",
|
|
59
|
+
mode: "create",
|
|
60
|
+
mkdir: false,
|
|
61
|
+
mutation: false,
|
|
62
|
+
});
|
|
63
|
+
ignores = existsSync(upper);
|
|
64
|
+
probed = true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Fail closed: do not fold unless the probe proved case-insensitivity.
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
try {
|
|
71
|
+
containedRemove({ root: existing, target: lower, mutation: false });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
containedRemove({ root: existing, target: upper, mutation: false });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (probed) {
|
|
84
|
+
caseInsensitiveDirCache.set(existing, ignores);
|
|
85
|
+
}
|
|
86
|
+
return ignores;
|
|
87
|
+
}
|
|
88
|
+
function canonicalizeWorktreePath(path) {
|
|
89
|
+
const resolved = slashResolve(path);
|
|
90
|
+
let existing = "";
|
|
91
|
+
if (existsSync(path)) {
|
|
92
|
+
existing = path;
|
|
93
|
+
}
|
|
94
|
+
else if (existsSync(resolved)) {
|
|
95
|
+
existing = resolved;
|
|
96
|
+
}
|
|
97
|
+
if (existing.length > 0) {
|
|
98
|
+
try {
|
|
99
|
+
return realpathSync.native(existing).replace(/\\/g, "/");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Path vanished between exists and realpath.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const parent = dirname(resolved);
|
|
106
|
+
const base = basename(resolved);
|
|
107
|
+
let parentCanon = slashResolve(parent);
|
|
108
|
+
try {
|
|
109
|
+
if (existsSync(parent)) {
|
|
110
|
+
parentCanon = realpathSync.native(parent).replace(/\\/g, "/");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Keep slash-resolved parent.
|
|
115
|
+
}
|
|
116
|
+
const leaf = directoryIgnoresCase(parent) ? base.toLowerCase() : base;
|
|
117
|
+
return `${parentCanon}/${leaf}`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unregister one missing worktree by deleting only its `$GIT_DIR/worktrees/<id>`
|
|
121
|
+
* admin directory. Containment root is that worktrees dir (the git common dir
|
|
122
|
+
* may sit outside a linked-worktree projectRoot). `git worktree prune` has no
|
|
123
|
+
* path argument and operates on every registration, including concurrent
|
|
124
|
+
* agents' reflogs.
|
|
125
|
+
*
|
|
126
|
+
* Path identity uses on-disk realpath when the worktree exists, and otherwise
|
|
127
|
+
* folds case only when the parent directory is case-insensitive. A case-sensitive
|
|
128
|
+
* Windows directory can host two worktrees that differ only by case; those
|
|
129
|
+
* must not share an admin entry.
|
|
130
|
+
*/
|
|
131
|
+
function pruneEvaluatorWorktreeAdmin(git, projectRoot, worktreePath) {
|
|
132
|
+
const common = git(["rev-parse", "--git-common-dir"], projectRoot);
|
|
133
|
+
const trimmed = common.stdout.trim();
|
|
134
|
+
if (common.returncode !== 0 || trimmed.length === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const commonDir = isAbsolute(trimmed) ? resolve(trimmed) : resolve(projectRoot, trimmed);
|
|
138
|
+
const worktreesDir = join(commonDir, "worktrees");
|
|
139
|
+
if (!existsSync(worktreesDir)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const needle = canonicalizeWorktreePath(worktreePath);
|
|
143
|
+
let names = [];
|
|
144
|
+
try {
|
|
145
|
+
names = readdirSync(worktreesDir);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const name of names) {
|
|
151
|
+
if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const gitdirFile = join(worktreesDir, name, "gitdir");
|
|
155
|
+
if (!existsSync(gitdirFile)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
let recorded = "";
|
|
159
|
+
try {
|
|
160
|
+
recorded = readFileSync(gitdirFile, "utf8").trim();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const recordedWorktree = recorded.replace(/\\/g, "/").replace(/\/\.git$/u, "");
|
|
166
|
+
if (canonicalizeWorktreePath(recordedWorktree) === needle) {
|
|
167
|
+
containedRemove({
|
|
168
|
+
root: resolve(worktreesDir),
|
|
169
|
+
target: join(worktreesDir, name),
|
|
170
|
+
recursive: true,
|
|
171
|
+
mutation: false,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
30
177
|
function worktreeStillRegistered(git, projectRoot, worktreePath) {
|
|
31
178
|
const listed = git(["worktree", "list", "--porcelain"], projectRoot);
|
|
32
179
|
if (listed.returncode !== 0) {
|
|
33
180
|
return true;
|
|
34
181
|
}
|
|
35
|
-
const needle =
|
|
182
|
+
const needle = canonicalizeWorktreePath(worktreePath);
|
|
36
183
|
for (const line of listed.stdout.split(/\r?\n/u)) {
|
|
37
184
|
if (!line.startsWith("worktree ")) {
|
|
38
185
|
continue;
|
|
39
186
|
}
|
|
40
|
-
const listedPath = line.slice("worktree ".length)
|
|
187
|
+
const listedPath = canonicalizeWorktreePath(line.slice("worktree ".length));
|
|
41
188
|
if (listedPath === needle) {
|
|
42
189
|
return true;
|
|
43
190
|
}
|
|
@@ -53,13 +200,13 @@ export function removeEvaluatorWorktree(projectRoot, worktreePath, git = swarmGi
|
|
|
53
200
|
}
|
|
54
201
|
const firstError = proc.stderr.trim() || "<no stderr>";
|
|
55
202
|
forceDeleteWorktreeDir(worktreePath);
|
|
56
|
-
runner
|
|
203
|
+
pruneEvaluatorWorktreeAdmin(runner, projectRoot, worktreePath);
|
|
57
204
|
const retry = runner(["worktree", "remove", "--force", worktreePath], projectRoot);
|
|
58
205
|
if (retry.returncode === 0) {
|
|
59
206
|
return;
|
|
60
207
|
}
|
|
61
208
|
forceDeleteWorktreeDir(worktreePath);
|
|
62
|
-
runner
|
|
209
|
+
pruneEvaluatorWorktreeAdmin(runner, projectRoot, worktreePath);
|
|
63
210
|
if (!worktreeStillRegistered(runner, projectRoot, worktreePath) && !existsSync(worktreePath)) {
|
|
64
211
|
return;
|
|
65
212
|
}
|
|
@@ -93,6 +93,27 @@ export interface CurrentShapeSidecar {
|
|
|
93
93
|
readonly authorAssociation: string;
|
|
94
94
|
readonly body: string;
|
|
95
95
|
}
|
|
96
|
+
/** A shape-shaped comment dropped by the #2307 authorship filter (#3934). */
|
|
97
|
+
export interface DiscardedShapeCandidate {
|
|
98
|
+
readonly commentId: number;
|
|
99
|
+
/** Normalized `author_association` -- never comment text. */
|
|
100
|
+
readonly authorAssociation: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Why `selectCurrentShapeComment` returned null (#3934). Advisory: it is not
|
|
104
|
+
* consumed by any gate, exit code, or count.
|
|
105
|
+
*/
|
|
106
|
+
export interface CurrentShapeNullReason {
|
|
107
|
+
readonly kind: "no-shape-comment" | "non-maintainer-shape";
|
|
108
|
+
readonly discarded: readonly DiscardedShapeCandidate[];
|
|
109
|
+
readonly message: string;
|
|
110
|
+
}
|
|
111
|
+
/** Sidecar payload, or the reason none was selectable (#3934). */
|
|
112
|
+
export interface CurrentShapeSidecarOutcome {
|
|
113
|
+
readonly sidecar: CurrentShapeSidecar | null;
|
|
114
|
+
/** Non-null exactly when `sidecar` is null. */
|
|
115
|
+
readonly reason: CurrentShapeNullReason | null;
|
|
116
|
+
}
|
|
96
117
|
/** True when labels or sub-issue summary mark the issue as umbrella/tracker-like. */
|
|
97
118
|
export declare function isUmbrellaLikeIssue(raw: Record<string, unknown>): boolean;
|
|
98
119
|
/** Normalize a raw issue payload's comment array (REST or pre-mapped). */
|
|
@@ -104,13 +125,28 @@ export declare function commentsFromRawPayload(raw: Record<string, unknown>): Is
|
|
|
104
125
|
export declare function countMaintainerCurrentShapeComments(comments: readonly IssueComment[]): number;
|
|
105
126
|
/** Markdown section appended to cache content.md when a canonical shape exists. */
|
|
106
127
|
export declare function formatCurrentShapeSection(selected: CurrentShapeComment): string;
|
|
128
|
+
/** Advisory cache note for a selected-null thread that had discarded candidates (#3934). */
|
|
129
|
+
export declare function formatCurrentShapeNotSelectedSection(reason: CurrentShapeNullReason): string;
|
|
107
130
|
/**
|
|
108
131
|
* Append the canonical current-shape comment (if any) to a rendered cache body.
|
|
109
|
-
*
|
|
132
|
+
*
|
|
133
|
+
* When nothing is selectable but the thread carried shape-shaped comments that
|
|
134
|
+
* the #2307 authorship filter dropped, append the advisory not-selected note
|
|
135
|
+
* instead of returning the body unchanged (#3934): an agent reading content.md
|
|
136
|
+
* without invoking `umbrella:current-shape` would otherwise see only the stale
|
|
137
|
+
* body the #1152 rule tells it to distrust. A thread with no shape comment at
|
|
138
|
+
* all is still returned unchanged, so ordinary issues gain no note.
|
|
110
139
|
*/
|
|
111
140
|
export declare function appendCurrentShapeSection(baseContent: string, raw: Record<string, unknown>): string;
|
|
112
|
-
/**
|
|
113
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Build the current-shape.json sidecar payload.
|
|
143
|
+
*
|
|
144
|
+
* Reports why nothing was selectable rather than returning a bare null (#3934),
|
|
145
|
+
* so a caller can tell "no shape comment on this thread" from "a shape comment
|
|
146
|
+
* exists but its author is outside MAINTAINER_ASSOCIATIONS". Advisory only --
|
|
147
|
+
* no sidecar is written on either null kind, exactly as before.
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildCurrentShapeSidecar(raw: Record<string, unknown>): CurrentShapeSidecarOutcome;
|
|
114
150
|
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
115
151
|
export declare function parseCommentsFromGhStdout(stdout: string): IssueComment[];
|
|
116
152
|
export declare function extractPassFromBody(body: string): number | null;
|
|
@@ -128,6 +164,18 @@ export declare function detectSections(body: string): SectionPresence;
|
|
|
128
164
|
export declare function sectionsRecord(presence: SectionPresence): Record<string, boolean>;
|
|
129
165
|
export declare const NO_CURRENT_SHAPE_MESSAGE: string;
|
|
130
166
|
export declare const NON_MAINTAINER_CURRENT_SHAPE_MESSAGE: string;
|
|
167
|
+
/** Bound the diagnostic so one forged thread cannot flood a cache note (#3934). */
|
|
168
|
+
export declare const MAX_REPORTED_DISCARDED_CANDIDATES = 5;
|
|
169
|
+
/**
|
|
170
|
+
* Classify a null return from `selectCurrentShapeComment` (#3934).
|
|
171
|
+
*
|
|
172
|
+
* Advisory only: it changes no selection, no maintainer count, and no exit code.
|
|
173
|
+
* Discarded candidates are named by comment id and normalized author
|
|
174
|
+
* association; a comment body is never reproduced, because forwarding untrusted
|
|
175
|
+
* text into a cache or xBRIEF narrative is the injection this filter exists to
|
|
176
|
+
* refuse (#2307).
|
|
177
|
+
*/
|
|
178
|
+
export declare function describeCurrentShapeNull(comments: readonly IssueComment[]): CurrentShapeNullReason;
|
|
131
179
|
export declare function fetchCurrentShape(options: {
|
|
132
180
|
repo: string;
|
|
133
181
|
issueNumber: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { scan } from "../cache/scanner.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveBinaryForArgv } from "../scm/call-shape.js";
|
|
4
4
|
import { SUBPROCESS_MAX_BUFFER } from "../subprocess/max-buffer.js";
|
|
5
5
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
6
6
|
/**
|
|
@@ -192,30 +192,64 @@ export function formatCurrentShapeSection(selected) {
|
|
|
192
192
|
"",
|
|
193
193
|
].join("\n");
|
|
194
194
|
}
|
|
195
|
+
/** Advisory cache note for a selected-null thread that had discarded candidates (#3934). */
|
|
196
|
+
export function formatCurrentShapeNotSelectedSection(reason) {
|
|
197
|
+
return [
|
|
198
|
+
"",
|
|
199
|
+
"---",
|
|
200
|
+
"",
|
|
201
|
+
"## Canonical current shape: not selected (#1152 / #2307)",
|
|
202
|
+
"",
|
|
203
|
+
`_${reason.message} Deterministic read path: \`task umbrella:current-shape <N>\`._`,
|
|
204
|
+
"",
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
195
207
|
/**
|
|
196
208
|
* Append the canonical current-shape comment (if any) to a rendered cache body.
|
|
197
|
-
*
|
|
209
|
+
*
|
|
210
|
+
* When nothing is selectable but the thread carried shape-shaped comments that
|
|
211
|
+
* the #2307 authorship filter dropped, append the advisory not-selected note
|
|
212
|
+
* instead of returning the body unchanged (#3934): an agent reading content.md
|
|
213
|
+
* without invoking `umbrella:current-shape` would otherwise see only the stale
|
|
214
|
+
* body the #1152 rule tells it to distrust. A thread with no shape comment at
|
|
215
|
+
* all is still returned unchanged, so ordinary issues gain no note.
|
|
198
216
|
*/
|
|
199
217
|
export function appendCurrentShapeSection(baseContent, raw) {
|
|
200
|
-
const
|
|
218
|
+
const comments = commentsFromRawPayload(raw);
|
|
219
|
+
const selected = selectCurrentShapeComment(comments);
|
|
201
220
|
if (selected === null) {
|
|
202
|
-
|
|
221
|
+
const reason = describeCurrentShapeNull(comments);
|
|
222
|
+
if (reason.kind !== "non-maintainer-shape") {
|
|
223
|
+
return baseContent;
|
|
224
|
+
}
|
|
225
|
+
return `${baseContent.trimEnd()}\n${formatCurrentShapeNotSelectedSection(reason)}`;
|
|
203
226
|
}
|
|
204
227
|
return `${baseContent.trimEnd()}\n${formatCurrentShapeSection(selected)}`;
|
|
205
228
|
}
|
|
206
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Build the current-shape.json sidecar payload.
|
|
231
|
+
*
|
|
232
|
+
* Reports why nothing was selectable rather than returning a bare null (#3934),
|
|
233
|
+
* so a caller can tell "no shape comment on this thread" from "a shape comment
|
|
234
|
+
* exists but its author is outside MAINTAINER_ASSOCIATIONS". Advisory only --
|
|
235
|
+
* no sidecar is written on either null kind, exactly as before.
|
|
236
|
+
*/
|
|
207
237
|
export function buildCurrentShapeSidecar(raw) {
|
|
208
|
-
const
|
|
238
|
+
const comments = commentsFromRawPayload(raw);
|
|
239
|
+
const selected = selectCurrentShapeComment(comments);
|
|
209
240
|
if (selected === null) {
|
|
210
|
-
return null;
|
|
241
|
+
return { sidecar: null, reason: describeCurrentShapeNull(comments) };
|
|
211
242
|
}
|
|
212
243
|
return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
244
|
+
sidecar: {
|
|
245
|
+
commentId: selected.id,
|
|
246
|
+
htmlUrl: selected.htmlUrl,
|
|
247
|
+
pass: selected.pass,
|
|
248
|
+
authorLogin: selected.authorLogin,
|
|
249
|
+
authorAssociation: selected.authorAssociation,
|
|
250
|
+
body: selected.body,
|
|
251
|
+
},
|
|
252
|
+
reason: null,
|
|
219
253
|
};
|
|
220
254
|
}
|
|
221
255
|
/** Merge `gh api --paginate` concatenated JSON array pages into comment rows. */
|
|
@@ -278,9 +312,10 @@ export function parseCommentsFromGhStdout(stdout) {
|
|
|
278
312
|
return comments;
|
|
279
313
|
}
|
|
280
314
|
function defaultFetchComments(repo, issueNumber) {
|
|
281
|
-
const binary = resolveBinary();
|
|
282
315
|
const path = `repos/${repo}/issues/${issueNumber}/comments?per_page=100`;
|
|
283
|
-
const
|
|
316
|
+
const apiArgs = ["--paginate", path];
|
|
317
|
+
const binary = resolveBinaryForArgv("api", apiArgs);
|
|
318
|
+
const proc = spawnSync(binary, ["api", ...apiArgs], {
|
|
284
319
|
encoding: "utf8",
|
|
285
320
|
maxBuffer: SUBPROCESS_MAX_BUFFER,
|
|
286
321
|
});
|
|
@@ -385,6 +420,58 @@ export const NON_MAINTAINER_CURRENT_SHAPE_MESSAGE = "A ## Current shape (as of p
|
|
|
385
420
|
"non-maintainer (author_association not in OWNER/MEMBER/COLLABORATOR) and is " +
|
|
386
421
|
"ignored per AGENTS.md ## Umbrella current-shape convention (#1152 / #2307). " +
|
|
387
422
|
"A maintainer must (re-)post the current-shape comment for it to be authoritative.";
|
|
423
|
+
/** Bound the diagnostic so one forged thread cannot flood a cache note (#3934). */
|
|
424
|
+
export const MAX_REPORTED_DISCARDED_CANDIDATES = 5;
|
|
425
|
+
const SAFE_ASSOCIATION_RE = /^[A-Z_]{1,32}$/;
|
|
426
|
+
/**
|
|
427
|
+
* GitHub sets `author_association` from a fixed enum, but a replayed or
|
|
428
|
+
* hand-built payload can carry anything. Normalizing keeps the diagnostic from
|
|
429
|
+
* smuggling arbitrary payload text into an agent-facing surface (#3934).
|
|
430
|
+
*/
|
|
431
|
+
function normalizeAssociation(raw) {
|
|
432
|
+
const upper = raw.trim().toUpperCase();
|
|
433
|
+
return SAFE_ASSOCIATION_RE.test(upper) ? upper : "UNKNOWN";
|
|
434
|
+
}
|
|
435
|
+
function formatDiscardedCandidates(discarded) {
|
|
436
|
+
const shown = discarded
|
|
437
|
+
.slice(0, MAX_REPORTED_DISCARDED_CANDIDATES)
|
|
438
|
+
.map((candidate) => `comment ${candidate.commentId} (${candidate.authorAssociation})`);
|
|
439
|
+
const hidden = discarded.length - shown.length;
|
|
440
|
+
return hidden > 0 ? `${shown.join(", ")}, and ${hidden} more` : shown.join(", ");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Classify a null return from `selectCurrentShapeComment` (#3934).
|
|
444
|
+
*
|
|
445
|
+
* Advisory only: it changes no selection, no maintainer count, and no exit code.
|
|
446
|
+
* Discarded candidates are named by comment id and normalized author
|
|
447
|
+
* association; a comment body is never reproduced, because forwarding untrusted
|
|
448
|
+
* text into a cache or xBRIEF narrative is the injection this filter exists to
|
|
449
|
+
* refuse (#2307).
|
|
450
|
+
*/
|
|
451
|
+
export function describeCurrentShapeNull(comments) {
|
|
452
|
+
const discarded = [];
|
|
453
|
+
for (const comment of comments) {
|
|
454
|
+
if (isMaintainerAuthored(comment.authorAssociation)) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (extractPassFromBody(comment.body) === null) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
discarded.push({
|
|
461
|
+
commentId: comment.id,
|
|
462
|
+
authorAssociation: normalizeAssociation(comment.authorAssociation),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (discarded.length === 0) {
|
|
466
|
+
return { kind: "no-shape-comment", discarded: [], message: NO_CURRENT_SHAPE_MESSAGE };
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
kind: "non-maintainer-shape",
|
|
470
|
+
discarded,
|
|
471
|
+
message: `${NON_MAINTAINER_CURRENT_SHAPE_MESSAGE} ` +
|
|
472
|
+
`Discarded candidate(s): ${formatDiscardedCandidates(discarded)}.`,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
388
475
|
export function fetchCurrentShape(options) {
|
|
389
476
|
const fetcher = options.fetchComments ?? defaultFetchComments;
|
|
390
477
|
const fetched = fetcher(options.repo, options.issueNumber);
|
|
@@ -393,11 +480,12 @@ export function fetchCurrentShape(options) {
|
|
|
393
480
|
}
|
|
394
481
|
const selected = selectCurrentShapeComment(fetched);
|
|
395
482
|
if (selected === null) {
|
|
396
|
-
// Distinguish provenance-filtered absence from genuine absence (#2307)
|
|
397
|
-
|
|
483
|
+
// Distinguish provenance-filtered absence from genuine absence (#2307), via
|
|
484
|
+
// the same classifier the cache-side callers use (#3934). The CLI keeps its
|
|
485
|
+
// two existing messages verbatim -- discarded ids stay off this surface.
|
|
398
486
|
return {
|
|
399
487
|
ok: false,
|
|
400
|
-
error:
|
|
488
|
+
error: describeCurrentShapeNull(fetched).kind === "non-maintainer-shape"
|
|
401
489
|
? NON_MAINTAINER_CURRENT_SHAPE_MESSAGE
|
|
402
490
|
: NO_CURRENT_SHAPE_MESSAGE,
|
|
403
491
|
kind: "not-found",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C1 declared deposit closure (#3601 / #3899).
|
|
3
|
+
*
|
|
4
|
+
* Required consumer-deposit paths are a closed typed declaration, not a
|
|
5
|
+
* regex over AGENTS.md RFC2119 prose. Existence checks reuse existsSync
|
|
6
|
+
* the same way validate-links.ts does, against a staged pack root (the
|
|
7
|
+
* prepack flatten), not the source checkout.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEPOSIT_REQUIRED_SCHEMA: "deft.deposit-required-paths.v1";
|
|
10
|
+
export declare const DEPOSIT_REQUIRED_REL = "contracts/deposit-required-paths.json";
|
|
11
|
+
export declare const DEPOSIT_PREFIX = ".deft/core/";
|
|
12
|
+
export interface DepositRequiredDeclaration {
|
|
13
|
+
readonly schema: typeof DEPOSIT_REQUIRED_SCHEMA;
|
|
14
|
+
readonly paths: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface DepositClosureResult {
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
readonly missing: readonly string[];
|
|
19
|
+
readonly checked: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function extractDepositRequiredComments(source: string): string[];
|
|
22
|
+
export declare function parseDepositRequiredDeclaration(jsonText: string): DepositRequiredDeclaration;
|
|
23
|
+
export declare function packRelativeFromDepositPath(declared: string): string;
|
|
24
|
+
/** Prepack mapping used by @deftai/directive-content. */
|
|
25
|
+
export declare function sourcePathForPackRelative(repoRoot: string, packRelative: string): string;
|
|
26
|
+
export declare function evaluateDepositClosure(options: {
|
|
27
|
+
readonly packRoot: string;
|
|
28
|
+
readonly paths: readonly string[];
|
|
29
|
+
}): DepositClosureResult;
|
|
30
|
+
export declare function loadDepositRequiredDeclaration(filePath: string): DepositRequiredDeclaration;
|
|
31
|
+
export declare function resolveDeclarationFile(root: string): string | null;
|
|
32
|
+
export declare function evaluateInstalledDepositClosure(projectRoot: string): {
|
|
33
|
+
readonly skipped: boolean;
|
|
34
|
+
readonly missing: readonly string[];
|
|
35
|
+
readonly declarationPath: string | null;
|
|
36
|
+
readonly error: string | null;
|
|
37
|
+
};
|
|
38
|
+
export declare function renderDeclaredDepositClosureLine(result: ReturnType<typeof evaluateInstalledDepositClosure>): string;
|
|
39
|
+
//# sourceMappingURL=deposit-required.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* C1 declared deposit closure (#3601 / #3899).
|
|
5
|
+
*
|
|
6
|
+
* Required consumer-deposit paths are a closed typed declaration, not a
|
|
7
|
+
* regex over AGENTS.md RFC2119 prose. Existence checks reuse existsSync
|
|
8
|
+
* the same way validate-links.ts does, against a staged pack root (the
|
|
9
|
+
* prepack flatten), not the source checkout.
|
|
10
|
+
*/
|
|
11
|
+
export const DEPOSIT_REQUIRED_SCHEMA = "deft.deposit-required-paths.v1";
|
|
12
|
+
export const DEPOSIT_REQUIRED_REL = "contracts/deposit-required-paths.json";
|
|
13
|
+
export const DEPOSIT_PREFIX = ".deft/core/";
|
|
14
|
+
/** Parser-visible field. Not a scan of ! / backtick paths. */
|
|
15
|
+
const DEPOSIT_REQUIRED_COMMENT = /<!--\s*deposit-required:\s+(\S+)\s*-->/g;
|
|
16
|
+
export function extractDepositRequiredComments(source) {
|
|
17
|
+
const out = [];
|
|
18
|
+
const re = new RegExp(DEPOSIT_REQUIRED_COMMENT.source, "g");
|
|
19
|
+
let match = re.exec(source);
|
|
20
|
+
while (match !== null) {
|
|
21
|
+
const path = match[1];
|
|
22
|
+
if (path !== undefined) {
|
|
23
|
+
out.push(path);
|
|
24
|
+
}
|
|
25
|
+
match = re.exec(source);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export function parseDepositRequiredDeclaration(jsonText) {
|
|
30
|
+
let _data;
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(jsonText);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
37
|
+
throw new Error(`deposit-required: invalid JSON (${reason})`);
|
|
38
|
+
}
|
|
39
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
40
|
+
throw new Error("deposit-required: expected an object");
|
|
41
|
+
}
|
|
42
|
+
const data = parsed;
|
|
43
|
+
if (data.schema !== DEPOSIT_REQUIRED_SCHEMA) {
|
|
44
|
+
throw new Error("deposit-required: expected schema " +
|
|
45
|
+
DEPOSIT_REQUIRED_SCHEMA +
|
|
46
|
+
", got " +
|
|
47
|
+
String(data.schema));
|
|
48
|
+
}
|
|
49
|
+
if (!Array.isArray(data.paths) || data.paths.length === 0) {
|
|
50
|
+
throw new Error("deposit-required: paths must be a non-empty array of strings");
|
|
51
|
+
}
|
|
52
|
+
const paths = [];
|
|
53
|
+
for (const item of data.paths) {
|
|
54
|
+
if (typeof item !== "string" ||
|
|
55
|
+
!item.startsWith(DEPOSIT_PREFIX) ||
|
|
56
|
+
item.includes("\\") ||
|
|
57
|
+
item.includes("..")) {
|
|
58
|
+
throw new Error(`deposit-required: invalid path ${String(item)}`);
|
|
59
|
+
}
|
|
60
|
+
paths.push(item);
|
|
61
|
+
}
|
|
62
|
+
return { schema: DEPOSIT_REQUIRED_SCHEMA, paths };
|
|
63
|
+
}
|
|
64
|
+
export function packRelativeFromDepositPath(declared) {
|
|
65
|
+
if (!declared.startsWith(DEPOSIT_PREFIX)) {
|
|
66
|
+
throw new Error(`deposit-required: not a deposit path: ${declared}`);
|
|
67
|
+
}
|
|
68
|
+
return declared.slice(DEPOSIT_PREFIX.length);
|
|
69
|
+
}
|
|
70
|
+
/** Prepack mapping used by @deftai/directive-content. */
|
|
71
|
+
export function sourcePathForPackRelative(repoRoot, packRelative) {
|
|
72
|
+
const rel = packRelative.replace(/\\/g, "/");
|
|
73
|
+
if (rel === "main.md" ||
|
|
74
|
+
rel === "SKILL.md" ||
|
|
75
|
+
rel === "Taskfile.yml" ||
|
|
76
|
+
rel.startsWith("tasks/") ||
|
|
77
|
+
rel.startsWith(".githooks/")) {
|
|
78
|
+
return join(repoRoot, ...rel.split("/"));
|
|
79
|
+
}
|
|
80
|
+
return join(repoRoot, "content", ...rel.split("/"));
|
|
81
|
+
}
|
|
82
|
+
export function evaluateDepositClosure(options) {
|
|
83
|
+
const missing = [];
|
|
84
|
+
for (const declared of options.paths) {
|
|
85
|
+
const rel = packRelativeFromDepositPath(declared);
|
|
86
|
+
const target = join(options.packRoot, ...rel.split("/"));
|
|
87
|
+
if (!existsSync(target)) {
|
|
88
|
+
missing.push(declared);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { ok: missing.length === 0, missing, checked: options.paths.length };
|
|
92
|
+
}
|
|
93
|
+
export function loadDepositRequiredDeclaration(filePath) {
|
|
94
|
+
return parseDepositRequiredDeclaration(readFileSync(filePath, "utf8"));
|
|
95
|
+
}
|
|
96
|
+
export function resolveDeclarationFile(root) {
|
|
97
|
+
const underContent = join(root, "content", DEPOSIT_REQUIRED_REL);
|
|
98
|
+
if (existsSync(underContent)) {
|
|
99
|
+
return underContent;
|
|
100
|
+
}
|
|
101
|
+
const underRoot = join(root, DEPOSIT_REQUIRED_REL);
|
|
102
|
+
if (existsSync(underRoot)) {
|
|
103
|
+
return underRoot;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
export function evaluateInstalledDepositClosure(projectRoot) {
|
|
108
|
+
const deftDir = join(projectRoot, ".deft", "core");
|
|
109
|
+
const declarationPath = resolveDeclarationFile(deftDir) ?? resolveDeclarationFile(projectRoot);
|
|
110
|
+
if (declarationPath === null) {
|
|
111
|
+
return { skipped: true, missing: [], declarationPath: null, error: null };
|
|
112
|
+
}
|
|
113
|
+
if (!existsSync(deftDir)) {
|
|
114
|
+
return { skipped: true, missing: [], declarationPath, error: null };
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const declaration = loadDepositRequiredDeclaration(declarationPath);
|
|
118
|
+
const result = evaluateDepositClosure({ packRoot: deftDir, paths: declaration.paths });
|
|
119
|
+
return { skipped: false, missing: result.missing, declarationPath, error: null };
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
123
|
+
return { skipped: false, missing: [], declarationPath, error: reason };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function renderDeclaredDepositClosureLine(result) {
|
|
127
|
+
if (result.skipped) {
|
|
128
|
+
return "Deposit required-paths: skip -- no C1 declaration in this tree.";
|
|
129
|
+
}
|
|
130
|
+
if (result.error) {
|
|
131
|
+
return ("Deposit required-paths: fail -- could not read C1 declaration (" +
|
|
132
|
+
result.error +
|
|
133
|
+
"). Run directive update to refresh the deposit (#3601 C1).");
|
|
134
|
+
}
|
|
135
|
+
if (result.missing.length === 0) {
|
|
136
|
+
return "Deposit required-paths: ok -- declared C1 paths exist in .deft/core.";
|
|
137
|
+
}
|
|
138
|
+
const sample = result.missing.slice(0, 5).join(", ");
|
|
139
|
+
const extra = result.missing.length > 5 ? ` (+${String(result.missing.length - 5)} more)` : "";
|
|
140
|
+
return ("Deposit required-paths: fail -- " +
|
|
141
|
+
String(result.missing.length) +
|
|
142
|
+
" declared path(s) missing from .deft/core. Examples: " +
|
|
143
|
+
sample +
|
|
144
|
+
extra +
|
|
145
|
+
". Run directive update to refresh the deposit (#3601 C1).");
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=deposit-required.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { resolveCapacityAllocation } from "./capacity-policy.js";
|
|
2
2
|
export { computeReport, renderReport } from "./capacity-show.js";
|
|
3
|
+
export * as depositRequired from "./deposit-required.js";
|
|
3
4
|
export { isDatePrefixedVbriefFilename } from "./filename.js";
|
|
4
5
|
export { extractLinkTargets, shouldSkipLinkTarget } from "./link-parser.js";
|
|
5
6
|
export * from "./types.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { resolveCapacityAllocation } from "./capacity-policy.js";
|
|
2
2
|
export { computeReport, renderReport } from "./capacity-show.js";
|
|
3
|
+
export * as depositRequired from "./deposit-required.js";
|
|
3
4
|
export { isDatePrefixedVbriefFilename } from "./filename.js";
|
|
4
5
|
export { extractLinkTargets, shouldSkipLinkTarget } from "./link-parser.js";
|
|
5
6
|
export * from "./types.js";
|
|
@@ -18,8 +18,7 @@ export interface ValidateLinksOptions {
|
|
|
18
18
|
readonly linkCheckStrict?: boolean;
|
|
19
19
|
readonly argv?: readonly string[];
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
*/
|
|
21
|
+
/** Collect broken internal markdown links under cwd. Used by packed-fixture tests (#3937). */
|
|
22
|
+
export declare function collectBrokenLinks(cwd: string): BrokenLink[];
|
|
24
23
|
export declare function evaluate(options?: ValidateLinksOptions): EvaluateResult;
|
|
25
24
|
//# sourceMappingURL=validate-links.d.ts.map
|