@deftai/directive-core 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/content-contracts/skills/helpers.d.ts +6 -0
- package/dist/content-contracts/skills/helpers.js +47 -3
- package/dist/content-contracts/skills/skill-external-fetch-gate.d.ts +18 -0
- package/dist/content-contracts/skills/skill-external-fetch-gate.js +81 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +139 -0
- package/dist/doctor/main.js +2 -1
- package/dist/eval/crud-telemetry.d.ts +2 -0
- package/dist/eval/crud-telemetry.js +30 -0
- package/dist/intake/github-body-cli.js +2 -0
- package/dist/intake/github-body.d.ts +11 -0
- package/dist/intake/github-body.js +95 -24
- package/dist/integration-e2e/helpers.js +1 -1
- package/dist/issue-sync/sync-from-xbrief.js +3 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +275 -0
- package/dist/orphan-active/index.d.ts +3 -0
- package/dist/orphan-active/index.js +3 -0
- package/dist/orphan-active/refs.d.ts +16 -0
- package/dist/orphan-active/refs.js +105 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +19 -5
- package/dist/policy/product-signal.d.ts +45 -0
- package/dist/policy/product-signal.js +210 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.js +18 -0
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +44 -73
- package/dist/pr-wait-mergeable/result.d.ts +2 -1
- package/dist/pr-wait-mergeable/result.js +4 -0
- package/dist/pr-wait-mergeable/semantic-green.d.ts +27 -0
- package/dist/pr-wait-mergeable/semantic-green.js +202 -0
- package/dist/pr-wait-mergeable/types.d.ts +1 -0
- package/dist/pr-watch/constants.d.ts +4 -4
- package/dist/pr-watch/constants.js +4 -4
- package/dist/pr-watch/watch.js +5 -3
- package/dist/product-signal/actor-name.d.ts +18 -0
- package/dist/product-signal/actor-name.js +94 -0
- package/dist/product-signal/consent.d.ts +30 -0
- package/dist/product-signal/consent.js +116 -0
- package/dist/product-signal/gates.d.ts +17 -0
- package/dist/product-signal/gates.js +66 -0
- package/dist/product-signal/github-private-sink-adapter.d.ts +28 -0
- package/dist/product-signal/github-private-sink-adapter.js +274 -0
- package/dist/product-signal/headless.d.ts +8 -0
- package/dist/product-signal/headless.js +32 -0
- package/dist/product-signal/install-context.d.ts +13 -0
- package/dist/product-signal/install-context.js +41 -0
- package/dist/product-signal/local-signal-summary.d.ts +7 -0
- package/dist/product-signal/local-signal-summary.js +173 -0
- package/dist/product-signal/payload.d.ts +90 -0
- package/dist/product-signal/payload.js +124 -0
- package/dist/product-signal/sink-bootstrap.d.ts +29 -0
- package/dist/product-signal/sink-bootstrap.js +108 -0
- package/dist/product-signal/submit-adapter.d.ts +14 -0
- package/dist/product-signal/submit-adapter.js +2 -0
- package/dist/product-signal/submit.d.ts +54 -0
- package/dist/product-signal/submit.js +290 -0
- package/dist/release/native-steps.js +11 -4
- package/dist/render/framework-commands.js +6 -0
- package/dist/scm/gh-rest.d.ts +3 -1
- package/dist/scm/gh-rest.js +22 -0
- package/dist/scope/brief-io.d.ts +26 -0
- package/dist/scope/brief-io.js +77 -0
- package/dist/scope/decompose.js +2 -2
- package/dist/scope/decomposed-refs.js +3 -3
- package/dist/scope/demote.js +2 -2
- package/dist/scope/project-definition-sync.d.ts +2 -2
- package/dist/scope/project-definition-sync.js +15 -3
- package/dist/scope/registry-artifact-sync.d.ts +6 -1
- package/dist/scope/registry-artifact-sync.js +31 -13
- package/dist/scope/transition.js +23 -15
- package/dist/scope/undo.js +2 -2
- package/dist/scope/vbrief-json.d.ts +1 -2
- package/dist/scope/vbrief-json.js +1 -4
- package/dist/staleness-tickler/state.js +19 -2
- package/dist/triage/queue/cache.js +5 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/skill-external-fetch-gate.d.ts +21 -0
- package/dist/verify-source/skill-external-fetch-gate.js +71 -0
- package/package.json +7 -3
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { GhRestError, restCreateIssue, restIssueListPaginated, restPostComment, restUpdateIssue, } from "../scm/gh-rest.js";
|
|
2
|
+
import { normalizeActorKey } from "./actor-name.js";
|
|
3
|
+
import { classifySinkError } from "./gates.js";
|
|
4
|
+
export const PRODUCT_SIGNAL_MARKER_PREFIX = "<!-- deft:product-signal v1";
|
|
5
|
+
export const SURFACE_LABELS = {
|
|
6
|
+
pulse: "surface:pulse",
|
|
7
|
+
portrait: "surface:portrait",
|
|
8
|
+
};
|
|
9
|
+
/** Build HTML comment marker for standing-thread lookup (#2693 D8). */
|
|
10
|
+
export function buildThreadMarker(installId, actorName, surface) {
|
|
11
|
+
return (`${PRODUCT_SIGNAL_MARKER_PREFIX} ` +
|
|
12
|
+
`installId=${installId} actorKey=${normalizeActorKey(actorName)} surface=${surface} -->`);
|
|
13
|
+
}
|
|
14
|
+
function npsLabels(nps) {
|
|
15
|
+
if (nps === null) {
|
|
16
|
+
return ["nps:none"];
|
|
17
|
+
}
|
|
18
|
+
if (nps >= 9) {
|
|
19
|
+
return ["nps:promoter"];
|
|
20
|
+
}
|
|
21
|
+
if (nps >= 7) {
|
|
22
|
+
return ["nps:passive"];
|
|
23
|
+
}
|
|
24
|
+
return ["nps:detractor"];
|
|
25
|
+
}
|
|
26
|
+
function directiveVersionLabel(version) {
|
|
27
|
+
const match = version.match(/^(\d+\.\d+)/);
|
|
28
|
+
const minor = match?.[1] ?? "0.0";
|
|
29
|
+
return `directive:${minor}`;
|
|
30
|
+
}
|
|
31
|
+
function labelNamesFromIssue(issue) {
|
|
32
|
+
const labels = issue.labels;
|
|
33
|
+
if (!Array.isArray(labels)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const names = [];
|
|
37
|
+
for (const label of labels) {
|
|
38
|
+
if (typeof label === "string" && label.trim().length > 0) {
|
|
39
|
+
names.push(label.trim());
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (label !== null && typeof label === "object" && !Array.isArray(label)) {
|
|
43
|
+
const name = label.name;
|
|
44
|
+
if (typeof name === "string" && name.trim().length > 0) {
|
|
45
|
+
names.push(name.trim());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return names;
|
|
50
|
+
}
|
|
51
|
+
/** Merge labels for PATCH updates (GitHub replaces the full label set). */
|
|
52
|
+
export function mergeIssueLabels(existing, next) {
|
|
53
|
+
const merged = new Set(labelNamesFromIssue(existing));
|
|
54
|
+
for (const label of next) {
|
|
55
|
+
if (label.trim().length > 0) {
|
|
56
|
+
merged.add(label.trim());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...merged];
|
|
60
|
+
}
|
|
61
|
+
function formatPayloadBody(payload) {
|
|
62
|
+
const marker = buildThreadMarker(payload.installId, payload.actorName, payload.surface);
|
|
63
|
+
const humanLines = [];
|
|
64
|
+
if (payload.human.nps !== null) {
|
|
65
|
+
humanLines.push(`- NPS: ${payload.human.nps}`);
|
|
66
|
+
}
|
|
67
|
+
for (const answer of payload.human.answers) {
|
|
68
|
+
humanLines.push(`- **${answer.q}**: ${answer.a}`);
|
|
69
|
+
}
|
|
70
|
+
if (payload.human.freeText !== null && payload.human.freeText.trim().length > 0) {
|
|
71
|
+
humanLines.push(`- Free text: ${payload.human.freeText.trim()}`);
|
|
72
|
+
}
|
|
73
|
+
const summaryJson = payload.localSignalSummary !== null
|
|
74
|
+
? `\n<details><summary>localSignalSummary</summary>\n\n\`\`\`json\n${JSON.stringify(payload.localSignalSummary, null, 2)}\n\`\`\`\n</details>\n`
|
|
75
|
+
: "";
|
|
76
|
+
const skillsJson = payload.skillsSummary !== null
|
|
77
|
+
? `\n<details><summary>skillsSummary</summary>\n\n\`\`\`json\n${JSON.stringify(payload.skillsSummary, null, 2)}\n\`\`\`\n</details>\n`
|
|
78
|
+
: "";
|
|
79
|
+
return [
|
|
80
|
+
marker,
|
|
81
|
+
"",
|
|
82
|
+
`## ${payload.surface === "portrait" ? "Portrait" : "Pulse"} — ${payload.actorName}`,
|
|
83
|
+
"",
|
|
84
|
+
`- installId: \`${payload.installId}\``,
|
|
85
|
+
`- actorNameSource: ${payload.actorNameSource}`,
|
|
86
|
+
`- directive: ${payload.directiveVersion}`,
|
|
87
|
+
`- harness: ${payload.harness}${payload.harnessVersion ? ` (${payload.harnessVersion})` : ""}`,
|
|
88
|
+
`- os: ${payload.os} / shell: ${payload.shell}`,
|
|
89
|
+
`- collectedAt: ${payload.collectedAt}`,
|
|
90
|
+
"",
|
|
91
|
+
"### Human",
|
|
92
|
+
"",
|
|
93
|
+
humanLines.length > 0 ? humanLines.join("\n") : "_(no human answers)_",
|
|
94
|
+
payload.agentNotes ? `\n### Agent notes\n\n${payload.agentNotes}` : "",
|
|
95
|
+
summaryJson,
|
|
96
|
+
skillsJson,
|
|
97
|
+
"",
|
|
98
|
+
"_Submitted via product-signal (Refs #2693)._",
|
|
99
|
+
"",
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
function formatPulseComment(payload) {
|
|
103
|
+
const date = payload.collectedAt.slice(0, 10);
|
|
104
|
+
const humanLines = [];
|
|
105
|
+
if (payload.human.nps !== null) {
|
|
106
|
+
humanLines.push(`NPS ${payload.human.nps}`);
|
|
107
|
+
}
|
|
108
|
+
for (const answer of payload.human.answers) {
|
|
109
|
+
humanLines.push(`${answer.q}: ${answer.a}`);
|
|
110
|
+
}
|
|
111
|
+
if (payload.human.freeText) {
|
|
112
|
+
humanLines.push(payload.human.freeText);
|
|
113
|
+
}
|
|
114
|
+
return [`**Pulse ${date}**`, "", ...humanLines, ""].join("\n");
|
|
115
|
+
}
|
|
116
|
+
function findStandingIssue(repo, installId, actorName, surface, seams) {
|
|
117
|
+
const needleKey = normalizeActorKey(actorName);
|
|
118
|
+
const markerNeedle = `installId=${installId} actorKey=${needleKey} surface=${surface}`;
|
|
119
|
+
const issues = restIssueListPaginated(repo, { state: "open", labels: [SURFACE_LABELS[surface]], limit: 200 }, seams);
|
|
120
|
+
for (const issue of issues) {
|
|
121
|
+
const body = typeof issue.body === "string" ? issue.body : "";
|
|
122
|
+
if (body.includes(markerNeedle)) {
|
|
123
|
+
return issue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function issueUrlFromRecord(issue) {
|
|
129
|
+
const url = issue.html_url;
|
|
130
|
+
return typeof url === "string" ? url : null;
|
|
131
|
+
}
|
|
132
|
+
function issueNumberFromRecord(issue) {
|
|
133
|
+
const num = issue.number;
|
|
134
|
+
return typeof num === "number" ? num : null;
|
|
135
|
+
}
|
|
136
|
+
/** Phase-1 GitHub private sink adapter (#2693 D5/D8). */
|
|
137
|
+
export class GitHubPrivateSinkAdapter {
|
|
138
|
+
id = "github-private-sink";
|
|
139
|
+
repo;
|
|
140
|
+
seams;
|
|
141
|
+
constructor(options) {
|
|
142
|
+
this.repo = options.sinkRepo;
|
|
143
|
+
this.seams = options.seams ?? {};
|
|
144
|
+
}
|
|
145
|
+
async submit(payload, extras) {
|
|
146
|
+
try {
|
|
147
|
+
const labels = [
|
|
148
|
+
SURFACE_LABELS[payload.surface],
|
|
149
|
+
...npsLabels(payload.human.nps),
|
|
150
|
+
directiveVersionLabel(payload.directiveVersion),
|
|
151
|
+
];
|
|
152
|
+
let result;
|
|
153
|
+
if (payload.surface === "portrait") {
|
|
154
|
+
result = this.submitPortrait(payload, labels);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
result = this.submitPulse(payload, labels);
|
|
158
|
+
}
|
|
159
|
+
if (result.outcome === "submitted" &&
|
|
160
|
+
extras?.gapText !== undefined &&
|
|
161
|
+
extras.gapText !== null &&
|
|
162
|
+
extras.gapText.trim().length > 0) {
|
|
163
|
+
this.appendGapCommentOnPulse(payload, extras.gapText.trim());
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
if (err instanceof GhRestError) {
|
|
169
|
+
const outcome = classifySinkError(err.stderr, err.exitCode);
|
|
170
|
+
return {
|
|
171
|
+
outcome,
|
|
172
|
+
message: `product-signal soft-skip (${outcome}): ${err.stderr || err.message}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
return {
|
|
177
|
+
outcome: "sink-unreachable",
|
|
178
|
+
message: `product-signal soft-skip (sink-unreachable): ${message}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
submitPortrait(payload, labels) {
|
|
183
|
+
const body = formatPayloadBody(payload);
|
|
184
|
+
const title = `[portrait] ${payload.actorName} @ ${payload.installId.slice(0, 8)}`;
|
|
185
|
+
const existing = findStandingIssue(this.repo, payload.installId, payload.actorName, "portrait", this.seams);
|
|
186
|
+
if (existing !== null) {
|
|
187
|
+
const num = issueNumberFromRecord(existing);
|
|
188
|
+
if (num === null) {
|
|
189
|
+
return { outcome: "sink-unreachable", message: "standing portrait issue missing number" };
|
|
190
|
+
}
|
|
191
|
+
const updated = restUpdateIssue(this.repo, num, { body, labels: [...labels] }, this.seams);
|
|
192
|
+
return {
|
|
193
|
+
outcome: "submitted",
|
|
194
|
+
issueUrl: issueUrlFromRecord(updated),
|
|
195
|
+
issueNumber: issueNumberFromRecord(updated),
|
|
196
|
+
message: `portrait upserted on #${num}`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const created = restCreateIssue(this.repo, title, body, labels, this.seams);
|
|
200
|
+
return {
|
|
201
|
+
outcome: "submitted",
|
|
202
|
+
issueUrl: issueUrlFromRecord(created),
|
|
203
|
+
issueNumber: issueNumberFromRecord(created),
|
|
204
|
+
message: "portrait standing issue created",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
submitPulse(payload, labels) {
|
|
208
|
+
const existing = findStandingIssue(this.repo, payload.installId, payload.actorName, "pulse", this.seams);
|
|
209
|
+
let issueNumber;
|
|
210
|
+
let issueUrl;
|
|
211
|
+
if (existing === null) {
|
|
212
|
+
const title = `[pulse] ${payload.actorName} @ ${payload.installId.slice(0, 8)}`;
|
|
213
|
+
const body = formatPayloadBody(payload);
|
|
214
|
+
const created = restCreateIssue(this.repo, title, body, labels, this.seams);
|
|
215
|
+
issueNumber = issueNumberFromRecord(created);
|
|
216
|
+
issueUrl = issueUrlFromRecord(created);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
issueNumber = issueNumberFromRecord(existing);
|
|
220
|
+
issueUrl = issueUrlFromRecord(existing);
|
|
221
|
+
if (issueNumber !== null) {
|
|
222
|
+
restUpdateIssue(this.repo, issueNumber, { labels: mergeIssueLabels(existing, labels) }, this.seams);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (issueNumber === null) {
|
|
226
|
+
return { outcome: "sink-unreachable", message: "pulse standing issue missing number" };
|
|
227
|
+
}
|
|
228
|
+
restPostComment(this.repo, issueNumber, formatPulseComment(payload), this.seams);
|
|
229
|
+
return {
|
|
230
|
+
outcome: "submitted",
|
|
231
|
+
issueUrl,
|
|
232
|
+
issueNumber,
|
|
233
|
+
message: `pulse comment appended on #${issueNumber}`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** Append a Gap: comment on the standing pulse thread (#2693 D19). */
|
|
237
|
+
appendGapCommentOnPulse(payload, gapText) {
|
|
238
|
+
try {
|
|
239
|
+
const existing = findStandingIssue(this.repo, payload.installId, payload.actorName, "pulse", this.seams);
|
|
240
|
+
if (existing === null) {
|
|
241
|
+
return {
|
|
242
|
+
outcome: "sink-unreachable",
|
|
243
|
+
message: "no standing pulse issue for gap comment",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const num = issueNumberFromRecord(existing);
|
|
247
|
+
if (num === null) {
|
|
248
|
+
return { outcome: "sink-unreachable", message: "pulse issue missing number" };
|
|
249
|
+
}
|
|
250
|
+
const date = payload.collectedAt.slice(0, 10);
|
|
251
|
+
const body = `**Gap ${date}**\n\nGap: ${gapText}\n`;
|
|
252
|
+
restPostComment(this.repo, num, body, this.seams);
|
|
253
|
+
restUpdateIssue(this.repo, num, { labels: mergeIssueLabels(existing, [SURFACE_LABELS.pulse, "signal:gap"]) }, this.seams);
|
|
254
|
+
return {
|
|
255
|
+
outcome: "submitted",
|
|
256
|
+
issueUrl: issueUrlFromRecord(existing),
|
|
257
|
+
issueNumber: num,
|
|
258
|
+
message: `gap comment appended on pulse #${num}`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
if (err instanceof GhRestError) {
|
|
263
|
+
const outcome = classifySinkError(err.stderr, err.exitCode);
|
|
264
|
+
return {
|
|
265
|
+
outcome,
|
|
266
|
+
message: `gap comment soft-skip (${outcome}): ${err.stderr || err.message}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
270
|
+
return { outcome: "sink-unreachable", message };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=github-private-sink-adapter.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Headless / non-interactive detection (#2693 D16). */
|
|
2
|
+
export interface HeadlessDetectionOptions {
|
|
3
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
4
|
+
readonly stdinIsTTY?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** True when the session should fail-open without interactive prompts (#2693 D16). */
|
|
7
|
+
export declare function isHeadlessSession(options?: HeadlessDetectionOptions): boolean;
|
|
8
|
+
//# sourceMappingURL=headless.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Headless / non-interactive detection (#2693 D16). */
|
|
2
|
+
const CI_MARKERS = [
|
|
3
|
+
"CI",
|
|
4
|
+
"CONTINUOUS_INTEGRATION",
|
|
5
|
+
"GITHUB_ACTIONS",
|
|
6
|
+
"GITLAB_CI",
|
|
7
|
+
"BUILDKITE",
|
|
8
|
+
"CIRCLECI",
|
|
9
|
+
"JENKINS_URL",
|
|
10
|
+
"TF_BUILD",
|
|
11
|
+
"CURSOR_CLOUD_AGENT",
|
|
12
|
+
"DEFT_CLOUD_AGENT",
|
|
13
|
+
];
|
|
14
|
+
/** True when the session should fail-open without interactive prompts (#2693 D16). */
|
|
15
|
+
export function isHeadlessSession(options = {}) {
|
|
16
|
+
const env = options.env ?? process.env;
|
|
17
|
+
if (env.DEFT_HEADLESS === "1" || env.DEFT_HEADLESS === "true") {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
for (const key of CI_MARKERS) {
|
|
21
|
+
const val = env[key];
|
|
22
|
+
if (val !== undefined && val !== "" && val !== "0" && val !== "false") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY;
|
|
27
|
+
if (stdinIsTTY === false) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ProductSignalHarness = "cursor" | "cli" | "codex" | "opencode" | "other";
|
|
2
|
+
export interface InstallContext {
|
|
3
|
+
readonly installId: string;
|
|
4
|
+
readonly directiveVersion: string;
|
|
5
|
+
readonly os: string;
|
|
6
|
+
readonly osVersion: string;
|
|
7
|
+
readonly shell: string;
|
|
8
|
+
readonly harness: ProductSignalHarness;
|
|
9
|
+
readonly harnessVersion: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Collect shared install context for product-signal payloads (#2693 D7). */
|
|
12
|
+
export declare function collectInstallContext(projectRoot: string): InstallContext;
|
|
13
|
+
//# sourceMappingURL=install-context.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { release } from "node:os";
|
|
2
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
3
|
+
import { resolveInstallId } from "../events/attribution-enrichment.js";
|
|
4
|
+
import { detectEnvironmentContext } from "../platform/shell-context.js";
|
|
5
|
+
function detectHarness(env = process.env) {
|
|
6
|
+
const explicit = env.DEFT_HARNESS?.trim().toLowerCase();
|
|
7
|
+
if (explicit === "cursor" ||
|
|
8
|
+
explicit === "cli" ||
|
|
9
|
+
explicit === "codex" ||
|
|
10
|
+
explicit === "opencode" ||
|
|
11
|
+
explicit === "other") {
|
|
12
|
+
return explicit;
|
|
13
|
+
}
|
|
14
|
+
if (env.CURSOR_SESSION_ID || env.CURSOR_TRACE_ID) {
|
|
15
|
+
return "cursor";
|
|
16
|
+
}
|
|
17
|
+
if (env.CODEX_HOME || env.CODEX_SESSION) {
|
|
18
|
+
return "codex";
|
|
19
|
+
}
|
|
20
|
+
if (env.OPENCODE) {
|
|
21
|
+
return "opencode";
|
|
22
|
+
}
|
|
23
|
+
return "cli";
|
|
24
|
+
}
|
|
25
|
+
/** Collect shared install context for product-signal payloads (#2693 D7). */
|
|
26
|
+
export function collectInstallContext(projectRoot) {
|
|
27
|
+
const envCtx = detectEnvironmentContext();
|
|
28
|
+
const installId = resolveInstallId(projectRoot) ?? "unknown";
|
|
29
|
+
const harness = detectHarness();
|
|
30
|
+
const harnessVersion = process.env.DEFT_HARNESS_VERSION?.trim() || process.env.CURSOR_VERSION?.trim() || null;
|
|
31
|
+
return {
|
|
32
|
+
installId,
|
|
33
|
+
directiveVersion: readCorePackageVersion(),
|
|
34
|
+
os: envCtx.hostPlatform,
|
|
35
|
+
osVersion: release(),
|
|
36
|
+
shell: envCtx.shell.name,
|
|
37
|
+
harness,
|
|
38
|
+
harnessVersion,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=install-context.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type LocalSignalSummary } from "./payload.js";
|
|
2
|
+
export declare const DEFAULT_LOCAL_SIGNAL_WINDOW = "30d";
|
|
3
|
+
/** Assemble minimized local ledger summaries (#2693 D15). */
|
|
4
|
+
export declare function assembleLocalSignalSummary(projectRoot: string, options?: {
|
|
5
|
+
window?: string;
|
|
6
|
+
}): LocalSignalSummary;
|
|
7
|
+
//# sourceMappingURL=local-signal-summary.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { evaluateHealth } from "../eval/health.js";
|
|
3
|
+
import { healthMetricsHistoryPath, helpedMetricsHistoryPath, } from "../metrics/resolve-metrics-home.js";
|
|
4
|
+
import { resolveValueFeedback } from "../policy/value-feedback.js";
|
|
5
|
+
import { computeValueShowTrend } from "../value/readback.js";
|
|
6
|
+
import { LOCAL_SIGNAL_SUMMARY_SCHEMA_VERSION, } from "./payload.js";
|
|
7
|
+
export const DEFAULT_LOCAL_SIGNAL_WINDOW = "30d";
|
|
8
|
+
function parseWindowMs(window) {
|
|
9
|
+
const match = window.trim().match(/^(\d+)\s*(d|h|m)$/i);
|
|
10
|
+
if (match === null) {
|
|
11
|
+
return 30 * 86_400_000;
|
|
12
|
+
}
|
|
13
|
+
const amount = Number(match[1]);
|
|
14
|
+
const unit = match[2]?.toLowerCase();
|
|
15
|
+
if (unit === "d") {
|
|
16
|
+
return amount * 86_400_000;
|
|
17
|
+
}
|
|
18
|
+
if (unit === "h") {
|
|
19
|
+
return amount * 3_600_000;
|
|
20
|
+
}
|
|
21
|
+
return amount * 60_000;
|
|
22
|
+
}
|
|
23
|
+
function readLastJsonlRecord(path) {
|
|
24
|
+
if (!existsSync(path)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const lines = readFileSync(path, "utf8")
|
|
29
|
+
.split("\n")
|
|
30
|
+
.filter((line) => line.trim().length > 0);
|
|
31
|
+
const last = lines[lines.length - 1];
|
|
32
|
+
if (last === undefined) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(last);
|
|
36
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function buildValueFeedbackSummary(projectRoot, windowMs) {
|
|
46
|
+
const policy = resolveValueFeedback(projectRoot);
|
|
47
|
+
if (!policy.enabled) {
|
|
48
|
+
return {
|
|
49
|
+
enabled: false,
|
|
50
|
+
total: 0,
|
|
51
|
+
byClass: { value: 0, bypass: 0, adoption: 0, friction: 0 },
|
|
52
|
+
topEvents: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const trend = computeValueShowTrend(projectRoot, { windowMs });
|
|
56
|
+
const topEvents = Object.entries(trend.byEvent)
|
|
57
|
+
.sort(([, a], [, b]) => b - a)
|
|
58
|
+
.slice(0, 5)
|
|
59
|
+
.map(([name, count]) => ({ name, count }));
|
|
60
|
+
const byClass = {};
|
|
61
|
+
for (const key of ["value", "bypass", "adoption", "friction"]) {
|
|
62
|
+
byClass[key] = trend.byClass[key];
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
enabled: true,
|
|
66
|
+
total: trend.total,
|
|
67
|
+
byClass,
|
|
68
|
+
topEvents,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildEvalHealthSummary(projectRoot) {
|
|
72
|
+
const ledgerPath = healthMetricsHistoryPath(projectRoot);
|
|
73
|
+
if (ledgerPath !== null) {
|
|
74
|
+
const last = readLastJsonlRecord(ledgerPath);
|
|
75
|
+
if (last !== null && typeof last.score === "number") {
|
|
76
|
+
const contradictions = last.contradictions;
|
|
77
|
+
return {
|
|
78
|
+
score: last.score,
|
|
79
|
+
contradictionCount: Array.isArray(contradictions) ? contradictions.length : 0,
|
|
80
|
+
collectedAt: typeof last.recordedAt === "string" ? last.recordedAt : null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const result = evaluateHealth({ projectRoot, persist: false });
|
|
85
|
+
if (result.report === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
score: result.report.score,
|
|
90
|
+
contradictionCount: result.report.contradictions.length,
|
|
91
|
+
collectedAt: result.report.recordedAt,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildHelpedHealthSummary(projectRoot, window) {
|
|
95
|
+
const windowMs = parseWindowMs(window);
|
|
96
|
+
const since = Date.now() - windowMs;
|
|
97
|
+
const helpedPath = helpedMetricsHistoryPath(projectRoot);
|
|
98
|
+
let helpedCount = 0;
|
|
99
|
+
if (helpedPath !== null && existsSync(helpedPath)) {
|
|
100
|
+
try {
|
|
101
|
+
const lines = readFileSync(helpedPath, "utf8")
|
|
102
|
+
.split("\n")
|
|
103
|
+
.filter((l) => l.trim());
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
try {
|
|
106
|
+
const rec = JSON.parse(line);
|
|
107
|
+
if (rec !== null && typeof rec === "object" && !Array.isArray(rec)) {
|
|
108
|
+
const at = rec.recordedAt;
|
|
109
|
+
if (typeof at === "string") {
|
|
110
|
+
const ts = Date.parse(at);
|
|
111
|
+
if (!Number.isNaN(ts) && ts >= since) {
|
|
112
|
+
helpedCount += 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// skip bad line
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
helpedCount = 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const healthPath = healthMetricsHistoryPath(projectRoot);
|
|
127
|
+
let healthEntryCount = 0;
|
|
128
|
+
if (healthPath !== null && existsSync(healthPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const lines = readFileSync(healthPath, "utf8")
|
|
131
|
+
.split("\n")
|
|
132
|
+
.filter((l) => l.trim());
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
try {
|
|
135
|
+
const rec = JSON.parse(line);
|
|
136
|
+
if (rec !== null && typeof rec === "object" && !Array.isArray(rec)) {
|
|
137
|
+
const at = rec.recordedAt;
|
|
138
|
+
if (typeof at === "string") {
|
|
139
|
+
const ts = Date.parse(at);
|
|
140
|
+
if (!Number.isNaN(ts) && ts >= since) {
|
|
141
|
+
healthEntryCount += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// skip
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
healthEntryCount = 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
helpedCount: helpedPath === null ? null : helpedCount,
|
|
157
|
+
healthEntryCount: healthPath === null ? null : healthEntryCount,
|
|
158
|
+
window,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Assemble minimized local ledger summaries (#2693 D15). */
|
|
162
|
+
export function assembleLocalSignalSummary(projectRoot, options = {}) {
|
|
163
|
+
const window = options.window ?? DEFAULT_LOCAL_SIGNAL_WINDOW;
|
|
164
|
+
const windowMs = parseWindowMs(window);
|
|
165
|
+
return {
|
|
166
|
+
schemaVersion: LOCAL_SIGNAL_SUMMARY_SCHEMA_VERSION,
|
|
167
|
+
window,
|
|
168
|
+
valueFeedback: buildValueFeedbackSummary(projectRoot, windowMs),
|
|
169
|
+
evalHealth: buildEvalHealthSummary(projectRoot),
|
|
170
|
+
helpedHealth: buildHelpedHealthSummary(projectRoot, window),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=local-signal-summary.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ActorNameSource } from "./actor-name.js";
|
|
2
|
+
import type { ProductSignalHarness } from "./install-context.js";
|
|
3
|
+
export declare const PRODUCT_SIGNAL_PAYLOAD_SCHEMA_VERSION: 1;
|
|
4
|
+
export declare const LOCAL_SIGNAL_SUMMARY_SCHEMA_VERSION: 1;
|
|
5
|
+
export declare const SKILLS_SUMMARY_SCHEMA_VERSION: 1;
|
|
6
|
+
export declare const MAX_PAYLOAD_JSON_BYTES = 64000;
|
|
7
|
+
export declare const MAX_HUMAN_ANSWERS = 3;
|
|
8
|
+
export declare const MAX_ANSWER_LENGTH = 2000;
|
|
9
|
+
export declare const MAX_FREE_TEXT_LENGTH = 4000;
|
|
10
|
+
export declare const MAX_AGENT_NOTES_LENGTH = 2000;
|
|
11
|
+
export type ProductSignalSurface = "pulse" | "portrait";
|
|
12
|
+
export interface HumanAnswer {
|
|
13
|
+
readonly q: string;
|
|
14
|
+
readonly a: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProductSignalHuman {
|
|
17
|
+
readonly nps: number | null;
|
|
18
|
+
readonly answers: readonly HumanAnswer[];
|
|
19
|
+
readonly freeText: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface ValueFeedbackSummary {
|
|
22
|
+
readonly enabled: boolean;
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly byClass: {
|
|
25
|
+
readonly value: number;
|
|
26
|
+
readonly bypass: number;
|
|
27
|
+
readonly adoption: number;
|
|
28
|
+
readonly friction: number;
|
|
29
|
+
};
|
|
30
|
+
readonly topEvents: readonly {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly count: number;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
export interface EvalHealthSummary {
|
|
36
|
+
readonly score: number | null;
|
|
37
|
+
readonly contradictionCount: number | null;
|
|
38
|
+
readonly collectedAt: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface HelpedHealthSummary {
|
|
41
|
+
readonly helpedCount: number | null;
|
|
42
|
+
readonly healthEntryCount: number | null;
|
|
43
|
+
readonly window: string;
|
|
44
|
+
}
|
|
45
|
+
export interface LocalSignalSummary {
|
|
46
|
+
readonly schemaVersion: typeof LOCAL_SIGNAL_SUMMARY_SCHEMA_VERSION;
|
|
47
|
+
readonly window: string;
|
|
48
|
+
readonly valueFeedback: ValueFeedbackSummary | null;
|
|
49
|
+
readonly evalHealth: EvalHealthSummary | null;
|
|
50
|
+
readonly helpedHealth: HelpedHealthSummary | null;
|
|
51
|
+
}
|
|
52
|
+
export interface SkillsSummaryEntry {
|
|
53
|
+
readonly skill: string;
|
|
54
|
+
readonly useCount: number;
|
|
55
|
+
readonly viewCount: number;
|
|
56
|
+
readonly lastUsed: string | null;
|
|
57
|
+
}
|
|
58
|
+
export interface SkillsSummary {
|
|
59
|
+
readonly schemaVersion: typeof SKILLS_SUMMARY_SCHEMA_VERSION;
|
|
60
|
+
readonly top: readonly SkillsSummaryEntry[];
|
|
61
|
+
readonly skillCount: number;
|
|
62
|
+
}
|
|
63
|
+
export interface ProductSignalPayload {
|
|
64
|
+
readonly schemaVersion: typeof PRODUCT_SIGNAL_PAYLOAD_SCHEMA_VERSION;
|
|
65
|
+
readonly surface: ProductSignalSurface;
|
|
66
|
+
readonly installId: string;
|
|
67
|
+
readonly actorName: string;
|
|
68
|
+
readonly actorNameSource: ActorNameSource;
|
|
69
|
+
readonly directiveVersion: string;
|
|
70
|
+
readonly os: string;
|
|
71
|
+
readonly osVersion: string;
|
|
72
|
+
readonly shell: string;
|
|
73
|
+
readonly harness: ProductSignalHarness;
|
|
74
|
+
readonly harnessVersion: string | null;
|
|
75
|
+
readonly consentTier: string;
|
|
76
|
+
readonly consentSource: "user" | "org-policy" | "typed-override";
|
|
77
|
+
readonly consentVersion: string;
|
|
78
|
+
readonly human: ProductSignalHuman;
|
|
79
|
+
readonly agentNotes: string | null;
|
|
80
|
+
readonly localSignalSummary: LocalSignalSummary | null;
|
|
81
|
+
readonly skillsSummary: SkillsSummary | null;
|
|
82
|
+
readonly collectedAt: string;
|
|
83
|
+
}
|
|
84
|
+
/** Client-side payload validation before submit (#2693 D7). */
|
|
85
|
+
export declare function validateProductSignalPayload(payload: ProductSignalPayload): string[];
|
|
86
|
+
/** Optional #829 sidecar path (read-only attach; emit not implemented here). */
|
|
87
|
+
export declare const SKILLS_TELEMETRY_SIDECAR_REL: string;
|
|
88
|
+
/** Read minimized skillsSummary when local sidecar exists (#2693 D14). */
|
|
89
|
+
export declare function readSkillsSummarySidecar(projectRoot: string): SkillsSummary | null;
|
|
90
|
+
//# sourceMappingURL=payload.d.ts.map
|