@deftai/directive-core 0.83.0 → 0.85.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/cache/fetch.d.ts +17 -0
- package/dist/cache/fetch.js +50 -0
- package/dist/cache/main.js +36 -2
- package/dist/cache/task-cache/constants.d.ts +4 -0
- package/dist/cache/task-cache/constants.js +4 -0
- package/dist/cache/task-cache/executor.d.ts +9 -0
- package/dist/cache/task-cache/executor.js +51 -0
- package/dist/cache/task-cache/hash.d.ts +17 -0
- package/dist/cache/task-cache/hash.js +92 -0
- package/dist/cache/task-cache/index.d.ts +14 -0
- package/dist/cache/task-cache/index.js +15 -0
- package/dist/cache/task-cache/lint.d.ts +4 -0
- package/dist/cache/task-cache/lint.js +55 -0
- package/dist/cache/task-cache/registry.d.ts +7 -0
- package/dist/cache/task-cache/registry.js +67 -0
- package/dist/cache/task-cache/store.d.ts +10 -0
- package/dist/cache/task-cache/store.js +48 -0
- package/dist/cache/task-cache/types.d.ts +48 -0
- package/dist/cache/task-cache/types.js +3 -0
- package/dist/check/cached-orchestrator.d.ts +16 -0
- package/dist/check/cached-orchestrator.js +76 -0
- package/dist/check/context.d.ts +30 -0
- package/dist/check/context.js +28 -0
- package/dist/check/gate-lists.d.ts +18 -0
- package/dist/check/gate-lists.js +68 -0
- package/dist/check/index.d.ts +4 -1
- package/dist/check/index.js +3 -0
- package/dist/check/orchestrator.d.ts +3 -45
- package/dist/check/orchestrator.js +8 -46
- package/dist/check/runner-detect.d.ts +20 -0
- package/dist/check/runner-detect.js +131 -0
- package/dist/doctor/checks.js +16 -1
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +8 -4
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +13 -4
- package/dist/doctor/npm-registry.d.ts +23 -0
- package/dist/doctor/npm-registry.js +128 -0
- package/dist/doctor/npm-view.d.ts +10 -0
- package/dist/doctor/npm-view.js +31 -0
- package/dist/doctor/payload-staleness.js +2 -9
- package/dist/doctor/signpost-checks.d.ts +2 -1
- package/dist/doctor/signpost-checks.js +2 -0
- package/dist/doctor/types.d.ts +9 -0
- package/dist/eval/readback.js +6 -1
- package/dist/eval-health-relocation/evaluate.js +15 -1
- package/dist/fs/projection-containment.js +1 -1
- package/dist/hooks/cursor-hooks.d.ts +6 -20
- package/dist/hooks/cursor-hooks.js +7 -92
- package/dist/hooks/dispatcher.d.ts +2 -0
- package/dist/hooks/dispatcher.js +48 -6
- package/dist/init-deposit/agent-hooks.d.ts +2 -2
- package/dist/init-deposit/agent-hooks.js +15 -37
- package/dist/init-deposit/gitignore.js +1 -0
- package/dist/init-deposit/hygiene.d.ts +22 -0
- package/dist/init-deposit/hygiene.js +136 -4
- package/dist/init-deposit/legacy-detect.d.ts +25 -1
- package/dist/init-deposit/legacy-detect.js +143 -11
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +18 -5
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +2 -0
- package/dist/policy/org-force-on-migration.d.ts +44 -0
- package/dist/policy/org-force-on-migration.js +240 -0
- package/dist/policy/product-signal.d.ts +3 -1
- package/dist/policy/product-signal.js +15 -4
- package/dist/policy/value-feedback.d.ts +18 -1
- package/dist/policy/value-feedback.js +78 -5
- package/dist/preflight-cache/evaluate.js +12 -1
- package/dist/product-signal/consent-prompt.d.ts +13 -0
- package/dist/product-signal/consent-prompt.js +30 -0
- package/dist/product-signal/submit.js +22 -1
- package/dist/render/index.d.ts +1 -0
- package/dist/render/index.js +1 -0
- package/dist/render/rule-map-template.d.ts +2 -0
- package/dist/render/rule-map-template.js +407 -0
- package/dist/render/rule-map.d.ts +2 -0
- package/dist/render/rule-map.js +703 -0
- package/dist/review-monitor/constants.d.ts +7 -1
- package/dist/review-monitor/constants.js +40 -14
- package/dist/review-monitor/github-lease.d.ts +45 -0
- package/dist/review-monitor/github-lease.js +106 -0
- package/dist/review-monitor/index.d.ts +2 -0
- package/dist/review-monitor/index.js +2 -0
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +152 -0
- package/dist/review-monitor/record.d.ts +61 -13
- package/dist/review-monitor/record.js +379 -132
- package/dist/review-monitor/verify.d.ts +2 -0
- package/dist/review-monitor/verify.js +32 -13
- package/dist/scm/gh-rest.d.ts +7 -1
- package/dist/scm/gh-rest.js +35 -0
- package/dist/scope/decompose.js +9 -3
- package/dist/session/git.d.ts +2 -0
- package/dist/session/git.js +14 -0
- package/dist/session/session-start.js +5 -0
- package/dist/session/verify-session-ritual.js +39 -5
- package/dist/swarm/routing-set-cli.js +16 -5
- package/dist/swarm/routing.d.ts +1 -1
- package/dist/swarm/routing.js +3 -1
- package/dist/task-surface/index.js +53 -13
- package/dist/value/readback.js +6 -1
- package/dist/xbrief-migrate/constants.d.ts +6 -0
- package/dist/xbrief-migrate/constants.js +6 -0
- package/dist/xbrief-migrate/index.d.ts +2 -2
- package/dist/xbrief-migrate/index.js +2 -2
- package/dist/xbrief-migrate/migrate-project.d.ts +7 -0
- package/dist/xbrief-migrate/migrate-project.js +18 -1
- package/package.json +7 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type OrgAutoEnableOptions } from "./value-feedback-autoenable.js";
|
|
2
|
+
/** Durable once-per-checkout marker for #2822 trusted-org install force-on. */
|
|
3
|
+
export declare const ORG_FORCE_ON_MARKER_REL: string;
|
|
4
|
+
export declare const ORG_FORCE_ON_MARKER_VERSION: 1;
|
|
5
|
+
export interface OrgForceOnMarker {
|
|
6
|
+
readonly version: typeof ORG_FORCE_ON_MARKER_VERSION;
|
|
7
|
+
readonly appliedAt: string;
|
|
8
|
+
readonly originOrg: string;
|
|
9
|
+
readonly valueFeedback: boolean;
|
|
10
|
+
readonly productSignal: boolean;
|
|
11
|
+
readonly directiveVersion: string;
|
|
12
|
+
}
|
|
13
|
+
/** Canonical typed block written by the one-time migration (#2822 Part A). */
|
|
14
|
+
export declare const FORCE_ON_VALUE_FEEDBACK_BLOCK: {
|
|
15
|
+
readonly enabled: true;
|
|
16
|
+
readonly emitEvents: true;
|
|
17
|
+
readonly sessionLine: true;
|
|
18
|
+
readonly upstreamPrompt: false;
|
|
19
|
+
};
|
|
20
|
+
/** Read the #2822 migration marker when present and valid. */
|
|
21
|
+
export declare function readOrgForceOnMarker(projectRoot: string): OrgForceOnMarker | null;
|
|
22
|
+
export interface RunOrgForceOnMigrationOptions {
|
|
23
|
+
readonly autoEnable?: OrgAutoEnableOptions;
|
|
24
|
+
readonly actor?: string;
|
|
25
|
+
readonly now?: Date;
|
|
26
|
+
}
|
|
27
|
+
export interface OrgForceOnMigrationResult {
|
|
28
|
+
readonly ran: boolean;
|
|
29
|
+
readonly skippedReason: string | null;
|
|
30
|
+
readonly valueFeedbackChanged: boolean;
|
|
31
|
+
readonly productSignalChanged: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One-time trusted-org install/upgrade force-on for valueFeedback + productSignal
|
|
35
|
+
* (#2822). Idempotent via durable marker; non-trusted orgs are unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runOrgForceOnMigration(projectRoot: string, options?: RunOrgForceOnMigrationOptions): OrgForceOnMigrationResult;
|
|
38
|
+
/** True when typed valueFeedback matches the #2822 force-on block shape. */
|
|
39
|
+
export declare function isForceOnValueFeedbackBlock(raw: unknown): boolean;
|
|
40
|
+
/** Resolve auditable source for valueFeedback after #2822 migration. */
|
|
41
|
+
export declare function valueFeedbackInstallForceOnSource(projectRoot: string, raw: unknown): "install-force-on" | null;
|
|
42
|
+
/** Resolve auditable source for productSignal after #2822 migration. */
|
|
43
|
+
export declare function productSignalInstallForceOnSource(projectRoot: string, raw: unknown): "install-force-on" | null;
|
|
44
|
+
//# sourceMappingURL=org-force-on-migration.d.ts.map
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
4
|
+
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
5
|
+
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
6
|
+
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
7
|
+
import { detectOriginOrg, isTrustedOrgAutoEnable, } from "./value-feedback-autoenable.js";
|
|
8
|
+
/** Durable once-per-checkout marker for #2822 trusted-org install force-on. */
|
|
9
|
+
export const ORG_FORCE_ON_MARKER_REL = join(".deft-cache", "org-force-on-v2822.json");
|
|
10
|
+
export const ORG_FORCE_ON_MARKER_VERSION = 1;
|
|
11
|
+
/** Canonical typed block written by the one-time migration (#2822 Part A). */
|
|
12
|
+
export const FORCE_ON_VALUE_FEEDBACK_BLOCK = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
emitEvents: true,
|
|
15
|
+
sessionLine: true,
|
|
16
|
+
upstreamPrompt: false,
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_PRODUCT_SIGNAL_SINK = "deftai/product-signal";
|
|
19
|
+
function markerPath(projectRoot) {
|
|
20
|
+
return resolve(projectRoot, ORG_FORCE_ON_MARKER_REL);
|
|
21
|
+
}
|
|
22
|
+
function parseMarker(raw) {
|
|
23
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const rec = raw;
|
|
27
|
+
if (rec.version !== ORG_FORCE_ON_MARKER_VERSION ||
|
|
28
|
+
typeof rec.appliedAt !== "string" ||
|
|
29
|
+
typeof rec.originOrg !== "string" ||
|
|
30
|
+
typeof rec.valueFeedback !== "boolean" ||
|
|
31
|
+
typeof rec.productSignal !== "boolean" ||
|
|
32
|
+
typeof rec.directiveVersion !== "string") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
version: ORG_FORCE_ON_MARKER_VERSION,
|
|
37
|
+
appliedAt: rec.appliedAt,
|
|
38
|
+
originOrg: rec.originOrg,
|
|
39
|
+
valueFeedback: rec.valueFeedback,
|
|
40
|
+
productSignal: rec.productSignal,
|
|
41
|
+
directiveVersion: rec.directiveVersion,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Read the #2822 migration marker when present and valid. */
|
|
45
|
+
export function readOrgForceOnMarker(projectRoot) {
|
|
46
|
+
const path = markerPath(projectRoot);
|
|
47
|
+
if (!existsSync(path)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return parseMarker(JSON.parse(readFileSync(path, "utf8")));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function writeOrgForceOnMarker(projectRoot, marker) {
|
|
58
|
+
const path = markerPath(projectRoot);
|
|
59
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
60
|
+
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
|
|
61
|
+
}
|
|
62
|
+
function readTypedBoolean(raw, key, fallback) {
|
|
63
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
const value = raw[key];
|
|
67
|
+
return typeof value === "boolean" ? value : fallback;
|
|
68
|
+
}
|
|
69
|
+
function valueFeedbackNeedsForceOn(raw) {
|
|
70
|
+
const enabled = readTypedBoolean(raw, "enabled", false);
|
|
71
|
+
if (!enabled) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const emitEvents = readTypedBoolean(raw, "emitEvents", true);
|
|
75
|
+
const sessionLine = readTypedBoolean(raw, "sessionLine", true);
|
|
76
|
+
const upstreamPrompt = readTypedBoolean(raw, "upstreamPrompt", false);
|
|
77
|
+
return !emitEvents || !sessionLine || upstreamPrompt;
|
|
78
|
+
}
|
|
79
|
+
function productSignalNeedsForceOn(raw) {
|
|
80
|
+
return !readTypedBoolean(raw, "enabled", false);
|
|
81
|
+
}
|
|
82
|
+
function ensurePolicyBlock(plan) {
|
|
83
|
+
migrateLegacyPolicyKey(plan);
|
|
84
|
+
const existing = plan[PLAN_POLICY_KEY];
|
|
85
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
86
|
+
if (existing === undefined) {
|
|
87
|
+
plan[PLAN_POLICY_KEY] = {};
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error("plan.policy is not an object");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return plan[PLAN_POLICY_KEY];
|
|
94
|
+
}
|
|
95
|
+
function writeMarkerForSkip(projectRoot, options, valueFeedback, productSignal) {
|
|
96
|
+
const originOrg = detectOriginOrg(projectRoot, options.autoEnable) ?? "unknown";
|
|
97
|
+
const now = options.now ?? new Date();
|
|
98
|
+
writeOrgForceOnMarker(projectRoot, {
|
|
99
|
+
version: ORG_FORCE_ON_MARKER_VERSION,
|
|
100
|
+
appliedAt: now.toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
101
|
+
originOrg,
|
|
102
|
+
valueFeedback,
|
|
103
|
+
productSignal,
|
|
104
|
+
directiveVersion: readCorePackageVersion(),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One-time trusted-org install/upgrade force-on for valueFeedback + productSignal
|
|
109
|
+
* (#2822). Idempotent via durable marker; non-trusted orgs are unchanged.
|
|
110
|
+
*/
|
|
111
|
+
export function runOrgForceOnMigration(projectRoot, options = {}) {
|
|
112
|
+
if (readOrgForceOnMarker(projectRoot) !== null) {
|
|
113
|
+
return {
|
|
114
|
+
ran: false,
|
|
115
|
+
skippedReason: "marker-present",
|
|
116
|
+
valueFeedbackChanged: false,
|
|
117
|
+
productSignalChanged: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (!isTrustedOrgAutoEnable(projectRoot, options.autoEnable)) {
|
|
121
|
+
return {
|
|
122
|
+
ran: false,
|
|
123
|
+
skippedReason: "non-trusted-org",
|
|
124
|
+
valueFeedbackChanged: false,
|
|
125
|
+
productSignalChanged: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const [data, err] = loadProjectDefinition(projectRoot);
|
|
129
|
+
if (data === null) {
|
|
130
|
+
return {
|
|
131
|
+
ran: false,
|
|
132
|
+
skippedReason: err ?? "project-definition-missing",
|
|
133
|
+
valueFeedbackChanged: false,
|
|
134
|
+
productSignalChanged: false,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
138
|
+
const policyObj = typeof policyBlock === "object" && policyBlock !== null && !Array.isArray(policyBlock)
|
|
139
|
+
? policyBlock
|
|
140
|
+
: {};
|
|
141
|
+
const vfRaw = "valueFeedback" in policyObj ? policyObj.valueFeedback : undefined;
|
|
142
|
+
const psRaw = "productSignal" in policyObj ? policyObj.productSignal : undefined;
|
|
143
|
+
const needsVf = valueFeedbackNeedsForceOn(vfRaw);
|
|
144
|
+
const needsPs = productSignalNeedsForceOn(psRaw);
|
|
145
|
+
if (!needsVf && !needsPs) {
|
|
146
|
+
writeMarkerForSkip(projectRoot, options, false, false);
|
|
147
|
+
return {
|
|
148
|
+
ran: false,
|
|
149
|
+
skippedReason: "already-enabled",
|
|
150
|
+
valueFeedbackChanged: false,
|
|
151
|
+
productSignalChanged: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const path = projectDefinitionPath(projectRoot);
|
|
155
|
+
let valueFeedbackChanged = false;
|
|
156
|
+
let productSignalChanged = false;
|
|
157
|
+
projectDefinitionMutationLock(projectRoot, () => {
|
|
158
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
159
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
160
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
161
|
+
}
|
|
162
|
+
const rootData = parsed;
|
|
163
|
+
if (typeof rootData.plan !== "object" ||
|
|
164
|
+
rootData.plan === null ||
|
|
165
|
+
Array.isArray(rootData.plan)) {
|
|
166
|
+
if (rootData.plan === undefined) {
|
|
167
|
+
rootData.plan = {};
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const plan = rootData.plan;
|
|
174
|
+
const policy = ensurePolicyBlock(plan);
|
|
175
|
+
const previousVf = policy.valueFeedback;
|
|
176
|
+
const previousPs = policy.productSignal;
|
|
177
|
+
if (needsVf) {
|
|
178
|
+
policy.valueFeedback = { ...FORCE_ON_VALUE_FEEDBACK_BLOCK };
|
|
179
|
+
valueFeedbackChanged = valueFeedbackNeedsForceOn(previousVf);
|
|
180
|
+
}
|
|
181
|
+
if (needsPs) {
|
|
182
|
+
const prevObj = typeof previousPs === "object" && previousPs !== null && !Array.isArray(previousPs)
|
|
183
|
+
? previousPs
|
|
184
|
+
: {};
|
|
185
|
+
const sinkRepo = typeof prevObj.sinkRepo === "string" && prevObj.sinkRepo.trim().length > 0
|
|
186
|
+
? prevObj.sinkRepo.trim()
|
|
187
|
+
: DEFAULT_PRODUCT_SIGNAL_SINK;
|
|
188
|
+
policy.productSignal = { enabled: true, sinkRepo };
|
|
189
|
+
productSignalChanged = productSignalNeedsForceOn(previousPs);
|
|
190
|
+
}
|
|
191
|
+
if (valueFeedbackChanged || productSignalChanged) {
|
|
192
|
+
atomicWriteProjectDefinition(path, rootData);
|
|
193
|
+
}
|
|
194
|
+
const actor = options.actor ?? "directive-update";
|
|
195
|
+
appendAuditLog(projectRoot, [
|
|
196
|
+
`actor=${actor}`,
|
|
197
|
+
"org-force-on-v2822",
|
|
198
|
+
`valueFeedback=${needsVf ? "forced-on" : "unchanged"}`,
|
|
199
|
+
`productSignal=${needsPs ? "forced-on" : "unchanged"}`,
|
|
200
|
+
`previousValueFeedback=${JSON.stringify(previousVf ?? null)}`,
|
|
201
|
+
`previousProductSignal=${JSON.stringify(previousPs ?? null)}`,
|
|
202
|
+
].join(" "));
|
|
203
|
+
writeMarkerForSkip(projectRoot, options, needsVf, needsPs);
|
|
204
|
+
return { changed: valueFeedbackChanged || productSignalChanged };
|
|
205
|
+
});
|
|
206
|
+
return {
|
|
207
|
+
ran: true,
|
|
208
|
+
skippedReason: null,
|
|
209
|
+
valueFeedbackChanged,
|
|
210
|
+
productSignalChanged,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** True when typed valueFeedback matches the #2822 force-on block shape. */
|
|
214
|
+
export function isForceOnValueFeedbackBlock(raw) {
|
|
215
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
const block = raw;
|
|
219
|
+
return (block.enabled === FORCE_ON_VALUE_FEEDBACK_BLOCK.enabled &&
|
|
220
|
+
readTypedBoolean(raw, "emitEvents", false) === FORCE_ON_VALUE_FEEDBACK_BLOCK.emitEvents &&
|
|
221
|
+
readTypedBoolean(raw, "sessionLine", false) === FORCE_ON_VALUE_FEEDBACK_BLOCK.sessionLine &&
|
|
222
|
+
readTypedBoolean(raw, "upstreamPrompt", true) === FORCE_ON_VALUE_FEEDBACK_BLOCK.upstreamPrompt);
|
|
223
|
+
}
|
|
224
|
+
/** Resolve auditable source for valueFeedback after #2822 migration. */
|
|
225
|
+
export function valueFeedbackInstallForceOnSource(projectRoot, raw) {
|
|
226
|
+
const marker = readOrgForceOnMarker(projectRoot);
|
|
227
|
+
if (marker === null || !marker.valueFeedback) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return isForceOnValueFeedbackBlock(raw) ? "install-force-on" : null;
|
|
231
|
+
}
|
|
232
|
+
/** Resolve auditable source for productSignal after #2822 migration. */
|
|
233
|
+
export function productSignalInstallForceOnSource(projectRoot, raw) {
|
|
234
|
+
const marker = readOrgForceOnMarker(projectRoot);
|
|
235
|
+
if (marker === null || !marker.productSignal) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return readTypedBoolean(raw, "enabled", false) ? "install-force-on" : null;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=org-force-on-migration.js.map
|
|
@@ -9,7 +9,7 @@ export interface ProductSignalConfig {
|
|
|
9
9
|
readonly enabled: boolean;
|
|
10
10
|
readonly sinkRepo: string;
|
|
11
11
|
}
|
|
12
|
-
export type ProductSignalSource = "typed" | "default" | "default-on-error";
|
|
12
|
+
export type ProductSignalSource = "typed" | "install-force-on" | "default" | "default-on-error";
|
|
13
13
|
export interface ProductSignalResolved extends ProductSignalConfig {
|
|
14
14
|
readonly source: ProductSignalSource;
|
|
15
15
|
readonly error: string | null;
|
|
@@ -17,6 +17,8 @@ export interface ProductSignalResolved extends ProductSignalConfig {
|
|
|
17
17
|
export declare const PRODUCT_SIGNAL_CAPABILITY_COST_DISCLOSURE: string;
|
|
18
18
|
/** Validate a `plan.policy.productSignal` payload. */
|
|
19
19
|
export declare function validateProductSignal(value: unknown): string[];
|
|
20
|
+
/** Resolve a typed `productSignal` block without install-force-on overlay. */
|
|
21
|
+
export declare function resolveProductSignalFromTypedBlock(raw: unknown): ProductSignalResolved;
|
|
20
22
|
/** Resolve `plan.policy.productSignal` from PROJECT-DEFINITION (#2693). */
|
|
21
23
|
export declare function resolveProductSignal(projectRoot: string): ProductSignalResolved;
|
|
22
24
|
/** Human-readable status line for CLI surfaces. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
3
|
+
import { productSignalInstallForceOnSource } from "./org-force-on-migration.js";
|
|
3
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
4
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
5
6
|
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
@@ -48,7 +49,8 @@ export function validateProductSignal(value) {
|
|
|
48
49
|
}
|
|
49
50
|
return errors;
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
/** Resolve a typed `productSignal` block without install-force-on overlay. */
|
|
53
|
+
export function resolveProductSignalFromTypedBlock(raw) {
|
|
52
54
|
const errors = validateProductSignal(raw);
|
|
53
55
|
if (errors.length > 0) {
|
|
54
56
|
return defaultResolved("default-on-error", errors[0] ?? "invalid productSignal block");
|
|
@@ -81,7 +83,13 @@ export function resolveProductSignal(projectRoot) {
|
|
|
81
83
|
!("productSignal" in policyBlock)) {
|
|
82
84
|
return defaultResolved("default");
|
|
83
85
|
}
|
|
84
|
-
|
|
86
|
+
const raw = policyBlock.productSignal;
|
|
87
|
+
const installSource = productSignalInstallForceOnSource(projectRoot, raw);
|
|
88
|
+
if (installSource !== null) {
|
|
89
|
+
const resolved = resolveProductSignalFromTypedBlock(raw);
|
|
90
|
+
return { ...resolved, source: installSource };
|
|
91
|
+
}
|
|
92
|
+
return resolveProductSignalFromTypedBlock(raw);
|
|
85
93
|
}
|
|
86
94
|
/** Human-readable status line for CLI surfaces. */
|
|
87
95
|
export function formatProductSignalStatusLine(policy) {
|
|
@@ -117,7 +125,10 @@ export function inspectProductSignal(data, projectRoot) {
|
|
|
117
125
|
}
|
|
118
126
|
return fieldFromResolved(defaultResolved("default"));
|
|
119
127
|
}
|
|
120
|
-
|
|
128
|
+
const raw = policyBlock.productSignal;
|
|
129
|
+
const installSource = projectRoot !== undefined ? productSignalInstallForceOnSource(projectRoot, raw) : null;
|
|
130
|
+
const resolved = resolveProductSignalFromTypedBlock(raw);
|
|
131
|
+
return fieldFromResolved(installSource !== null ? { ...resolved, source: installSource } : resolved);
|
|
121
132
|
}
|
|
122
133
|
/** Persist `productSignal.enabled=true` after capability-cost disclosure (#2693). */
|
|
123
134
|
export function enableProductSignal(projectRoot, options) {
|
|
@@ -168,7 +179,7 @@ export function enableProductSignal(projectRoot, options) {
|
|
|
168
179
|
? prevObj.sinkRepo.trim()
|
|
169
180
|
: DEFAULT_PRODUCT_SIGNAL_SINK_REPO);
|
|
170
181
|
const nextBlock = { enabled: true, sinkRepo };
|
|
171
|
-
const previousNormalized =
|
|
182
|
+
const previousNormalized = resolveProductSignalFromTypedBlock(previous);
|
|
172
183
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
173
184
|
previousNormalized.sinkRepo !== nextBlock.sinkRepo;
|
|
174
185
|
policyBlock.productSignal = nextBlock;
|
|
@@ -17,7 +17,7 @@ export interface ValueFeedbackConfig {
|
|
|
17
17
|
readonly sessionLine: boolean;
|
|
18
18
|
readonly upstreamPrompt: boolean;
|
|
19
19
|
}
|
|
20
|
-
export type ValueFeedbackSource = "typed" | "org-auto" | "default" | "default-on-error";
|
|
20
|
+
export type ValueFeedbackSource = "typed" | "org-auto" | "install-force-on" | "default" | "default-on-error";
|
|
21
21
|
export interface ValueFeedbackResolved extends ValueFeedbackConfig {
|
|
22
22
|
readonly source: ValueFeedbackSource;
|
|
23
23
|
readonly error: string | null;
|
|
@@ -25,6 +25,8 @@ export interface ValueFeedbackResolved extends ValueFeedbackConfig {
|
|
|
25
25
|
export declare const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE: string;
|
|
26
26
|
/** Validate a `plan.policy.valueFeedback` payload. */
|
|
27
27
|
export declare function validateValueFeedback(value: unknown): string[];
|
|
28
|
+
/** Resolve a typed `valueFeedback` block without org-auto / install-force-on layers. */
|
|
29
|
+
export declare function resolveValueFeedbackFromTypedBlock(raw: unknown): ValueFeedbackResolved;
|
|
28
30
|
export interface ResolveValueFeedbackOptions {
|
|
29
31
|
/** Test seam for origin-org auto-enable resolution (#2376). */
|
|
30
32
|
readonly autoEnable?: OrgAutoEnableOptions;
|
|
@@ -78,4 +80,19 @@ export interface EnableValueFeedbackResult {
|
|
|
78
80
|
}
|
|
79
81
|
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
80
82
|
export declare function enableValueFeedback(projectRoot: string, options: EnableValueFeedbackOptions): EnableValueFeedbackResult;
|
|
83
|
+
export interface ClearValueFeedbackOptions {
|
|
84
|
+
readonly actor?: string;
|
|
85
|
+
readonly note?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface ClearValueFeedbackResult {
|
|
88
|
+
readonly exitCode: 0 | 2;
|
|
89
|
+
readonly stdout: string;
|
|
90
|
+
readonly changed: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Remove the typed `valueFeedback` key so trusted-org resolution can return to
|
|
94
|
+
* org-auto (#2822). Does not delete the install-force-on marker — subsequent
|
|
95
|
+
* updates will not re-force against intentional opt-out.
|
|
96
|
+
*/
|
|
97
|
+
export declare function clearValueFeedback(projectRoot: string, options?: ClearValueFeedbackOptions): ClearValueFeedbackResult;
|
|
81
98
|
//# sourceMappingURL=value-feedback.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
3
|
+
import { valueFeedbackInstallForceOnSource } from "./org-force-on-migration.js";
|
|
3
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
4
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
5
6
|
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
@@ -77,7 +78,8 @@ export function validateValueFeedback(value) {
|
|
|
77
78
|
}
|
|
78
79
|
return errors;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
/** Resolve a typed `valueFeedback` block without org-auto / install-force-on layers. */
|
|
82
|
+
export function resolveValueFeedbackFromTypedBlock(raw) {
|
|
81
83
|
const errors = validateValueFeedback(raw);
|
|
82
84
|
if (errors.length > 0) {
|
|
83
85
|
return defaultResolved("default-on-error", errors[0] ?? "invalid valueFeedback block");
|
|
@@ -130,7 +132,13 @@ export function resolveValueFeedback(projectRoot, options = {}) {
|
|
|
130
132
|
}
|
|
131
133
|
return defaultResolved("default");
|
|
132
134
|
}
|
|
133
|
-
|
|
135
|
+
const raw = policyBlock.valueFeedback;
|
|
136
|
+
const installSource = valueFeedbackInstallForceOnSource(projectRoot, raw);
|
|
137
|
+
if (installSource !== null) {
|
|
138
|
+
const resolved = resolveValueFeedbackFromTypedBlock(raw);
|
|
139
|
+
return { ...resolved, source: installSource };
|
|
140
|
+
}
|
|
141
|
+
return resolveValueFeedbackFromTypedBlock(raw);
|
|
134
142
|
}
|
|
135
143
|
/** Master gate: when `enabled` is false, every downstream path is rejected. */
|
|
136
144
|
export function isValueFeedbackPathAllowed(path, policy) {
|
|
@@ -196,8 +204,10 @@ export function inspectValueFeedback(data, projectRoot, options = {}) {
|
|
|
196
204
|
}
|
|
197
205
|
return fieldFromResolved(defaultResolved("default"));
|
|
198
206
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
207
|
+
const raw = policyBlock.valueFeedback;
|
|
208
|
+
const installSource = projectRoot !== undefined ? valueFeedbackInstallForceOnSource(projectRoot, raw) : null;
|
|
209
|
+
const resolved = resolveValueFeedbackFromTypedBlock(raw);
|
|
210
|
+
return fieldFromResolved(installSource !== null ? { ...resolved, source: installSource } : resolved);
|
|
201
211
|
}
|
|
202
212
|
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
203
213
|
export function enableValueFeedback(projectRoot, options) {
|
|
@@ -259,7 +269,7 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
259
269
|
sessionLine: readPersistedSubFlag("sessionLine"),
|
|
260
270
|
upstreamPrompt: readPersistedSubFlag("upstreamPrompt"),
|
|
261
271
|
};
|
|
262
|
-
const previousNormalized =
|
|
272
|
+
const previousNormalized = resolveValueFeedbackFromTypedBlock(previous);
|
|
263
273
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
264
274
|
previousNormalized.emitEvents !== nextBlock.emitEvents ||
|
|
265
275
|
previousNormalized.sessionLine !== nextBlock.sessionLine ||
|
|
@@ -302,4 +312,67 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
302
312
|
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
303
313
|
}
|
|
304
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Remove the typed `valueFeedback` key so trusted-org resolution can return to
|
|
317
|
+
* org-auto (#2822). Does not delete the install-force-on marker — subsequent
|
|
318
|
+
* updates will not re-force against intentional opt-out.
|
|
319
|
+
*/
|
|
320
|
+
export function clearValueFeedback(projectRoot, options = {}) {
|
|
321
|
+
const path = projectDefinitionPath(projectRoot);
|
|
322
|
+
try {
|
|
323
|
+
const { changed } = projectDefinitionMutationLock(projectRoot, () => {
|
|
324
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
325
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
326
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
327
|
+
}
|
|
328
|
+
const data = parsed;
|
|
329
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
330
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
331
|
+
}
|
|
332
|
+
const plan = data.plan;
|
|
333
|
+
migrateLegacyPolicyKey(plan);
|
|
334
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
335
|
+
if (typeof existingPolicy !== "object" ||
|
|
336
|
+
existingPolicy === null ||
|
|
337
|
+
Array.isArray(existingPolicy)) {
|
|
338
|
+
return { changed: false };
|
|
339
|
+
}
|
|
340
|
+
const policyBlock = existingPolicy;
|
|
341
|
+
if (!("valueFeedback" in policyBlock)) {
|
|
342
|
+
return { changed: false };
|
|
343
|
+
}
|
|
344
|
+
const previous = policyBlock.valueFeedback;
|
|
345
|
+
delete policyBlock.valueFeedback;
|
|
346
|
+
atomicWriteProjectDefinition(path, data);
|
|
347
|
+
const actor = options.actor ?? policyColonInvocation("clear-value-feedback");
|
|
348
|
+
const note = options.note ?? "";
|
|
349
|
+
const parts = [
|
|
350
|
+
`actor=${actor}`,
|
|
351
|
+
"valueFeedback=cleared",
|
|
352
|
+
`previous=${JSON.stringify(previous ?? null)}`,
|
|
353
|
+
];
|
|
354
|
+
if (note) {
|
|
355
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
356
|
+
}
|
|
357
|
+
appendAuditLog(projectRoot, parts.join(" "));
|
|
358
|
+
return { changed: true };
|
|
359
|
+
});
|
|
360
|
+
const resolved = resolveValueFeedback(projectRoot);
|
|
361
|
+
const lines = [
|
|
362
|
+
`\u2713 ${FIELD_VALUE_FEEDBACK} typed key removed (resolution returns to org-auto/default).`,
|
|
363
|
+
changed
|
|
364
|
+
? " audit: meta/policy-changes.log updated."
|
|
365
|
+
: " no-op: typed key was already absent.",
|
|
366
|
+
formatValueFeedbackStatusLine(resolved),
|
|
367
|
+
];
|
|
368
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
372
|
+
if (message.includes("PROJECT-DEFINITION not found")) {
|
|
373
|
+
return { exitCode: 2, stdout: `\u274c ${message}\n`, changed: false };
|
|
374
|
+
}
|
|
375
|
+
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
305
378
|
//# sourceMappingURL=value-feedback.js.map
|
|
@@ -14,7 +14,7 @@ import { execFileSync } from "node:child_process";
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
15
|
import { join, relative } from "node:path";
|
|
16
16
|
import { isTriageCacheEmpty, maybeAutoPopulateEmptyCache } from "../cache/empty-populate.js";
|
|
17
|
-
import { probeCacheDrift } from "../cache/fetch.js";
|
|
17
|
+
import { probeCacheDrift, readOpenInventoryStamp, } from "../cache/fetch.js";
|
|
18
18
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
19
19
|
import { recoveryHintForStaleFailure } from "../session/cache-recovery.js";
|
|
20
20
|
export { recoveryHintForStaleFailure } from "../session/cache-recovery.js";
|
|
@@ -423,6 +423,17 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
423
423
|
minMetaPath = p;
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
+
// #2826: empty open inventory with a successful fetch stamp is fresh — not
|
|
427
|
+
// the never-fetched case that yields minFetchedAt=null / Infinity age.
|
|
428
|
+
if (minFetchedAt === null && rawCandidatePaths.length === 0 && resolvedRepo !== null) {
|
|
429
|
+
const stamp = readOpenInventoryStamp(cacheRoot, source, resolvedRepo);
|
|
430
|
+
if (stamp !== null) {
|
|
431
|
+
const parsed = new Date(stamp.fetched_at);
|
|
432
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
433
|
+
minFetchedAt = parsed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
426
437
|
const now = nowFn();
|
|
427
438
|
const ageMs = minFetchedAt !== null ? now.getTime() - minFetchedAt.getTime() : Infinity;
|
|
428
439
|
const ageH = ageMs / (1000 * 3600);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HeadlessDetectionOptions } from "./headless.js";
|
|
2
|
+
/** D17 first-time consent ask (#2693 / #2822). */
|
|
3
|
+
export declare const PRODUCT_SIGNAL_CONSENT_PROMPT: string;
|
|
4
|
+
export interface ProductSignalConsentEligibilityOptions extends HeadlessDetectionOptions {
|
|
5
|
+
readonly projectRoot: string;
|
|
6
|
+
}
|
|
7
|
+
/** True when an interactive session should surface the D17 consent ask (#2822 AC). */
|
|
8
|
+
export declare function isProductSignalConsentEligible(options: ProductSignalConsentEligibilityOptions): boolean;
|
|
9
|
+
/** Format the D17 consent ask for session start / status surfaces. */
|
|
10
|
+
export declare function formatProductSignalConsentPrompt(): string;
|
|
11
|
+
/** Emit the D17 ask when eligible; headless callers get an empty string (#2693 D16). */
|
|
12
|
+
export declare function maybeFormatProductSignalConsentPrompt(options: ProductSignalConsentEligibilityOptions): string;
|
|
13
|
+
//# sourceMappingURL=consent-prompt.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveProductSignal } from "../policy/product-signal.js";
|
|
2
|
+
import { isProductSignalConsented } from "./consent.js";
|
|
3
|
+
import { isHeadlessSession } from "./headless.js";
|
|
4
|
+
/** D17 first-time consent ask (#2693 / #2822). */
|
|
5
|
+
export const PRODUCT_SIGNAL_CONSENT_PROMPT = "May we collect usage metrics and related session signal from this install to improve Directive? " +
|
|
6
|
+
"This can include a short check-in plus minimized local summaries (value/health and related ledgers). " +
|
|
7
|
+
"Nothing is sent while this path is off or without your consent.\n\n" +
|
|
8
|
+
"If you are unsure, please check with your company before saying no — they are a Directive partner " +
|
|
9
|
+
"and may already expect this signal to be shared.\n\n" +
|
|
10
|
+
"Reply **yes** to consent (`task product-signal:consent -- --grant`), or **no** to decline.";
|
|
11
|
+
/** True when an interactive session should surface the D17 consent ask (#2822 AC). */
|
|
12
|
+
export function isProductSignalConsentEligible(options) {
|
|
13
|
+
if (isHeadlessSession(options)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const policy = resolveProductSignal(options.projectRoot);
|
|
17
|
+
if (!policy.enabled) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return !isProductSignalConsented({ env: options.env });
|
|
21
|
+
}
|
|
22
|
+
/** Format the D17 consent ask for session start / status surfaces. */
|
|
23
|
+
export function formatProductSignalConsentPrompt() {
|
|
24
|
+
return `[deft product-signal] First-time consent eligible:\n${PRODUCT_SIGNAL_CONSENT_PROMPT}\n`;
|
|
25
|
+
}
|
|
26
|
+
/** Emit the D17 ask when eligible; headless callers get an empty string (#2693 D16). */
|
|
27
|
+
export function maybeFormatProductSignalConsentPrompt(options) {
|
|
28
|
+
return isProductSignalConsentEligible(options) ? formatProductSignalConsentPrompt() : "";
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=consent-prompt.js.map
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
3
4
|
import { enableProductSignal, formatProductSignalStatusLine, resolveProductSignal, } from "../policy/product-signal.js";
|
|
4
5
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
5
6
|
import { resolveActorName } from "./actor-name.js";
|
|
6
7
|
import { authorizeProductSignalSink, grantProductSignalConsent, readProductSignalConsent, revokeProductSignalConsent, } from "./consent.js";
|
|
8
|
+
import { maybeFormatProductSignalConsentPrompt } from "./consent-prompt.js";
|
|
7
9
|
import { evaluateProductSignalGates } from "./gates.js";
|
|
8
10
|
import { GitHubPrivateSinkAdapter } from "./github-private-sink-adapter.js";
|
|
9
11
|
import { collectInstallContext } from "./install-context.js";
|
|
@@ -13,6 +15,7 @@ import { bootstrapProductSignalSink } from "./sink-bootstrap.js";
|
|
|
13
15
|
export const PRODUCT_SIGNAL_LAST_SUBMIT_REL = join(".deft-cache", "product-signal-last-submit.json");
|
|
14
16
|
function recordLastSubmit(projectRoot, outcome, url) {
|
|
15
17
|
const path = resolve(projectRoot, PRODUCT_SIGNAL_LAST_SUBMIT_REL);
|
|
18
|
+
assertWriteTargetSafe(projectRoot, path);
|
|
16
19
|
try {
|
|
17
20
|
mkdirSync(join(path, ".."), { recursive: true });
|
|
18
21
|
appendFileSync(path, `${JSON.stringify({
|
|
@@ -127,7 +130,21 @@ export async function submitProductSignal(options) {
|
|
|
127
130
|
const adapter = new GitHubPrivateSinkAdapter({ sinkRepo: policy.sinkRepo });
|
|
128
131
|
const result = await adapter.submit(payload, { gapText: options.gapText });
|
|
129
132
|
if (result.outcome === "submitted") {
|
|
130
|
-
|
|
133
|
+
try {
|
|
134
|
+
recordLastSubmit(root, result.outcome, result.issueUrl ?? null);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof ProjectionContainmentError) {
|
|
138
|
+
return {
|
|
139
|
+
outcome: "error-config",
|
|
140
|
+
exitCode: 2,
|
|
141
|
+
message: `${err.message}\n`,
|
|
142
|
+
issueUrl: result.issueUrl,
|
|
143
|
+
payload,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
149
|
const urlLine = result.issueUrl ? ` url=${result.issueUrl}` : "";
|
|
133
150
|
return {
|
|
@@ -150,6 +167,10 @@ export function runProductSignalStatus(projectRoot) {
|
|
|
150
167
|
`[deft product-signal] consented=${String(consent !== null)} configuredSink=${configuredSink} consentedSink=${sinkAuth.consentedSink ?? "none"} sinksMatch=${String(sinkAuth.sinksMatch)}`,
|
|
151
168
|
last ? `[deft product-signal] ${last}` : "[deft product-signal] last submit: none",
|
|
152
169
|
];
|
|
170
|
+
const consentPrompt = maybeFormatProductSignalConsentPrompt({ projectRoot: root });
|
|
171
|
+
if (consentPrompt.length > 0) {
|
|
172
|
+
lines.push(consentPrompt.trimEnd());
|
|
173
|
+
}
|
|
153
174
|
return { exitCode: 0, text: `${lines.join("\n")}\n` };
|
|
154
175
|
}
|
|
155
176
|
export function runProductSignalEnable(projectRoot, confirm) {
|
package/dist/render/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * as projectRender from "./project-render.js";
|
|
|
8
8
|
export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
|
|
9
9
|
export * as roadmapRender from "./roadmap-render.js";
|
|
10
10
|
export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRoadmap, renderRoadmapToBuffer, } from "./roadmap-render.js";
|
|
11
|
+
export { main as ruleMapMain } from "./rule-map.js";
|
|
11
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
12
13
|
export * as specRender from "./spec-render.js";
|
|
13
14
|
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|
package/dist/render/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * as projectRender from "./project-render.js";
|
|
|
8
8
|
export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
|
|
9
9
|
export * as roadmapRender from "./roadmap-render.js";
|
|
10
10
|
export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRoadmap, renderRoadmapToBuffer, } from "./roadmap-render.js";
|
|
11
|
+
export { main as ruleMapMain } from "./rule-map.js";
|
|
11
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
12
13
|
export * as specRender from "./spec-render.js";
|
|
13
14
|
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|