@deftai/directive-core 0.80.0 → 0.81.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/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +139 -0
- package/dist/doctor/main.js +2 -1
- 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/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/scm/gh-rest.d.ts +3 -1
- package/dist/scm/gh-rest.js +22 -0
- package/package.json +3 -3
|
@@ -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
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
export const PRODUCT_SIGNAL_PAYLOAD_SCHEMA_VERSION = 1;
|
|
4
|
+
export const LOCAL_SIGNAL_SUMMARY_SCHEMA_VERSION = 1;
|
|
5
|
+
export const SKILLS_SUMMARY_SCHEMA_VERSION = 1;
|
|
6
|
+
export const MAX_PAYLOAD_JSON_BYTES = 64_000;
|
|
7
|
+
export const MAX_HUMAN_ANSWERS = 3;
|
|
8
|
+
export const MAX_ANSWER_LENGTH = 2_000;
|
|
9
|
+
export const MAX_FREE_TEXT_LENGTH = 4_000;
|
|
10
|
+
export const MAX_AGENT_NOTES_LENGTH = 2_000;
|
|
11
|
+
const SECRET_PATTERNS = [
|
|
12
|
+
/\bghp_[A-Za-z0-9]{20,}\b/,
|
|
13
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
14
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
15
|
+
/\bsk-[A-Za-z0-9]{20,}\b/,
|
|
16
|
+
/\bBearer\s+[A-Za-z0-9._-]{20,}\b/i,
|
|
17
|
+
/\bBEGIN (?:RSA |EC )?PRIVATE KEY\b/,
|
|
18
|
+
/\b\.env\b/,
|
|
19
|
+
];
|
|
20
|
+
function containsSecretHygieneViolation(text) {
|
|
21
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
22
|
+
if (pattern.test(text)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function isValidNps(value) {
|
|
29
|
+
if (value === null) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 10;
|
|
33
|
+
}
|
|
34
|
+
/** Client-side payload validation before submit (#2693 D7). */
|
|
35
|
+
export function validateProductSignalPayload(payload) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
if (payload.schemaVersion !== PRODUCT_SIGNAL_PAYLOAD_SCHEMA_VERSION) {
|
|
38
|
+
errors.push(`schemaVersion must be ${PRODUCT_SIGNAL_PAYLOAD_SCHEMA_VERSION}`);
|
|
39
|
+
}
|
|
40
|
+
if (payload.surface !== "pulse" && payload.surface !== "portrait") {
|
|
41
|
+
errors.push("surface must be pulse or portrait");
|
|
42
|
+
}
|
|
43
|
+
if (payload.installId.trim().length === 0) {
|
|
44
|
+
errors.push("installId is required");
|
|
45
|
+
}
|
|
46
|
+
if (payload.actorName.trim().length === 0) {
|
|
47
|
+
errors.push("actorName is required");
|
|
48
|
+
}
|
|
49
|
+
if (!isValidNps(payload.human.nps)) {
|
|
50
|
+
errors.push("human.nps must be null or integer 0-10");
|
|
51
|
+
}
|
|
52
|
+
if (payload.human.answers.length > MAX_HUMAN_ANSWERS) {
|
|
53
|
+
errors.push(`human.answers exceeds max ${MAX_HUMAN_ANSWERS}`);
|
|
54
|
+
}
|
|
55
|
+
for (const answer of payload.human.answers) {
|
|
56
|
+
if (answer.q.length === 0 || answer.a.length === 0) {
|
|
57
|
+
errors.push("human.answers entries require non-empty q and a");
|
|
58
|
+
}
|
|
59
|
+
if (answer.a.length > MAX_ANSWER_LENGTH) {
|
|
60
|
+
errors.push(`human answer exceeds max length ${MAX_ANSWER_LENGTH}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (payload.human.freeText !== null && payload.human.freeText.length > MAX_FREE_TEXT_LENGTH) {
|
|
64
|
+
errors.push(`human.freeText exceeds max length ${MAX_FREE_TEXT_LENGTH}`);
|
|
65
|
+
}
|
|
66
|
+
if (payload.agentNotes !== null && payload.agentNotes.length > MAX_AGENT_NOTES_LENGTH) {
|
|
67
|
+
errors.push(`agentNotes exceeds max length ${MAX_AGENT_NOTES_LENGTH}`);
|
|
68
|
+
}
|
|
69
|
+
const serialized = JSON.stringify(payload);
|
|
70
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_PAYLOAD_JSON_BYTES) {
|
|
71
|
+
errors.push(`payload exceeds max size ${MAX_PAYLOAD_JSON_BYTES} bytes`);
|
|
72
|
+
}
|
|
73
|
+
if (containsSecretHygieneViolation(serialized)) {
|
|
74
|
+
errors.push("payload failed secret-hygiene scan");
|
|
75
|
+
}
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
/** Optional #829 sidecar path (read-only attach; emit not implemented here). */
|
|
79
|
+
export const SKILLS_TELEMETRY_SIDECAR_REL = join(".deft-cache", "skills-telemetry.json");
|
|
80
|
+
/** Read minimized skillsSummary when local sidecar exists (#2693 D14). */
|
|
81
|
+
export function readSkillsSummarySidecar(projectRoot) {
|
|
82
|
+
const path = resolve(projectRoot, SKILLS_TELEMETRY_SIDECAR_REL);
|
|
83
|
+
if (!existsSync(path)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
88
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const rec = parsed;
|
|
92
|
+
const topRaw = rec.top;
|
|
93
|
+
if (!Array.isArray(topRaw)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const top = [];
|
|
97
|
+
for (const item of topRaw.slice(0, 10)) {
|
|
98
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const row = item;
|
|
102
|
+
const skill = typeof row.skill === "string" ? row.skill.trim() : "";
|
|
103
|
+
if (skill.length === 0) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
top.push({
|
|
107
|
+
skill,
|
|
108
|
+
useCount: typeof row.useCount === "number" ? row.useCount : 0,
|
|
109
|
+
viewCount: typeof row.viewCount === "number" ? row.viewCount : 0,
|
|
110
|
+
lastUsed: typeof row.lastUsed === "string" ? row.lastUsed : null,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const skillCount = typeof rec.skillCount === "number" ? rec.skillCount : top.length;
|
|
114
|
+
return {
|
|
115
|
+
schemaVersion: SKILLS_SUMMARY_SCHEMA_VERSION,
|
|
116
|
+
top,
|
|
117
|
+
skillCount,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=payload.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type GhRestSeams } from "../scm/gh-rest.js";
|
|
2
|
+
/** D20 minimum label bootstrap set (#2693). */
|
|
3
|
+
export declare const PRODUCT_SIGNAL_BOOTSTRAP_LABELS: readonly {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly color: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
}[];
|
|
8
|
+
export declare const PRODUCT_SIGNAL_SINK_README = "# deftai/product-signal\n\nInternal (org) operator inbox for consented Directive product-improvement signal (Phase 1 under epic #2603).\n\n## Standing threads\n\n- Thread key: `(installId, actorName)` \u2014 see issue body marker `<!-- deft:product-signal v1 ... -->`\n- **Portrait**: one open issue per key; body upserted in place (`surface:portrait`)\n- **Pulse**: dated comments appended on standing issue (`surface:pulse`)\n- **Gap notes**: `Gap:` comments on the pulse thread (`signal:gap` label when present)\n\n## Labels (bootstrapped)\n\n- `surface:pulse`, `surface:portrait`\n- `nps:promoter`, `nps:passive`, `nps:detractor`, `nps:none`\n- `signal:gap`\n- `directive:<major.minor>` \u2014 created on first use per directive version\n\nThis repo is an operator-readable inbox, not the long-term analytics warehouse (#2603).\n";
|
|
9
|
+
export interface BootstrapSinkResult {
|
|
10
|
+
readonly exitCode: 0 | 1 | 2;
|
|
11
|
+
readonly stdout: string;
|
|
12
|
+
readonly repo: string;
|
|
13
|
+
}
|
|
14
|
+
/** Bootstrap D20 labels (idempotent — ignores existing label 422). */
|
|
15
|
+
export declare function bootstrapProductSignalLabels(repo?: string, seams?: GhRestSeams): {
|
|
16
|
+
created: number;
|
|
17
|
+
skipped: number;
|
|
18
|
+
errors: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Create sink repo + labels via gh CLI (#2693 D6/D20).
|
|
22
|
+
* Creates as private then sets visibility to **internal** (org-visible, not public).
|
|
23
|
+
*/
|
|
24
|
+
export declare function bootstrapProductSignalSink(options?: {
|
|
25
|
+
repo?: string;
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
seams?: GhRestSeams;
|
|
28
|
+
}): BootstrapSinkResult;
|
|
29
|
+
//# sourceMappingURL=sink-bootstrap.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { DEFAULT_PRODUCT_SIGNAL_SINK_REPO } from "../policy/product-signal.js";
|
|
3
|
+
import { GhRestError, restCreateLabel } from "../scm/gh-rest.js";
|
|
4
|
+
/** D20 minimum label bootstrap set (#2693). */
|
|
5
|
+
export const PRODUCT_SIGNAL_BOOTSTRAP_LABELS = [
|
|
6
|
+
{ name: "surface:pulse", color: "1D76DB", description: "Standing pulse thread" },
|
|
7
|
+
{ name: "surface:portrait", color: "5319E7", description: "Standing portrait thread" },
|
|
8
|
+
{ name: "nps:promoter", color: "0E8A16", description: "NPS 9-10" },
|
|
9
|
+
{ name: "nps:passive", color: "FBCA04", description: "NPS 7-8" },
|
|
10
|
+
{ name: "nps:detractor", color: "D93F0B", description: "NPS 0-6" },
|
|
11
|
+
{ name: "nps:none", color: "C5DEF5", description: "No NPS score" },
|
|
12
|
+
{ name: "signal:gap", color: "B60205", description: "Gap comment on pulse thread" },
|
|
13
|
+
];
|
|
14
|
+
export const PRODUCT_SIGNAL_SINK_README = `# deftai/product-signal
|
|
15
|
+
|
|
16
|
+
Internal (org) operator inbox for consented Directive product-improvement signal (Phase 1 under epic #2603).
|
|
17
|
+
|
|
18
|
+
## Standing threads
|
|
19
|
+
|
|
20
|
+
- Thread key: \`(installId, actorName)\` — see issue body marker \`<!-- deft:product-signal v1 ... -->\`
|
|
21
|
+
- **Portrait**: one open issue per key; body upserted in place (\`surface:portrait\`)
|
|
22
|
+
- **Pulse**: dated comments appended on standing issue (\`surface:pulse\`)
|
|
23
|
+
- **Gap notes**: \`Gap:\` comments on the pulse thread (\`signal:gap\` label when present)
|
|
24
|
+
|
|
25
|
+
## Labels (bootstrapped)
|
|
26
|
+
|
|
27
|
+
- \`surface:pulse\`, \`surface:portrait\`
|
|
28
|
+
- \`nps:promoter\`, \`nps:passive\`, \`nps:detractor\`, \`nps:none\`
|
|
29
|
+
- \`signal:gap\`
|
|
30
|
+
- \`directive:<major.minor>\` — created on first use per directive version
|
|
31
|
+
|
|
32
|
+
This repo is an operator-readable inbox, not the long-term analytics warehouse (#2603).
|
|
33
|
+
`;
|
|
34
|
+
/** Bootstrap D20 labels (idempotent — ignores existing label 422). */
|
|
35
|
+
export function bootstrapProductSignalLabels(repo = DEFAULT_PRODUCT_SIGNAL_SINK_REPO, seams = {}) {
|
|
36
|
+
let created = 0;
|
|
37
|
+
let skipped = 0;
|
|
38
|
+
const errors = [];
|
|
39
|
+
for (const label of PRODUCT_SIGNAL_BOOTSTRAP_LABELS) {
|
|
40
|
+
try {
|
|
41
|
+
restCreateLabel(repo, label.name, label.color, label.description, seams);
|
|
42
|
+
created += 1;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof GhRestError) {
|
|
46
|
+
if (err.stderr.toLowerCase().includes("already_exists") || err.exitCode === 422) {
|
|
47
|
+
skipped += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
errors.push(`${label.name}: ${err.stderr || err.message}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
errors.push(`${label.name}: ${String(err)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { created, skipped, errors };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create sink repo + labels via gh CLI (#2693 D6/D20).
|
|
61
|
+
* Creates as private then sets visibility to **internal** (org-visible, not public).
|
|
62
|
+
*/
|
|
63
|
+
export function bootstrapProductSignalSink(options = {}) {
|
|
64
|
+
const repo = options.repo ?? DEFAULT_PRODUCT_SIGNAL_SINK_REPO;
|
|
65
|
+
if (options.dryRun) {
|
|
66
|
+
return {
|
|
67
|
+
exitCode: 0,
|
|
68
|
+
stdout: `[dry-run] would bootstrap internal sink ${repo} with README + ${PRODUCT_SIGNAL_BOOTSTRAP_LABELS.length} labels\n`,
|
|
69
|
+
repo,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const create = spawnSync("gh", [
|
|
73
|
+
"repo",
|
|
74
|
+
"create",
|
|
75
|
+
repo,
|
|
76
|
+
"--private",
|
|
77
|
+
"--description",
|
|
78
|
+
"Internal product-improvement signal inbox for Directive partners (Refs #2693)",
|
|
79
|
+
"--add-readme",
|
|
80
|
+
], { encoding: "utf8" });
|
|
81
|
+
if (create.status !== 0 && !String(create.stderr).toLowerCase().includes("already exists")) {
|
|
82
|
+
return {
|
|
83
|
+
exitCode: 2,
|
|
84
|
+
stdout: `sink bootstrap failed: ${create.stderr || create.stdout}\n`,
|
|
85
|
+
repo,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Org "internal" visibility: gh create has no --internal; set after create.
|
|
89
|
+
const visibility = spawnSync("gh", ["repo", "edit", repo, "--visibility", "internal", "--accept-visibility-change-consequences"], { encoding: "utf8" });
|
|
90
|
+
if (visibility.status !== 0) {
|
|
91
|
+
return {
|
|
92
|
+
exitCode: 2,
|
|
93
|
+
stdout: `sink repo created but failed to set visibility=internal: ${visibility.stderr || visibility.stdout}\n`,
|
|
94
|
+
repo,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const labelResult = bootstrapProductSignalLabels(repo, options.seams);
|
|
98
|
+
const lines = [
|
|
99
|
+
`product-signal sink ${repo} ready (visibility=internal).`,
|
|
100
|
+
`labels: created=${labelResult.created} skipped=${labelResult.skipped}`,
|
|
101
|
+
];
|
|
102
|
+
if (labelResult.errors.length > 0) {
|
|
103
|
+
lines.push(`label warnings: ${labelResult.errors.join("; ")}`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("", "Update repo README with standing-thread conventions if empty.");
|
|
106
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, repo };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=sink-bootstrap.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ProductSignalOutcome } from "./gates.js";
|
|
2
|
+
import type { ProductSignalPayload } from "./payload.js";
|
|
3
|
+
export interface SubmitResult {
|
|
4
|
+
readonly outcome: ProductSignalOutcome;
|
|
5
|
+
readonly issueUrl?: string | null;
|
|
6
|
+
readonly issueNumber?: number | null;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
}
|
|
9
|
+
/** Transport-pluggable submit seam (#2693 D5). */
|
|
10
|
+
export interface SubmitAdapter {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
submit(payload: ProductSignalPayload): Promise<SubmitResult>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=submit-adapter.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ProductSignalOutcome } from "./gates.js";
|
|
2
|
+
import { type ProductSignalHuman, type ProductSignalPayload, type ProductSignalSurface } from "./payload.js";
|
|
3
|
+
export declare const PRODUCT_SIGNAL_LAST_SUBMIT_REL: string;
|
|
4
|
+
export interface ProductSignalSubmitInput {
|
|
5
|
+
readonly surface: ProductSignalSurface;
|
|
6
|
+
readonly human?: Partial<ProductSignalHuman>;
|
|
7
|
+
readonly agentNotes?: string | null;
|
|
8
|
+
readonly gapText?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ProductSignalSubmitOptions extends ProductSignalSubmitInput {
|
|
11
|
+
readonly projectRoot?: string | null;
|
|
12
|
+
readonly dryRun?: boolean;
|
|
13
|
+
readonly json?: boolean;
|
|
14
|
+
readonly skipGates?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ProductSignalSubmitResult {
|
|
17
|
+
readonly outcome: ProductSignalOutcome;
|
|
18
|
+
readonly exitCode: 0 | 1 | 2;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly issueUrl?: string | null;
|
|
21
|
+
readonly payload?: ProductSignalPayload;
|
|
22
|
+
}
|
|
23
|
+
/** Assemble a versioned product-signal payload (#2693 D7). */
|
|
24
|
+
export declare function assembleProductSignalPayload(projectRoot: string, input: ProductSignalSubmitInput): ProductSignalPayload;
|
|
25
|
+
/** Submit product-signal through gates + adapter (#2693). */
|
|
26
|
+
export declare function submitProductSignal(options: ProductSignalSubmitOptions): Promise<ProductSignalSubmitResult>;
|
|
27
|
+
export declare function runProductSignalStatus(projectRoot: string | null): {
|
|
28
|
+
exitCode: 0;
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function runProductSignalEnable(projectRoot: string | null, confirm: boolean): {
|
|
32
|
+
exitCode: 0 | 1 | 2;
|
|
33
|
+
text: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function runProductSignalConsent(action: "grant" | "revoke"): {
|
|
36
|
+
exitCode: 0 | 1;
|
|
37
|
+
text: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function runProductSignalBootstrapSink(dryRun: boolean): {
|
|
40
|
+
exitCode: 0 | 1 | 2;
|
|
41
|
+
text: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function parseProductSignalSubmitArgs(argv: string[]): {
|
|
44
|
+
surface: ProductSignalSurface;
|
|
45
|
+
dryRun: boolean;
|
|
46
|
+
json: boolean;
|
|
47
|
+
projectRoot: string;
|
|
48
|
+
nps: number | null;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
/** CLI module entrypoint for dispatch (#2693). */
|
|
52
|
+
export declare function productSignalMain(argv?: string[]): Promise<number>;
|
|
53
|
+
export declare function mainEntry(argv?: string[]): Promise<number>;
|
|
54
|
+
//# sourceMappingURL=submit.d.ts.map
|