@deftai/directive-core 0.70.0 → 0.71.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.
Files changed (79) hide show
  1. package/dist/branch/evaluate.js +7 -0
  2. package/dist/doctor/doctor-state.js +2 -2
  3. package/dist/eval/crud-telemetry.d.ts +59 -0
  4. package/dist/eval/crud-telemetry.js +307 -0
  5. package/dist/eval/health.d.ts +52 -0
  6. package/dist/eval/health.js +240 -0
  7. package/dist/eval/readback.d.ts +38 -0
  8. package/dist/eval/readback.js +209 -0
  9. package/dist/eval/report.d.ts +53 -0
  10. package/dist/eval/report.js +161 -0
  11. package/dist/eval/run.d.ts +79 -0
  12. package/dist/eval/run.js +309 -0
  13. package/dist/events/attribution-constants.d.ts +13 -0
  14. package/dist/events/attribution-constants.js +18 -0
  15. package/dist/events/attribution-ledger.d.ts +43 -0
  16. package/dist/events/attribution-ledger.js +61 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +5 -0
  19. package/dist/init-deposit/gitignore.js +15 -5
  20. package/dist/intake/candidates-log.d.ts +0 -1
  21. package/dist/intake/candidates-log.js +5 -2
  22. package/dist/intake/issue-ingest.d.ts +8 -0
  23. package/dist/intake/issue-ingest.js +19 -1
  24. package/dist/intake/reconcile-issues.js +9 -2
  25. package/dist/layout/resolve.d.ts +2 -2
  26. package/dist/layout/resolve.js +2 -2
  27. package/dist/lifecycle/events.js +2 -0
  28. package/dist/orchestration/subagent-monitor.js +2 -2
  29. package/dist/policy/index.d.ts +2 -1
  30. package/dist/policy/index.js +15 -2
  31. package/dist/policy/value-feedback.d.ts +56 -0
  32. package/dist/policy/value-feedback.js +284 -0
  33. package/dist/preflight-cache/evaluate.js +4 -3
  34. package/dist/scope/audit-log.js +3 -3
  35. package/dist/scope/transition.js +16 -5
  36. package/dist/session/session-start.js +20 -0
  37. package/dist/slice/record.js +3 -3
  38. package/dist/task-surface/index.js +4 -1
  39. package/dist/triage/actions/candidates-log.d.ts +4 -4
  40. package/dist/triage/actions/candidates-log.js +4 -7
  41. package/dist/triage/actions/index.js +8 -32
  42. package/dist/triage/bootstrap/gitignore.d.ts +9 -3
  43. package/dist/triage/bootstrap/gitignore.js +77 -40
  44. package/dist/triage/bootstrap/index.d.ts +1 -1
  45. package/dist/triage/bootstrap/index.js +4 -3
  46. package/dist/triage/bulk/index.js +2 -2
  47. package/dist/triage/cache-path.d.ts +41 -0
  48. package/dist/triage/cache-path.js +115 -0
  49. package/dist/triage/help/registry-data.d.ts +48 -15
  50. package/dist/triage/help/registry-data.js +86 -15
  51. package/dist/triage/index.d.ts +2 -0
  52. package/dist/triage/index.js +2 -0
  53. package/dist/triage/queue/audit.js +2 -2
  54. package/dist/triage/queue/cache.js +2 -2
  55. package/dist/triage/reconcile/reconcile.js +4 -3
  56. package/dist/triage/scope/mutations-core.js +3 -2
  57. package/dist/triage/subscribe/index.d.ts +1 -1
  58. package/dist/triage/subscribe/index.js +4 -3
  59. package/dist/triage/summary/index.d.ts +2 -2
  60. package/dist/triage/summary/index.js +4 -3
  61. package/dist/triage/summary/reconcilable.d.ts +1 -1
  62. package/dist/triage/summary/reconcilable.js +3 -2
  63. package/dist/triage/welcome/constants.d.ts +2 -2
  64. package/dist/triage/welcome/constants.js +2 -2
  65. package/dist/triage/welcome/prior-state.js +3 -2
  66. package/dist/triage/welcome/summary.js +5 -4
  67. package/dist/value/adoption-emit.d.ts +14 -0
  68. package/dist/value/adoption-emit.js +67 -0
  69. package/dist/value/adoption-registry.d.ts +62 -0
  70. package/dist/value/adoption-registry.js +214 -0
  71. package/dist/value/feedback-file.d.ts +67 -0
  72. package/dist/value/feedback-file.js +353 -0
  73. package/dist/value/friction-emit.d.ts +10 -0
  74. package/dist/value/friction-emit.js +24 -0
  75. package/dist/value/readback.d.ts +86 -0
  76. package/dist/value/readback.js +503 -0
  77. package/dist/vbrief-validate/decomposition.js +1 -1
  78. package/dist/wip-cap/evaluate.js +3 -0
  79. package/package.json +35 -3
