@deftai/directive-runtime 0.126.0-beta.2 → 0.126.0-beta.3
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/UPGRADING.md +4 -3
- package/commands.md +9 -6
- package/contracts/design-critique.md +39 -1
- package/dist/doctor-diagnose.d.ts +1 -1
- package/dist/doctor-diagnose.js +29 -3
- package/dist/doctor.js +1 -1
- package/dist/session-start.js +28 -1
- package/dist/verify-review-monitor.d.ts +7 -0
- package/dist/verify-review-monitor.js +21 -0
- package/docs/getting-started.md +13 -1
- package/package.json +1 -1
- package/packs/skills/skills-pack-0.1.json +4 -4
- package/packs/strategies/strategies-pack-0.1.json +32 -19
- package/skills/deft-directive-article-review/SKILL.md +104 -24
- package/skills/deft-directive-design-critique/SKILL.md +10 -9
- package/skills/deft-directive-feedback/SKILL.md +20 -9
- package/skills/deft-directive-pre-pr/SKILL.md +14 -0
- package/skills/deft-directive-review-cycle/SKILL.md +18 -1
- package/skills/deft-directive-setup/SKILL.md +41 -11
- package/skills/deft-directive-swarm/references/host-grok-build.md +1 -1
- package/skills/deft-directive-tutorial/SKILL.md +1 -1
- package/strategies/README.md +1 -0
- package/strategies/artifact-guards.md +2 -2
- package/strategies/design-thinking.md +182 -0
- package/strategies/discuss.md +2 -0
- package/strategies/interview.md +76 -23
- package/strategies/rapid.md +22 -12
- package/templates/agents-entry.md +4 -3
- package/templates/design-critique-brief.md +5 -1
- package/tutorial/beats.json +2 -2
- package/vendor/directive-content/UPGRADING.md +4 -3
- package/vendor/directive-content/commands.md +9 -6
- package/vendor/directive-content/contracts/design-critique.md +39 -1
- package/vendor/directive-content/docs/getting-started.md +13 -1
- package/vendor/directive-content/packs/skills/skills-pack-0.1.json +4 -4
- package/vendor/directive-content/packs/strategies/strategies-pack-0.1.json +32 -19
- package/vendor/directive-content/skills/deft-directive-article-review/SKILL.md +104 -24
- package/vendor/directive-content/skills/deft-directive-design-critique/SKILL.md +10 -9
- package/vendor/directive-content/skills/deft-directive-feedback/SKILL.md +20 -9
- package/vendor/directive-content/skills/deft-directive-pre-pr/SKILL.md +14 -0
- package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +18 -1
- package/vendor/directive-content/skills/deft-directive-setup/SKILL.md +41 -11
- package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +1 -1
- package/vendor/directive-content/skills/deft-directive-tutorial/SKILL.md +1 -1
- package/vendor/directive-content/strategies/README.md +1 -0
- package/vendor/directive-content/strategies/artifact-guards.md +2 -2
- package/vendor/directive-content/strategies/design-thinking.md +182 -0
- package/vendor/directive-content/strategies/discuss.md +2 -0
- package/vendor/directive-content/strategies/interview.md +76 -23
- package/vendor/directive-content/strategies/rapid.md +22 -12
- package/vendor/directive-content/templates/agents-entry.md +4 -3
- package/vendor/directive-content/templates/design-critique-brief.md +5 -1
- package/vendor/directive-content/tutorial/beats.json +2 -2
- package/vendor/directive-core/dist/.tsbuildinfo +1 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.d.ts +11 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.d.ts.map +1 -1
- package/vendor/directive-core/dist/content-contracts/skills/helpers.js +18 -5
- package/vendor/directive-core/dist/content-contracts/skills/helpers.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.d.ts +40 -0
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.js +116 -0
- package/vendor/directive-core/dist/design-critique/auto-stamp-path1.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/citation-grammar.d.ts +36 -0
- package/vendor/directive-core/dist/design-critique/citation-grammar.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/citation-grammar.js +60 -3
- package/vendor/directive-core/dist/design-critique/citation-grammar.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/completed-arc-record.d.ts +103 -3
- package/vendor/directive-core/dist/design-critique/completed-arc-record.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/completed-arc-record.js +127 -5
- package/vendor/directive-core/dist/design-critique/completed-arc-record.js.map +1 -1
- package/vendor/directive-core/dist/design-critique/ingest-posture.d.ts +27 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.d.ts.map +1 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.js +66 -0
- package/vendor/directive-core/dist/design-critique/ingest-posture.js.map +1 -0
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.d.ts +106 -7
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.d.ts.map +1 -1
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.js +284 -17
- package/vendor/directive-core/dist/design-critique/panel-handback-acceptance.js.map +1 -1
- package/vendor/directive-core/dist/docs/docs-impact.d.ts +13 -0
- package/vendor/directive-core/dist/docs/docs-impact.d.ts.map +1 -1
- package/vendor/directive-core/dist/docs/docs-impact.js +49 -4
- package/vendor/directive-core/dist/docs/docs-impact.js.map +1 -1
- package/vendor/directive-core/dist/doctor/main.d.ts +48 -0
- package/vendor/directive-core/dist/doctor/main.d.ts.map +1 -1
- package/vendor/directive-core/dist/doctor/main.js +221 -4
- package/vendor/directive-core/dist/doctor/main.js.map +1 -1
- package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
- package/vendor/directive-core/dist/hooks/dispatcher.js +151 -13
- package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
- package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +4 -0
- package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
- package/vendor/directive-core/dist/init-deposit/agent-hooks.js +25 -4
- package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
- package/vendor/directive-core/dist/intake/issue-ingest.d.ts +2 -0
- package/vendor/directive-core/dist/intake/issue-ingest.d.ts.map +1 -1
- package/vendor/directive-core/dist/intake/issue-ingest.js +26 -1
- package/vendor/directive-core/dist/intake/issue-ingest.js.map +1 -1
- package/vendor/directive-core/dist/pr-closing-keywords/detect.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-closing-keywords/detect.js +52 -7
- package/vendor/directive-core/dist/pr-closing-keywords/detect.js.map +1 -1
- package/vendor/directive-core/dist/pr-watch/main.d.ts +5 -0
- package/vendor/directive-core/dist/pr-watch/main.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-watch/main.js.map +1 -1
- package/vendor/directive-core/dist/pr-watch/watch.d.ts.map +1 -1
- package/vendor/directive-core/dist/pr-watch/watch.js +5 -0
- package/vendor/directive-core/dist/pr-watch/watch.js.map +1 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.d.ts.map +1 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.js +3 -1
- package/vendor/directive-core/dist/presentation-ceiling/evaluate.js.map +1 -1
- package/vendor/directive-core/dist/release/auto-hatch.d.ts +6 -0
- package/vendor/directive-core/dist/release/auto-hatch.d.ts.map +1 -1
- package/vendor/directive-core/dist/release/auto-hatch.js +13 -4
- package/vendor/directive-core/dist/release/auto-hatch.js.map +1 -1
- package/vendor/directive-core/dist/review-monitor/record.d.ts +11 -0
- package/vendor/directive-core/dist/review-monitor/record.d.ts.map +1 -1
- package/vendor/directive-core/dist/review-monitor/record.js +23 -0
- package/vendor/directive-core/dist/review-monitor/record.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/emit.d.ts +2 -1
- package/vendor/directive-core/dist/run-summary/emit.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/emit.js +3 -0
- package/vendor/directive-core/dist/run-summary/emit.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/index.d.ts +1 -1
- package/vendor/directive-core/dist/run-summary/index.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/index.js.map +1 -1
- package/vendor/directive-core/dist/run-summary/types.d.ts +8 -2
- package/vendor/directive-core/dist/run-summary/types.d.ts.map +1 -1
- package/vendor/directive-core/dist/run-summary/types.js +1 -0
- package/vendor/directive-core/dist/run-summary/types.js.map +1 -1
- package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +11 -0
- package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
- package/vendor/directive-core/dist/scope/delivery-evidence.js +34 -0
- package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
- package/vendor/directive-core/dist/session/occupancy.d.ts +101 -0
- package/vendor/directive-core/dist/session/occupancy.d.ts.map +1 -1
- package/vendor/directive-core/dist/session/occupancy.js +276 -1
- package/vendor/directive-core/dist/session/occupancy.js.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.d.ts.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.js +5 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors-cli.js.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.d.ts +71 -0
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.d.ts.map +1 -1
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.js +125 -2
- package/vendor/directive-core/dist/swarm/cohort-review-monitors.js.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.d.ts.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.js +9 -0
- package/vendor/directive-core/dist/telemetry-coverage/fake-trial.js.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/kinds.d.ts.map +1 -1
- package/vendor/directive-core/dist/telemetry-coverage/kinds.js +2 -0
- package/vendor/directive-core/dist/telemetry-coverage/kinds.js.map +1 -1
- package/vendor/directive-core/dist/value/feedback-file.d.ts +49 -6
- package/vendor/directive-core/dist/value/feedback-file.d.ts.map +1 -1
- package/vendor/directive-core/dist/value/feedback-file.js +446 -41
- package/vendor/directive-core/dist/value/feedback-file.js.map +1 -1
|
@@ -3,12 +3,14 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { isFrameworkRepoRoot } from "../check/orchestrator.js";
|
|
4
4
|
import { policyColonInvocation } from "../policy/policy-invocation.js";
|
|
5
5
|
import { isValueFeedbackPathAllowed, resolveValueFeedback } from "../policy/value-feedback.js";
|
|
6
|
-
import { GhRestError, restCreateIssue,
|
|
6
|
+
import { GhRestError, REST_MAX_PER_PAGE, REST_PAGINATION_MAX_PAGES, REST_SHARED_ROW_BUDGET, restCreateIssue, restIssueListOpenInventory, restPostComment, runGhApi, splitRepo, } from "../scm/gh-rest.js";
|
|
7
7
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
8
8
|
export const DEFAULT_UPSTREAM_REPO = "deftai/directive";
|
|
9
9
|
export const FRAMEWORK_GAP_TITLE_PREFIX = "[framework-gap]";
|
|
10
10
|
/** Sole sanctioned title classification (#3713). Does not write `adoption-blocker`. */
|
|
11
11
|
export const ADOPTION_BLOCKER_TITLE_TOKEN = "BLOCKER";
|
|
12
|
+
/** Retained closed-issue count window for feedback:file dedup (#5099 Prefer-A Recut-2). */
|
|
13
|
+
export const FEEDBACK_CLOSED_DEDUP_WINDOW = Number.parseInt("500", 10);
|
|
12
14
|
const FRAMEWORK_GAP_PREFIX_RE = /^\[framework-gap\]\s*/i;
|
|
13
15
|
const BLOCKER_TITLE_TOKEN_RE = /^BLOCKER\b[:\s-]*/i;
|
|
14
16
|
/** Strip source prefix and the optional BLOCKER token so marked/unmarked titles dedup. */
|
|
@@ -99,9 +101,18 @@ function buildAdoptionImpactSection(input) {
|
|
|
99
101
|
"",
|
|
100
102
|
];
|
|
101
103
|
}
|
|
102
|
-
/** Build the structured framework-gap issue body. */
|
|
103
|
-
export function buildFrameworkGapBody(input) {
|
|
104
|
+
/** Build the structured framework-gap issue or evidence-comment body. */
|
|
105
|
+
export function buildFrameworkGapBody(input, options) {
|
|
104
106
|
const summary = sanitizeOneLine(input.summary);
|
|
107
|
+
const mode = options?.mode ?? "issue";
|
|
108
|
+
const footer = mode === "comment"
|
|
109
|
+
? "_Evidence appended via `deft feedback:file` from a directive consumer project (Refs #1709 value-feedback gap escalation)._"
|
|
110
|
+
: "_Filed via `deft feedback:file` from a directive consumer project (Refs #1709 value-feedback gap escalation)._";
|
|
111
|
+
const related = typeof options?.relatedClosedNumber === "number" &&
|
|
112
|
+
Number.isFinite(options.relatedClosedNumber) &&
|
|
113
|
+
options.relatedClosedNumber > 0
|
|
114
|
+
? [`Related: closed #${options.relatedClosedNumber}`, ""]
|
|
115
|
+
: [];
|
|
105
116
|
const lines = [
|
|
106
117
|
"## Summary",
|
|
107
118
|
"",
|
|
@@ -124,29 +135,213 @@ export function buildFrameworkGapBody(input) {
|
|
|
124
135
|
sectionOrPlaceholder(input.sessionNotes, "_(not provided)_"),
|
|
125
136
|
"",
|
|
126
137
|
...buildAdoptionImpactSection(input),
|
|
138
|
+
...related,
|
|
127
139
|
"---",
|
|
128
|
-
|
|
140
|
+
footer,
|
|
129
141
|
"",
|
|
130
142
|
];
|
|
131
143
|
return lines.join("\n");
|
|
132
144
|
}
|
|
133
|
-
|
|
145
|
+
function matchTitleRow(row, needle) {
|
|
146
|
+
const issueTitle = typeof row.title === "string" ? row.title : "";
|
|
147
|
+
if (normalizeForDedup(issueTitle) !== needle) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const url = typeof row.html_url === "string" ? row.html_url : "";
|
|
151
|
+
const state = typeof row.state === "string" ? row.state : "";
|
|
152
|
+
const number = row.number;
|
|
153
|
+
if (typeof number !== "number" ||
|
|
154
|
+
!Number.isFinite(number) ||
|
|
155
|
+
number <= 0 ||
|
|
156
|
+
!Number.isInteger(number) ||
|
|
157
|
+
url.length === 0 ||
|
|
158
|
+
state.length === 0) {
|
|
159
|
+
return "malformed";
|
|
160
|
+
}
|
|
161
|
+
return { number, state, url, title: issueTitle };
|
|
162
|
+
}
|
|
163
|
+
function fetchClosedIssuePage(repo, page, perPage, seams) {
|
|
164
|
+
const [owner, name] = splitRepo(repo);
|
|
165
|
+
const endpoint = `repos/${owner}/${name}/issues`;
|
|
166
|
+
const runner = seams.runGhApiFn ?? runGhApi;
|
|
167
|
+
const args = [
|
|
168
|
+
endpoint,
|
|
169
|
+
"--method",
|
|
170
|
+
"GET",
|
|
171
|
+
"--raw-field",
|
|
172
|
+
"state=closed",
|
|
173
|
+
"--raw-field",
|
|
174
|
+
"sort=updated",
|
|
175
|
+
"--raw-field",
|
|
176
|
+
"direction=desc",
|
|
177
|
+
"--raw-field",
|
|
178
|
+
`per_page=${perPage}`,
|
|
179
|
+
"--raw-field",
|
|
180
|
+
`page=${page}`,
|
|
181
|
+
];
|
|
182
|
+
const result = runner(args, { whichFn: seams.whichFn });
|
|
183
|
+
if (result.returncode !== 0) {
|
|
184
|
+
const detail = result.stderr.trim();
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
message: detail.length > 0
|
|
188
|
+
? detail
|
|
189
|
+
: `closed-issue walk gh exit ${String(result.returncode)} (${endpoint})`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const stdout = result.stdout.trim();
|
|
193
|
+
if (stdout.length === 0) {
|
|
194
|
+
return { ok: true, rows: [] };
|
|
195
|
+
}
|
|
196
|
+
let parsed;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(stdout);
|
|
199
|
+
}
|
|
200
|
+
catch (exc) {
|
|
201
|
+
const message = exc instanceof Error ? exc.message : String(exc);
|
|
202
|
+
return { ok: false, message: `non-JSON closed-issue page: ${message}` };
|
|
203
|
+
}
|
|
204
|
+
if (!Array.isArray(parsed)) {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
message: `unexpected top-level type ${typeof parsed}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return { ok: true, rows: parsed };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Closed-issue sibling of `restWalkClosedPullsUpdatedDesc` for feedback:file dedup (#5099).
|
|
214
|
+
* Retained-count window; pull rows consume raw budget but are excluded from match/count.
|
|
215
|
+
*/
|
|
216
|
+
export function walkClosedIssuesForFeedbackDedup(repo, title, options = {}, seams = {}) {
|
|
217
|
+
const needle = normalizeForDedup(title);
|
|
218
|
+
if (needle.length === 0) {
|
|
219
|
+
return {
|
|
220
|
+
outcome: "list-exhausted",
|
|
221
|
+
inspectedIssueCount: 0,
|
|
222
|
+
rawRows: 0,
|
|
223
|
+
match: null,
|
|
224
|
+
malformedMatch: false,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const window = Math.max(1, options.window ?? FEEDBACK_CLOSED_DEDUP_WINDOW);
|
|
228
|
+
const cappedPerPage = Math.min(Math.max(1, options.perPage ?? REST_MAX_PER_PAGE), REST_MAX_PER_PAGE);
|
|
229
|
+
const budget = Math.max(0, options.remainingBudget ?? REST_SHARED_ROW_BUDGET);
|
|
230
|
+
let rawRows = 0;
|
|
231
|
+
let inspectedIssueCount = 0;
|
|
232
|
+
for (let page = 1; page <= REST_PAGINATION_MAX_PAGES; page += 1) {
|
|
233
|
+
if (rawRows >= budget) {
|
|
234
|
+
return {
|
|
235
|
+
outcome: "cap",
|
|
236
|
+
inspectedIssueCount,
|
|
237
|
+
rawRows,
|
|
238
|
+
match: null,
|
|
239
|
+
malformedMatch: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const pageResult = fetchClosedIssuePage(repo, page, cappedPerPage, seams);
|
|
243
|
+
if (!pageResult.ok) {
|
|
244
|
+
return {
|
|
245
|
+
outcome: "error",
|
|
246
|
+
inspectedIssueCount,
|
|
247
|
+
rawRows,
|
|
248
|
+
match: null,
|
|
249
|
+
malformedMatch: false,
|
|
250
|
+
errorMessage: pageResult.message,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const pagePayload = pageResult.rows;
|
|
254
|
+
if (pagePayload.length === 0) {
|
|
255
|
+
return {
|
|
256
|
+
outcome: "list-exhausted",
|
|
257
|
+
inspectedIssueCount,
|
|
258
|
+
rawRows,
|
|
259
|
+
match: null,
|
|
260
|
+
malformedMatch: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
for (const item of pagePayload) {
|
|
264
|
+
if (rawRows >= budget) {
|
|
265
|
+
return {
|
|
266
|
+
outcome: "cap",
|
|
267
|
+
inspectedIssueCount,
|
|
268
|
+
rawRows,
|
|
269
|
+
match: null,
|
|
270
|
+
malformedMatch: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
rawRows += 1;
|
|
274
|
+
if ("pull_request" in item) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
inspectedIssueCount += 1;
|
|
278
|
+
const matched = matchTitleRow(item, needle);
|
|
279
|
+
if (matched === "malformed") {
|
|
280
|
+
return {
|
|
281
|
+
outcome: "error",
|
|
282
|
+
inspectedIssueCount,
|
|
283
|
+
rawRows,
|
|
284
|
+
match: null,
|
|
285
|
+
malformedMatch: true,
|
|
286
|
+
errorMessage: "closed-issue title match lacks usable numeric number/state/url",
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (matched !== null) {
|
|
290
|
+
return {
|
|
291
|
+
outcome: "list-exhausted",
|
|
292
|
+
inspectedIssueCount,
|
|
293
|
+
rawRows,
|
|
294
|
+
match: matched,
|
|
295
|
+
malformedMatch: false,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (inspectedIssueCount >= window) {
|
|
299
|
+
return {
|
|
300
|
+
outcome: "window-exhausted",
|
|
301
|
+
inspectedIssueCount,
|
|
302
|
+
rawRows,
|
|
303
|
+
match: null,
|
|
304
|
+
malformedMatch: false,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (pagePayload.length < cappedPerPage) {
|
|
309
|
+
return {
|
|
310
|
+
outcome: "list-exhausted",
|
|
311
|
+
inspectedIssueCount,
|
|
312
|
+
rawRows,
|
|
313
|
+
match: null,
|
|
314
|
+
malformedMatch: false,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
outcome: "cap",
|
|
320
|
+
inspectedIssueCount,
|
|
321
|
+
rawRows,
|
|
322
|
+
match: null,
|
|
323
|
+
malformedMatch: false,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
134
326
|
export function findDuplicateIssue(repo, title, seams = {}) {
|
|
135
327
|
const needle = normalizeForDedup(title);
|
|
136
328
|
if (needle.length === 0) {
|
|
137
|
-
return null;
|
|
329
|
+
return { ok: true, match: null };
|
|
138
330
|
}
|
|
139
|
-
const issues =
|
|
331
|
+
const issues = restIssueListOpenInventory(repo, seams);
|
|
140
332
|
for (const issue of issues) {
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
333
|
+
const matched = matchTitleRow(issue, needle);
|
|
334
|
+
if (matched === "malformed") {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: "open-issue title match lacks usable numeric number/state/url",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (matched !== null) {
|
|
341
|
+
return { ok: true, match: matched };
|
|
147
342
|
}
|
|
148
343
|
}
|
|
149
|
-
return null;
|
|
344
|
+
return { ok: true, match: null };
|
|
150
345
|
}
|
|
151
346
|
function resolveRepo(options) {
|
|
152
347
|
const explicit = options.repo?.trim();
|
|
@@ -165,7 +360,40 @@ function buildConfirmationPrompt(title, body) {
|
|
|
165
360
|
`${body}\n` +
|
|
166
361
|
"Re-run with --confirm to file this issue upstream after reviewing the draft.\n");
|
|
167
362
|
}
|
|
168
|
-
|
|
363
|
+
function buildOpenMatchPreview(match, commentBody) {
|
|
364
|
+
return (`[deft feedback] Open duplicate: ${match.url} ("${match.title}").\n` +
|
|
365
|
+
"Proposed evidence comment (not posted):\n\n" +
|
|
366
|
+
`${commentBody}\n` +
|
|
367
|
+
"Re-run with --confirm to append this evidence as a comment (never creates a new issue).\n");
|
|
368
|
+
}
|
|
369
|
+
function buildClosedMatchBlock(match) {
|
|
370
|
+
return (`[deft feedback] Closed duplicate: ${match.url} ("${match.title}").\n` +
|
|
371
|
+
"Re-run with an explicit disposition before any write:\n" +
|
|
372
|
+
" --on-closed=comment append evidence on the closed issue (no reopen)\n" +
|
|
373
|
+
" --on-closed=create-anyway create a new issue with Related: closed #<n>\n");
|
|
374
|
+
}
|
|
375
|
+
function incompleteClosedSearchNote(stopReason, inspectedIssueCount, boundary) {
|
|
376
|
+
if (stopReason === "window-exhausted") {
|
|
377
|
+
return (`[deft feedback] Note: closed-issue search incomplete ` +
|
|
378
|
+
`(stopReason=window-exhausted, inspectedIssueCount=${inspectedIssueCount}, window=${boundary}).\n`);
|
|
379
|
+
}
|
|
380
|
+
return (`[deft feedback] Note: closed-issue search incomplete ` +
|
|
381
|
+
`(stopReason=cap, inspectedIssueCount=${inspectedIssueCount}, budget=${boundary}).\n`);
|
|
382
|
+
}
|
|
383
|
+
function extractCommentUrl(repo, issueNumber, created) {
|
|
384
|
+
if (typeof created.html_url === "string" && created.html_url.length > 0) {
|
|
385
|
+
return created.html_url;
|
|
386
|
+
}
|
|
387
|
+
const id = created.id;
|
|
388
|
+
if (typeof id === "number" && Number.isFinite(id)) {
|
|
389
|
+
return `https://github.com/${repo}/issues/${issueNumber}#issuecomment-${id}`;
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function mutationsAllowed(options, offline, dryRun) {
|
|
394
|
+
return options.confirm === true && !dryRun && !offline;
|
|
395
|
+
}
|
|
396
|
+
/** Core feedback:file workflow (#1709 / #5099 Prefer-A Recut-2). */
|
|
169
397
|
export function runFeedbackFile(options) {
|
|
170
398
|
const summary = sanitizeOneLine(options.summary);
|
|
171
399
|
if (summary.length === 0) {
|
|
@@ -192,13 +420,14 @@ export function runFeedbackFile(options) {
|
|
|
192
420
|
const projectRoot = resolve(projectRootRaw);
|
|
193
421
|
const repo = resolveRepo(options);
|
|
194
422
|
const title = buildFrameworkGapTitle(summary, { adoptionBlocker: options.adoptionBlocker });
|
|
195
|
-
const
|
|
423
|
+
const issueBody = buildFrameworkGapBody(options);
|
|
424
|
+
const commentBody = buildFrameworkGapBody(options, { mode: "comment" });
|
|
196
425
|
if (isMaintainerFrameworkRepo(projectRoot)) {
|
|
197
426
|
return {
|
|
198
427
|
outcome: "skipped-maintainer",
|
|
199
428
|
exitCode: 0,
|
|
200
429
|
title,
|
|
201
|
-
body,
|
|
430
|
+
body: issueBody,
|
|
202
431
|
repo,
|
|
203
432
|
message: "[deft feedback] Skipped: maintainer framework repo detected; gap escalation targets consumer projects only.\n",
|
|
204
433
|
};
|
|
@@ -206,13 +435,13 @@ export function runFeedbackFile(options) {
|
|
|
206
435
|
const policy = resolveValueFeedback(projectRoot);
|
|
207
436
|
if (!isValueFeedbackPathAllowed("upstreamPrompt", policy)) {
|
|
208
437
|
const readout = options.dryRun === true || options.json === true
|
|
209
|
-
? `${buildConfirmationPrompt(title,
|
|
438
|
+
? `${buildConfirmationPrompt(title, issueBody)}\n`
|
|
210
439
|
: "";
|
|
211
440
|
return {
|
|
212
441
|
outcome: "blocked-policy",
|
|
213
442
|
exitCode: 1,
|
|
214
443
|
title,
|
|
215
|
-
body,
|
|
444
|
+
body: issueBody,
|
|
216
445
|
repo,
|
|
217
446
|
message: "[deft feedback] Blocked: plan.policy.valueFeedback upstreamPrompt is OFF. " +
|
|
218
447
|
`Enable with \`${policyColonInvocation("enable-value-feedback", " -- --confirm --upstream-prompt")}\`, ` +
|
|
@@ -224,32 +453,185 @@ export function runFeedbackFile(options) {
|
|
|
224
453
|
const dryRun = options.dryRun ?? false;
|
|
225
454
|
const seams = options.seams ?? {};
|
|
226
455
|
let dedupSkippedNote = "";
|
|
456
|
+
let incompleteNote = "";
|
|
457
|
+
let createBody = issueBody;
|
|
227
458
|
if (!offline) {
|
|
228
459
|
try {
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
460
|
+
const openSearch = findDuplicateIssue(repo, title, seams);
|
|
461
|
+
if (!openSearch.ok) {
|
|
231
462
|
return {
|
|
232
|
-
outcome: "
|
|
233
|
-
exitCode:
|
|
463
|
+
outcome: "error-network",
|
|
464
|
+
exitCode: 2,
|
|
234
465
|
title,
|
|
235
|
-
body,
|
|
466
|
+
body: issueBody,
|
|
236
467
|
repo,
|
|
237
|
-
|
|
238
|
-
message: `[deft feedback] Duplicate detected: open issue ${duplicate.url} ` +
|
|
239
|
-
`("${duplicate.title}") matches this report.\n`,
|
|
468
|
+
message: networkErrorMessage("Open-issue inventory / duplicate search failed", openSearch.error),
|
|
240
469
|
};
|
|
241
470
|
}
|
|
471
|
+
const openMatch = openSearch.match;
|
|
472
|
+
if (openMatch !== null) {
|
|
473
|
+
if (!options.confirm) {
|
|
474
|
+
return {
|
|
475
|
+
outcome: "draft",
|
|
476
|
+
exitCode: 1,
|
|
477
|
+
title,
|
|
478
|
+
body: commentBody,
|
|
479
|
+
repo,
|
|
480
|
+
duplicateUrl: openMatch.url,
|
|
481
|
+
issueUrl: openMatch.url,
|
|
482
|
+
message: buildOpenMatchPreview(openMatch, commentBody),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (!mutationsAllowed(options, offline, dryRun)) {
|
|
486
|
+
return {
|
|
487
|
+
outcome: "draft",
|
|
488
|
+
exitCode: 0,
|
|
489
|
+
title,
|
|
490
|
+
body: commentBody,
|
|
491
|
+
repo,
|
|
492
|
+
duplicateUrl: openMatch.url,
|
|
493
|
+
issueUrl: openMatch.url,
|
|
494
|
+
message: `[deft feedback] Dry run: would comment on open duplicate ${openMatch.url}.\n` +
|
|
495
|
+
`${buildOpenMatchPreview(openMatch, commentBody)}`,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
const created = restPostComment(repo, openMatch.number, commentBody, seams);
|
|
500
|
+
const commentUrl = extractCommentUrl(repo, openMatch.number, created);
|
|
501
|
+
return {
|
|
502
|
+
outcome: "commented-open-duplicate",
|
|
503
|
+
exitCode: 0,
|
|
504
|
+
title,
|
|
505
|
+
body: commentBody,
|
|
506
|
+
repo,
|
|
507
|
+
issueUrl: openMatch.url,
|
|
508
|
+
duplicateUrl: openMatch.url,
|
|
509
|
+
commentUrl,
|
|
510
|
+
message: commentUrl !== null
|
|
511
|
+
? `[deft feedback] Commented on open duplicate ${openMatch.url}\nComment: ${commentUrl}\n`
|
|
512
|
+
: `[deft feedback] Commented on open duplicate ${openMatch.url}\n`,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
catch (err) {
|
|
516
|
+
return {
|
|
517
|
+
outcome: "error-network",
|
|
518
|
+
exitCode: 2,
|
|
519
|
+
title,
|
|
520
|
+
body: commentBody,
|
|
521
|
+
repo,
|
|
522
|
+
duplicateUrl: openMatch.url,
|
|
523
|
+
message: networkErrorMessage("Open-duplicate comment failed", err),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
242
527
|
}
|
|
243
528
|
catch (err) {
|
|
244
529
|
return {
|
|
245
530
|
outcome: "error-network",
|
|
246
531
|
exitCode: 2,
|
|
247
532
|
title,
|
|
248
|
-
body,
|
|
533
|
+
body: issueBody,
|
|
249
534
|
repo,
|
|
250
|
-
message: networkErrorMessage("
|
|
535
|
+
message: networkErrorMessage("Open-issue inventory / duplicate search failed", err),
|
|
251
536
|
};
|
|
252
537
|
}
|
|
538
|
+
const closedWalk = walkClosedIssuesForFeedbackDedup(repo, title, {
|
|
539
|
+
window: options.closedWindow,
|
|
540
|
+
remainingBudget: options.closedRowBudget,
|
|
541
|
+
}, seams);
|
|
542
|
+
if (closedWalk.malformedMatch || closedWalk.outcome === "error") {
|
|
543
|
+
return {
|
|
544
|
+
outcome: "error-network",
|
|
545
|
+
exitCode: 2,
|
|
546
|
+
title,
|
|
547
|
+
body: issueBody,
|
|
548
|
+
repo,
|
|
549
|
+
message: networkErrorMessage("Closed-issue search failed", closedWalk.errorMessage ?? "closed walk error"),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
if (closedWalk.match !== null) {
|
|
553
|
+
const closedMatch = closedWalk.match;
|
|
554
|
+
if (options.onClosed === undefined) {
|
|
555
|
+
return {
|
|
556
|
+
outcome: "blocked-closed-duplicate",
|
|
557
|
+
exitCode: 1,
|
|
558
|
+
title,
|
|
559
|
+
body: issueBody,
|
|
560
|
+
repo,
|
|
561
|
+
duplicateUrl: closedMatch.url,
|
|
562
|
+
issueUrl: closedMatch.url,
|
|
563
|
+
message: buildClosedMatchBlock(closedMatch),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (options.onClosed === "comment") {
|
|
567
|
+
if (!options.confirm) {
|
|
568
|
+
return {
|
|
569
|
+
outcome: "draft",
|
|
570
|
+
exitCode: 1,
|
|
571
|
+
title,
|
|
572
|
+
body: commentBody,
|
|
573
|
+
repo,
|
|
574
|
+
duplicateUrl: closedMatch.url,
|
|
575
|
+
issueUrl: closedMatch.url,
|
|
576
|
+
message: buildClosedMatchBlock(closedMatch) +
|
|
577
|
+
"\nProposed evidence comment (not posted):\n\n" +
|
|
578
|
+
`${commentBody}\n` +
|
|
579
|
+
"Re-run with --confirm --on-closed=comment to append evidence (no reopen).\n",
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (!mutationsAllowed(options, offline, dryRun)) {
|
|
583
|
+
return {
|
|
584
|
+
outcome: "draft",
|
|
585
|
+
exitCode: 0,
|
|
586
|
+
title,
|
|
587
|
+
body: commentBody,
|
|
588
|
+
repo,
|
|
589
|
+
duplicateUrl: closedMatch.url,
|
|
590
|
+
issueUrl: closedMatch.url,
|
|
591
|
+
message: `[deft feedback] Dry run: would comment on closed duplicate ${closedMatch.url} (no reopen).\n` +
|
|
592
|
+
`${commentBody}\n`,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const created = restPostComment(repo, closedMatch.number, commentBody, seams);
|
|
597
|
+
const commentUrl = extractCommentUrl(repo, closedMatch.number, created);
|
|
598
|
+
return {
|
|
599
|
+
outcome: "commented-open-duplicate",
|
|
600
|
+
exitCode: 0,
|
|
601
|
+
title,
|
|
602
|
+
body: commentBody,
|
|
603
|
+
repo,
|
|
604
|
+
issueUrl: closedMatch.url,
|
|
605
|
+
duplicateUrl: closedMatch.url,
|
|
606
|
+
commentUrl,
|
|
607
|
+
message: commentUrl !== null
|
|
608
|
+
? `[deft feedback] Commented on closed duplicate ${closedMatch.url} (no reopen)\nComment: ${commentUrl}\n`
|
|
609
|
+
: `[deft feedback] Commented on closed duplicate ${closedMatch.url} (no reopen)\n`,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
return {
|
|
614
|
+
outcome: "error-network",
|
|
615
|
+
exitCode: 2,
|
|
616
|
+
title,
|
|
617
|
+
body: commentBody,
|
|
618
|
+
repo,
|
|
619
|
+
duplicateUrl: closedMatch.url,
|
|
620
|
+
message: networkErrorMessage("Closed-duplicate comment failed", err),
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
createBody = buildFrameworkGapBody(options, {
|
|
625
|
+
mode: "issue",
|
|
626
|
+
relatedClosedNumber: closedMatch.number,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
else if (closedWalk.outcome === "window-exhausted") {
|
|
630
|
+
incompleteNote = incompleteClosedSearchNote("window-exhausted", closedWalk.inspectedIssueCount, options.closedWindow ?? FEEDBACK_CLOSED_DEDUP_WINDOW);
|
|
631
|
+
}
|
|
632
|
+
else if (closedWalk.outcome === "cap") {
|
|
633
|
+
incompleteNote = incompleteClosedSearchNote("cap", closedWalk.inspectedIssueCount, options.closedRowBudget ?? REST_SHARED_ROW_BUDGET);
|
|
634
|
+
}
|
|
253
635
|
}
|
|
254
636
|
else {
|
|
255
637
|
dedupSkippedNote = "[deft feedback] Note: duplicate detection skipped (DEFT_NO_NETWORK=1).\n\n";
|
|
@@ -259,24 +641,24 @@ export function runFeedbackFile(options) {
|
|
|
259
641
|
outcome: "draft",
|
|
260
642
|
exitCode: 1,
|
|
261
643
|
title,
|
|
262
|
-
body,
|
|
644
|
+
body: createBody,
|
|
263
645
|
repo,
|
|
264
|
-
message: `${dedupSkippedNote}${buildConfirmationPrompt(title,
|
|
646
|
+
message: `${dedupSkippedNote}${incompleteNote}${buildConfirmationPrompt(title, createBody)}`,
|
|
265
647
|
};
|
|
266
648
|
}
|
|
267
|
-
if (
|
|
649
|
+
if (!mutationsAllowed(options, offline, dryRun)) {
|
|
268
650
|
return {
|
|
269
651
|
outcome: "draft",
|
|
270
652
|
exitCode: 0,
|
|
271
653
|
title,
|
|
272
|
-
body,
|
|
654
|
+
body: createBody,
|
|
273
655
|
repo,
|
|
274
|
-
message: `${dedupSkippedNote}[deft feedback] Dry run: would file upstream issue in ${repo}.\n` +
|
|
275
|
-
`${buildConfirmationPrompt(title,
|
|
656
|
+
message: `${dedupSkippedNote}${incompleteNote}[deft feedback] Dry run: would file upstream issue in ${repo}.\n` +
|
|
657
|
+
`${buildConfirmationPrompt(title, createBody)}`,
|
|
276
658
|
};
|
|
277
659
|
}
|
|
278
660
|
try {
|
|
279
|
-
const created = restCreateIssue(repo, title,
|
|
661
|
+
const created = restCreateIssue(repo, title, createBody, [], seams);
|
|
280
662
|
const issueUrl = typeof created.html_url === "string" && created.html_url.length > 0 ? created.html_url : null;
|
|
281
663
|
const number = created.number;
|
|
282
664
|
const urlLine = issueUrl ??
|
|
@@ -285,12 +667,13 @@ export function runFeedbackFile(options) {
|
|
|
285
667
|
outcome: "filed",
|
|
286
668
|
exitCode: 0,
|
|
287
669
|
title,
|
|
288
|
-
body,
|
|
670
|
+
body: createBody,
|
|
289
671
|
repo,
|
|
290
672
|
issueUrl: urlLine,
|
|
291
|
-
message:
|
|
292
|
-
|
|
293
|
-
|
|
673
|
+
message: `${incompleteNote}` +
|
|
674
|
+
(urlLine !== null
|
|
675
|
+
? `[deft feedback] Filed upstream issue: ${urlLine}\n`
|
|
676
|
+
: "[deft feedback] Issue filed upstream (URL unavailable in API response).\n"),
|
|
294
677
|
};
|
|
295
678
|
}
|
|
296
679
|
catch (err) {
|
|
@@ -298,7 +681,7 @@ export function runFeedbackFile(options) {
|
|
|
298
681
|
outcome: "error-network",
|
|
299
682
|
exitCode: 2,
|
|
300
683
|
title,
|
|
301
|
-
body,
|
|
684
|
+
body: createBody,
|
|
302
685
|
repo,
|
|
303
686
|
message: networkErrorMessage("Upstream filing failed", err),
|
|
304
687
|
};
|
|
@@ -310,6 +693,12 @@ function appendContextValue(collected, value) {
|
|
|
310
693
|
if (typeof value === "string")
|
|
311
694
|
collected.push(value);
|
|
312
695
|
}
|
|
696
|
+
function parseOnClosedValue(value) {
|
|
697
|
+
if (value === "comment" || value === "create-anyway") {
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
return "bad";
|
|
701
|
+
}
|
|
313
702
|
/** Parse argv for feedback:file. */
|
|
314
703
|
export function parseFeedbackFileArgs(argv) {
|
|
315
704
|
const out = {};
|
|
@@ -379,6 +768,20 @@ export function parseFeedbackFileArgs(argv) {
|
|
|
379
768
|
else if (arg?.startsWith("--recovery-cost=")) {
|
|
380
769
|
out.recoveryCost = arg.slice("--recovery-cost=".length);
|
|
381
770
|
}
|
|
771
|
+
else if (arg === "--on-closed") {
|
|
772
|
+
const parsed = parseOnClosedValue(argv[++i]);
|
|
773
|
+
if (parsed === "bad") {
|
|
774
|
+
return { ...out, error: "invalid --on-closed value (expected comment|create-anyway)" };
|
|
775
|
+
}
|
|
776
|
+
out.onClosed = parsed;
|
|
777
|
+
}
|
|
778
|
+
else if (arg?.startsWith("--on-closed=")) {
|
|
779
|
+
const parsed = parseOnClosedValue(arg.slice("--on-closed=".length));
|
|
780
|
+
if (parsed === "bad") {
|
|
781
|
+
return { ...out, error: "invalid --on-closed value (expected comment|create-anyway)" };
|
|
782
|
+
}
|
|
783
|
+
out.onClosed = parsed;
|
|
784
|
+
}
|
|
382
785
|
else if (arg === "--repo") {
|
|
383
786
|
out.repo = argv[++i];
|
|
384
787
|
}
|
|
@@ -426,6 +829,7 @@ export function feedbackFileMain(argv) {
|
|
|
426
829
|
confirm: args.confirm,
|
|
427
830
|
dryRun: args.dryRun,
|
|
428
831
|
json: args.json,
|
|
832
|
+
onClosed: args.onClosed,
|
|
429
833
|
repo: args.repo,
|
|
430
834
|
projectRoot: args.projectRoot,
|
|
431
835
|
});
|
|
@@ -437,6 +841,7 @@ export function feedbackFileMain(argv) {
|
|
|
437
841
|
body: result.body,
|
|
438
842
|
repo: result.repo,
|
|
439
843
|
issue_url: result.issueUrl ?? null,
|
|
844
|
+
comment_url: result.commentUrl ?? null,
|
|
440
845
|
duplicate_url: result.duplicateUrl ?? null,
|
|
441
846
|
message: result.message.trim(),
|
|
442
847
|
}, null, 2)}\n`);
|