@@ -0,0 +1,214 @@
1
+ import { isValueFeedbackPathAllowed, } from "../policy/value-feedback.js";
2
+ /** Conservative thresholds — adoption nudges fire only above these bars (#1709). */
3
+ export const ADOPTION_THRESHOLDS = {
4
+ smallWorkMaxFiles: 2,
5
+ planningMinFiles: 4,
6
+ costMinFiles: 3,
7
+ largeMultiFileMinFiles: 5,
8
+ largeMultiFileMinModules: 2,
9
+ swarmMinFiles: 8,
10
+ swarmMinModules: 3,
11
+ glossaryMinFiles: 6,
12
+ lessonsMinFiles: 5,
13
+ };
14
+ export const ADOPTION_SIGNAL_CLASS = "adoption";
15
+ const CAPABILITY_CATALOG = [
16
+ {
17
+ id: "planning",
18
+ label: "Planning",
19
+ description: "Structured spec interview or speckit planning before implementation.",
20
+ usageSignals: ["command:/deft:directive:run:interview", "command:/deft:directive:run:speckit"],
21
+ nudgeHint: "Consider `/deft:directive:run:interview` or `/deft:directive:run:speckit` before a multi-file build.",
22
+ },
23
+ {
24
+ id: "cost",
25
+ label: "Cost awareness",
26
+ description: "Capacity/cost phase before committing to a build.",
27
+ usageSignals: ["command:task capacity:show"],
28
+ nudgeHint: "Run `task capacity:show` before a build to surface allocation targets.",
29
+ },
30
+ {
31
+ id: "decompose",
32
+ label: "Decompose",
33
+ description: "Split large multi-file work into reviewable PRs.",
34
+ usageSignals: ["command:split-to-prs"],
35
+ nudgeHint: "Large multi-file work may benefit from split-to-prs decomposition.",
36
+ },
37
+ {
38
+ id: "swarm",
39
+ label: "Swarm",
40
+ description: "Parallel agent dispatch for disjoint multi-story cohorts.",
41
+ usageSignals: ["command:task swarm:launch"],
42
+ nudgeHint: "Disjoint multi-story work may benefit from `task swarm:launch`.",
43
+ },
44
+ {
45
+ id: "pre-pr",
46
+ label: "Pre-PR loop",
47
+ description: "Iterative pre-PR quality loop before opening a pull request.",
48
+ usageSignals: ["skill:deft-directive-pre-pr"],
49
+ nudgeHint: "Run the deft-directive-pre-pr skill before opening a PR.",
50
+ },
51
+ {
52
+ id: "debug",
53
+ label: "Debug mode",
54
+ description: "Systematic debug workflow for failures and regressions.",
55
+ usageSignals: ["skill:debug-mode"],
56
+ nudgeHint: "Switch to debug mode when chasing a failure systematically.",
57
+ },
58
+ {
59
+ id: "glossary",
60
+ label: "Glossary",
61
+ description: "Project glossary for unfamiliar domain terms.",
62
+ usageSignals: ["command:/deft:glossary"],
63
+ nudgeHint: "Consult `/deft:glossary` when domain terms are ambiguous.",
64
+ },
65
+ {
66
+ id: "lessons",
67
+ label: "Lessons packs",
68
+ description: "Prior-art slices from directive content packs.",
69
+ usageSignals: ["command:deft packs:slice"],
70
+ nudgeHint: "Load a lessons pack slice before improvising on a novel problem.",
71
+ },
72
+ ];
73
+ function usedSet(ctx) {
74
+ return new Set(ctx.usedCapabilities);
75
+ }
76
+ function verdict(applicable, reason) {
77
+ return { applicable, reason };
78
+ }
79
+ const APPLICABILITY_RULES = {
80
+ planning(ctx) {
81
+ if (ctx.isPrOpening) {
82
+ return verdict(false, "at PR-open boundary — planning phase is past");
83
+ }
84
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.planningMinFiles) {
85
+ return verdict(false, "scope below planning threshold");
86
+ }
87
+ return verdict(true, "multi-file scope without a planning pass");
88
+ },
89
+ cost(ctx) {
90
+ if (!ctx.isBuildIntent) {
91
+ return verdict(false, "not a build-intent session");
92
+ }
93
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.costMinFiles) {
94
+ return verdict(false, "build scope below cost threshold");
95
+ }
96
+ return verdict(true, "build intent without a cost phase");
97
+ },
98
+ decompose(ctx) {
99
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.largeMultiFileMinFiles) {
100
+ return verdict(false, "file count below large multi-file threshold");
101
+ }
102
+ if (ctx.distinctModuleGlobs < ADOPTION_THRESHOLDS.largeMultiFileMinModules) {
103
+ return verdict(false, "work is not parallelizable across modules");
104
+ }
105
+ return verdict(true, "large multi-file parallelizable work");
106
+ },
107
+ swarm(ctx) {
108
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.swarmMinFiles) {
109
+ return verdict(false, "file count below swarm threshold");
110
+ }
111
+ if (ctx.distinctModuleGlobs < ADOPTION_THRESHOLDS.swarmMinModules) {
112
+ return verdict(false, "insufficient module breadth for swarm cohort");
113
+ }
114
+ return verdict(true, "broad parallelizable cohort scope");
115
+ },
116
+ "pre-pr"(ctx) {
117
+ if (!ctx.isPrOpening) {
118
+ return verdict(false, "not at PR-open boundary");
119
+ }
120
+ return verdict(true, "PR opening without pre-PR loop");
121
+ },
122
+ debug(_ctx) {
123
+ return verdict(false, "debug nudges require an explicit failure signal");
124
+ },
125
+ glossary(ctx) {
126
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.glossaryMinFiles) {
127
+ return verdict(false, "scope below glossary threshold");
128
+ }
129
+ return verdict(true, "broad unfamiliar scope");
130
+ },
131
+ lessons(ctx) {
132
+ if (ctx.filesTouched < ADOPTION_THRESHOLDS.lessonsMinFiles) {
133
+ return verdict(false, "scope below lessons threshold");
134
+ }
135
+ return verdict(true, "novel multi-file scope");
136
+ },
137
+ };
138
+ /** Return the full capability catalog (#1709-adoption-registry-a1). */
139
+ function listCapabilities() {
140
+ return CAPABILITY_CATALOG;
141
+ }
142
+ /** Lookup a single catalog entry by id. */
143
+ function getCapability(id) {
144
+ return CAPABILITY_CATALOG.find((entry) => entry.id === id);
145
+ }
146
+ /** Event name for the attribution ledger (`adoption:<capability>`). */
147
+ export function formatAdoptionEventName(id) {
148
+ return `${ADOPTION_SIGNAL_CLASS}:${id}`;
149
+ }
150
+ /** True when work is too small for any adoption nudge (#1709-adoption-registry-a3). */
151
+ export function isWorkTooSmallForAdoptionNudges(ctx) {
152
+ return ctx.filesTouched <= ADOPTION_THRESHOLDS.smallWorkMaxFiles;
153
+ }
154
+ /** Evaluate whether a capability's conservative heuristic applies to the work snapshot. */
155
+ export function evaluateApplicability(id, ctx) {
156
+ if (isWorkTooSmallForAdoptionNudges(ctx)) {
157
+ return verdict(false, "small work — adoption nudges suppressed");
158
+ }
159
+ return APPLICABILITY_RULES[id](ctx);
160
+ }
161
+ function buildAdoptionSignal(id, ctx, applicability) {
162
+ const record = getCapability(id);
163
+ if (record === undefined) {
164
+ return null;
165
+ }
166
+ return {
167
+ signalClass: ADOPTION_SIGNAL_CLASS,
168
+ event: formatAdoptionEventName(record.id),
169
+ capabilityId: record.id,
170
+ message: record.nudgeHint,
171
+ evidence: {
172
+ filesTouched: ctx.filesTouched,
173
+ distinctModuleGlobs: ctx.distinctModuleGlobs,
174
+ usageSignals: record.usageSignals,
175
+ applicabilityReason: applicability.reason,
176
+ },
177
+ };
178
+ }
179
+ /**
180
+ * Detect applicable-but-unused capabilities (#1709-adoption-registry-a2).
181
+ * Does not consult the value-feedback policy — use `detectApplicableButUnusedGated`.
182
+ */
183
+ export function detectApplicableButUnused(ctx) {
184
+ if (isWorkTooSmallForAdoptionNudges(ctx)) {
185
+ return [];
186
+ }
187
+ const used = usedSet(ctx);
188
+ const signals = [];
189
+ for (const record of listCapabilities()) {
190
+ if (used.has(record.id)) {
191
+ continue;
192
+ }
193
+ const applicability = evaluateApplicability(record.id, ctx);
194
+ if (!applicability.applicable) {
195
+ continue;
196
+ }
197
+ const signal = buildAdoptionSignal(record.id, ctx, applicability);
198
+ if (signal !== null) {
199
+ signals.push(signal);
200
+ }
201
+ }
202
+ return signals;
203
+ }
204
+ /**
205
+ * Policy-gated adoption detection — returns signals only when value feedback
206
+ * is enabled and the emitEvents path is allowed (#1709 opt-in gate).
207
+ */
208
+ export function detectApplicableButUnusedGated(ctx, policy) {
209
+ if (!isValueFeedbackPathAllowed("emitEvents", policy)) {
210
+ return [];
211
+ }
212
+ return detectApplicableButUnused(ctx);
213
+ }
214
+ //# sourceMappingURL=adoption-registry.js.map
@@ -0,0 +1,67 @@
1
+ import { type GhRestSeams } from "../scm/gh-rest.js";
2
+ export declare const DEFAULT_UPSTREAM_REPO = "deftai/directive";
3
+ export declare const FRAMEWORK_GAP_TITLE_PREFIX = "[framework-gap]";
4
+ export type FeedbackFileOutcome = "draft" | "filed" | "skipped-maintainer" | "blocked-policy" | "blocked-duplicate" | "blocked-no-confirm" | "error-bad-args" | "error-config" | "error-network";
5
+ export interface FeedbackGapInput {
6
+ readonly summary: string;
7
+ readonly context?: string;
8
+ readonly expected?: string;
9
+ readonly actual?: string;
10
+ readonly sessionNotes?: string;
11
+ }
12
+ export interface FeedbackFileOptions extends FeedbackGapInput {
13
+ readonly projectRoot?: string | null;
14
+ readonly repo?: string;
15
+ readonly confirm?: boolean;
16
+ readonly dryRun?: boolean;
17
+ readonly json?: boolean;
18
+ readonly seams?: GhRestSeams;
19
+ }
20
+ export interface FeedbackFileResult {
21
+ readonly outcome: FeedbackFileOutcome;
22
+ readonly exitCode: 0 | 1 | 2;
23
+ readonly title: string;
24
+ readonly body: string;
25
+ readonly repo: string;
26
+ readonly issueUrl?: string | null;
27
+ readonly duplicateUrl?: string | null;
28
+ readonly message: string;
29
+ }
30
+ /** True when the resolved project root is the directive maintainer source checkout. */
31
+ export declare function isMaintainerFrameworkRepo(projectRoot: string): boolean;
32
+ /** Normalize a title for duplicate comparison (#1709). */
33
+ export declare function normalizeForDedup(title: string): string;
34
+ /** Build the upstream issue title for a framework-gap report. */
35
+ export declare function buildFrameworkGapTitle(summary: string): string;
36
+ /** Build the structured framework-gap issue body. */
37
+ export declare function buildFrameworkGapBody(input: FeedbackGapInput): string;
38
+ export interface DuplicateMatch {
39
+ readonly url: string;
40
+ readonly title: string;
41
+ }
42
+ /** Search open upstream issues for a normalized title match. */
43
+ export declare function findDuplicateIssue(repo: string, title: string, seams?: GhRestSeams): DuplicateMatch | null;
44
+ /** Core feedback:file workflow (#1709 gap escalation). */
45
+ export declare function runFeedbackFile(options: FeedbackFileOptions): FeedbackFileResult;
46
+ export interface FeedbackFileCliArgs {
47
+ summary?: string;
48
+ context?: string;
49
+ expected?: string;
50
+ actual?: string;
51
+ sessionNotes?: string;
52
+ confirm?: boolean;
53
+ dryRun?: boolean;
54
+ json?: boolean;
55
+ repo?: string;
56
+ projectRoot?: string;
57
+ error?: string;
58
+ }
59
+ /** Parse argv for feedback:file. */
60
+ export declare function parseFeedbackFileArgs(argv: readonly string[]): FeedbackFileCliArgs;
61
+ /** CLI entry for feedback:file. */
62
+ export declare function feedbackFileMain(argv: readonly string[]): number;
63
+ /** CLI module entrypoint for dispatch (#1709). */
64
+ export declare function mainEntry(argv?: string[]): number;
65
+ /** Convenience probe used in tests for project-root sentinel presence. */
66
+ export declare function projectRootLooksLikeConsumer(projectRoot: string): boolean;
67
+ //# sourceMappingURL=feedback-file.d.ts.map
@@ -0,0 +1,353 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { isFrameworkRepoRoot } from "../check/orchestrator.js";
4
+ import { isValueFeedbackPathAllowed, resolveValueFeedback } from "../policy/value-feedback.js";
5
+ import { GhRestError, restCreateIssue, restIssueListPaginated, } from "../scm/gh-rest.js";
6
+ import { resolveProjectRoot } from "../scope/project-context.js";
7
+ export const DEFAULT_UPSTREAM_REPO = "deftai/directive";
8
+ export const FRAMEWORK_GAP_TITLE_PREFIX = "[framework-gap]";
9
+ function sanitizeOneLine(value) {
10
+ return value.replace(/\r?\n/g, " ").trim();
11
+ }
12
+ function sectionOrPlaceholder(value, placeholder) {
13
+ const trimmed = value?.trim() ?? "";
14
+ return trimmed.length > 0 ? trimmed : placeholder;
15
+ }
16
+ /** True when the resolved project root is the directive maintainer source checkout. */
17
+ export function isMaintainerFrameworkRepo(projectRoot) {
18
+ return isFrameworkRepoRoot(resolve(projectRoot));
19
+ }
20
+ /** Normalize a title for duplicate comparison (#1709). */
21
+ export function normalizeForDedup(title) {
22
+ return title
23
+ .trim()
24
+ .replace(/^\[framework-gap\]\s*/i, "")
25
+ .toLowerCase()
26
+ .replace(/[^\p{L}\p{N}]+/gu, " ")
27
+ .trim();
28
+ }
29
+ /** Build the upstream issue title for a framework-gap report. */
30
+ export function buildFrameworkGapTitle(summary) {
31
+ const clean = sanitizeOneLine(summary);
32
+ if (clean.toLowerCase().startsWith(FRAMEWORK_GAP_TITLE_PREFIX.toLowerCase())) {
33
+ return clean;
34
+ }
35
+ return `${FRAMEWORK_GAP_TITLE_PREFIX} ${clean}`;
36
+ }
37
+ /** Build the structured framework-gap issue body. */
38
+ export function buildFrameworkGapBody(input) {
39
+ const summary = sanitizeOneLine(input.summary);
40
+ const lines = [
41
+ "## Summary",
42
+ "",
43
+ summary,
44
+ "",
45
+ "## Context",
46
+ "",
47
+ sectionOrPlaceholder(input.context, "_(not provided)_"),
48
+ "",
49
+ "## Expected behavior",
50
+ "",
51
+ sectionOrPlaceholder(input.expected, "_(not provided)_"),
52
+ "",
53
+ "## Actual behavior / gap",
54
+ "",
55
+ sectionOrPlaceholder(input.actual, summary),
56
+ "",
57
+ "## Session notes",
58
+ "",
59
+ sectionOrPlaceholder(input.sessionNotes, "_(not provided)_"),
60
+ "",
61
+ "---",
62
+ "_Filed via `task feedback:file` from a directive consumer project (Refs #1709 value-feedback gap escalation)._",
63
+ "",
64
+ ];
65
+ return lines.join("\n");
66
+ }
67
+ /** Search open upstream issues for a normalized title match. */
68
+ export function findDuplicateIssue(repo, title, seams = {}) {
69
+ const needle = normalizeForDedup(title);
70
+ if (needle.length === 0) {
71
+ return null;
72
+ }
73
+ const issues = restIssueListPaginated(repo, { state: "open", limit: 100, excludePulls: true }, seams);
74
+ for (const issue of issues) {
75
+ const issueTitle = typeof issue.title === "string" ? issue.title : "";
76
+ if (normalizeForDedup(issueTitle) === needle) {
77
+ const url = typeof issue.html_url === "string" ? issue.html_url : "";
78
+ if (url.length > 0) {
79
+ return { url, title: issueTitle };
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function resolveRepo(options) {
86
+ const explicit = options.repo?.trim();
87
+ return explicit && explicit.length > 0 ? explicit : DEFAULT_UPSTREAM_REPO;
88
+ }
89
+ function isOffline() {
90
+ return process.env.DEFT_NO_NETWORK === "1";
91
+ }
92
+ function networkErrorMessage(prefix, err) {
93
+ const detail = err instanceof GhRestError ? err.message : String(err);
94
+ return `[deft feedback] ${prefix}: ${detail}\n`;
95
+ }
96
+ function buildConfirmationPrompt(title, body) {
97
+ return ("Draft framework-gap issue (not filed yet):\n\n" +
98
+ `Title: ${title}\n\n` +
99
+ `${body}\n` +
100
+ "Re-run with --confirm to file this issue upstream after reviewing the draft.\n");
101
+ }
102
+ /** Core feedback:file workflow (#1709 gap escalation). */
103
+ export function runFeedbackFile(options) {
104
+ const summary = sanitizeOneLine(options.summary);
105
+ if (summary.length === 0) {
106
+ return {
107
+ outcome: "error-bad-args",
108
+ exitCode: 2,
109
+ title: "",
110
+ body: "",
111
+ repo: resolveRepo(options),
112
+ message: "Error: summary is required (pass --summary or a positional argument).\n",
113
+ };
114
+ }
115
+ const projectRootRaw = resolveProjectRoot(options.projectRoot ?? undefined);
116
+ if (projectRootRaw === null) {
117
+ return {
118
+ outcome: "error-config",
119
+ exitCode: 2,
120
+ title: buildFrameworkGapTitle(summary),
121
+ body: buildFrameworkGapBody(options),
122
+ repo: resolveRepo(options),
123
+ message: "Error: could not resolve project root. Pass --project-root or run from a directive consumer repo.\n",
124
+ };
125
+ }
126
+ const projectRoot = resolve(projectRootRaw);
127
+ const repo = resolveRepo(options);
128
+ const title = buildFrameworkGapTitle(summary);
129
+ const body = buildFrameworkGapBody(options);
130
+ if (isMaintainerFrameworkRepo(projectRoot)) {
131
+ return {
132
+ outcome: "skipped-maintainer",
133
+ exitCode: 0,
134
+ title,
135
+ body,
136
+ repo,
137
+ message: "[deft feedback] Skipped: maintainer framework repo detected; gap escalation targets consumer projects only.\n",
138
+ };
139
+ }
140
+ const policy = resolveValueFeedback(projectRoot);
141
+ if (!isValueFeedbackPathAllowed("upstreamPrompt", policy)) {
142
+ return {
143
+ outcome: "blocked-policy",
144
+ exitCode: 1,
145
+ title,
146
+ body,
147
+ repo,
148
+ message: "[deft feedback] Blocked: plan.policy.valueFeedback upstreamPrompt is OFF. " +
149
+ "Enable with `task policy:enable-value-feedback -- --confirm` and set upstreamPrompt, " +
150
+ "or inspect via `task policy:show --field=valueFeedback`.\n",
151
+ };
152
+ }
153
+ const offline = isOffline();
154
+ const dryRun = options.dryRun ?? false;
155
+ const seams = options.seams ?? {};
156
+ let dedupSkippedNote = "";
157
+ if (!offline) {
158
+ try {
159
+ const duplicate = findDuplicateIssue(repo, title, seams);
160
+ if (duplicate !== null) {
161
+ return {
162
+ outcome: "blocked-duplicate",
163
+ exitCode: 1,
164
+ title,
165
+ body,
166
+ repo,
167
+ duplicateUrl: duplicate.url,
168
+ message: `[deft feedback] Duplicate detected: open issue ${duplicate.url} ` +
169
+ `("${duplicate.title}") matches this report.\n`,
170
+ };
171
+ }
172
+ }
173
+ catch (err) {
174
+ return {
175
+ outcome: "error-network",
176
+ exitCode: 2,
177
+ title,
178
+ body,
179
+ repo,
180
+ message: networkErrorMessage("Duplicate search failed", err),
181
+ };
182
+ }
183
+ }
184
+ else {
185
+ dedupSkippedNote = "[deft feedback] Note: duplicate detection skipped (DEFT_NO_NETWORK=1).\n\n";
186
+ }
187
+ if (!options.confirm) {
188
+ return {
189
+ outcome: "draft",
190
+ exitCode: 1,
191
+ title,
192
+ body,
193
+ repo,
194
+ message: `${dedupSkippedNote}${buildConfirmationPrompt(title, body)}`,
195
+ };
196
+ }
197
+ if (dryRun || offline) {
198
+ return {
199
+ outcome: "draft",
200
+ exitCode: 0,
201
+ title,
202
+ body,
203
+ repo,
204
+ message: `${dedupSkippedNote}[deft feedback] Dry run: would file upstream issue in ${repo}.\n` +
205
+ `${buildConfirmationPrompt(title, body)}`,
206
+ };
207
+ }
208
+ try {
209
+ const created = restCreateIssue(repo, title, body, [], seams);
210
+ const issueUrl = typeof created.html_url === "string" && created.html_url.length > 0 ? created.html_url : null;
211
+ const number = created.number;
212
+ const urlLine = issueUrl ??
213
+ (typeof number === "number" ? `https://github.com/${repo}/issues/${number}` : null);
214
+ return {
215
+ outcome: "filed",
216
+ exitCode: 0,
217
+ title,
218
+ body,
219
+ repo,
220
+ issueUrl: urlLine,
221
+ message: urlLine !== null
222
+ ? `[deft feedback] Filed upstream issue: ${urlLine}\n`
223
+ : "[deft feedback] Issue filed upstream (URL unavailable in API response).\n",
224
+ };
225
+ }
226
+ catch (err) {
227
+ return {
228
+ outcome: "error-network",
229
+ exitCode: 2,
230
+ title,
231
+ body,
232
+ repo,
233
+ message: networkErrorMessage("Upstream filing failed", err),
234
+ };
235
+ }
236
+ }
237
+ /** Parse argv for feedback:file. */
238
+ export function parseFeedbackFileArgs(argv) {
239
+ const out = {};
240
+ const positionals = [];
241
+ for (let i = 0; i < argv.length; i += 1) {
242
+ const arg = argv[i];
243
+ if (arg === "--confirm")
244
+ out.confirm = true;
245
+ else if (arg === "--dry-run")
246
+ out.dryRun = true;
247
+ else if (arg === "--json")
248
+ out.json = true;
249
+ else if (arg === "--summary") {
250
+ out.summary = argv[++i];
251
+ }
252
+ else if (arg?.startsWith("--summary=")) {
253
+ out.summary = arg.slice("--summary=".length);
254
+ }
255
+ else if (arg === "--context") {
256
+ out.context = argv[++i];
257
+ }
258
+ else if (arg?.startsWith("--context=")) {
259
+ out.context = arg.slice("--context=".length);
260
+ }
261
+ else if (arg === "--expected") {
262
+ out.expected = argv[++i];
263
+ }
264
+ else if (arg?.startsWith("--expected=")) {
265
+ out.expected = arg.slice("--expected=".length);
266
+ }
267
+ else if (arg === "--actual") {
268
+ out.actual = argv[++i];
269
+ }
270
+ else if (arg?.startsWith("--actual=")) {
271
+ out.actual = arg.slice("--actual=".length);
272
+ }
273
+ else if (arg === "--notes" || arg === "--session-notes") {
274
+ out.sessionNotes = argv[++i];
275
+ }
276
+ else if (arg?.startsWith("--notes=")) {
277
+ out.sessionNotes = arg.slice("--notes=".length);
278
+ }
279
+ else if (arg?.startsWith("--session-notes=")) {
280
+ out.sessionNotes = arg.slice("--session-notes=".length);
281
+ }
282
+ else if (arg === "--repo") {
283
+ out.repo = argv[++i];
284
+ }
285
+ else if (arg?.startsWith("--repo=")) {
286
+ out.repo = arg.slice("--repo=".length);
287
+ }
288
+ else if (arg === "--project-root") {
289
+ out.projectRoot = argv[++i];
290
+ }
291
+ else if (arg?.startsWith("--project-root=")) {
292
+ out.projectRoot = arg.slice("--project-root=".length);
293
+ }
294
+ else if (arg.startsWith("-")) {
295
+ return { ...out, error: `unrecognized argument: ${arg}` };
296
+ }
297
+ else {
298
+ positionals.push(arg);
299
+ }
300
+ }
301
+ if ((out.summary === undefined || out.summary.trim().length === 0) && positionals.length > 0) {
302
+ out.summary = positionals.join(" ");
303
+ }
304
+ return out;
305
+ }
306
+ /** CLI entry for feedback:file. */
307
+ export function feedbackFileMain(argv) {
308
+ const args = parseFeedbackFileArgs(argv);
309
+ if (args.error !== undefined) {
310
+ process.stderr.write(`feedback:file: ${args.error}\n`);
311
+ return 2;
312
+ }
313
+ const result = runFeedbackFile({
314
+ summary: args.summary ?? "",
315
+ context: args.context,
316
+ expected: args.expected,
317
+ actual: args.actual,
318
+ sessionNotes: args.sessionNotes,
319
+ confirm: args.confirm,
320
+ dryRun: args.dryRun,
321
+ json: args.json,
322
+ repo: args.repo,
323
+ projectRoot: args.projectRoot,
324
+ });
325
+ if (args.json) {
326
+ process.stdout.write(`${JSON.stringify({
327
+ outcome: result.outcome,
328
+ exit_code: result.exitCode,
329
+ title: result.title,
330
+ body: result.body,
331
+ repo: result.repo,
332
+ issue_url: result.issueUrl ?? null,
333
+ duplicate_url: result.duplicateUrl ?? null,
334
+ message: result.message.trim(),
335
+ }, null, 2)}\n`);
336
+ }
337
+ else {
338
+ process.stdout.write(result.message);
339
+ }
340
+ return result.exitCode;
341
+ }
342
+ /** CLI module entrypoint for dispatch (#1709). */
343
+ export function mainEntry(argv = process.argv.slice(2)) {
344
+ return feedbackFileMain(argv);
345
+ }
346
+ /** Convenience probe used in tests for project-root sentinel presence. */
347
+ export function projectRootLooksLikeConsumer(projectRoot) {
348
+ const root = resolve(projectRoot);
349
+ return (existsSync(resolve(root, "xbrief")) ||
350
+ existsSync(resolve(root, "vbrief")) ||
351
+ existsSync(resolve(root, ".deft")));
352
+ }
353
+ //# sourceMappingURL=feedback-file.js.map
@@ -0,0 +1,10 @@
1
+ import { type ValueFeedbackResolved } from "../policy/value-feedback.js";
2
+ export interface FrictionEmitOptions {
3
+ readonly logPath?: string | null;
4
+ readonly policyOverride?: ValueFeedbackResolved;
5
+ }
6
+ /** Record friction signals for each contradictory gate detected by eval:health (#2339). */
7
+ export declare function recordFrictionFromContradictoryGates(projectRoot: string, options?: FrictionEmitOptions): number;
8
+ /** Session/work-boundary friction probe alias (#2339). */
9
+ export declare function probeFrictionAtWorkBoundary(projectRoot: string, options?: FrictionEmitOptions): number;
10
+ //# sourceMappingURL=friction-emit.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { detectContradictoryGates } from "../eval/health.js";
2
+ import { recordFrictionSignal } from "../events/attribution-ledger.js";
3
+ import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
4
+ /** Record friction signals for each contradictory gate detected by eval:health (#2339). */
5
+ export function recordFrictionFromContradictoryGates(projectRoot, options = {}) {
6
+ const policy = options.policyOverride ?? resolveValueFeedback(projectRoot);
7
+ if (!isValueFeedbackPathAllowed("emitEvents", policy)) {
8
+ return 0;
9
+ }
10
+ const contradictions = detectContradictoryGates(projectRoot);
11
+ let recorded = 0;
12
+ for (const contradiction of contradictions) {
13
+ const record = recordFrictionSignal(projectRoot, "eval:health", `${contradiction.id}: ${contradiction.summary}`, { logPath: options.logPath, policyOverride: policy });
14
+ if (record !== null) {
15
+ recorded += 1;
16
+ }
17
+ }
18
+ return recorded;
19
+ }
20
+ /** Session/work-boundary friction probe alias (#2339). */
21
+ export function probeFrictionAtWorkBoundary(projectRoot, options = {}) {
22
+ return recordFrictionFromContradictoryGates(projectRoot, options);
23
+ }
24
+ //# sourceMappingURL=friction-emit.js.map