@deftai/directive-core 0.69.0 → 0.71.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/branch/evaluate.js +7 -0
- package/dist/deposit/contain.d.ts +41 -0
- package/dist/deposit/contain.js +113 -0
- package/dist/deposit/copy-tree.js +8 -2
- package/dist/doctor/checks.d.ts +11 -0
- package/dist/doctor/checks.js +75 -2
- package/dist/doctor/doctor-state.js +2 -2
- package/dist/doctor/main.js +54 -1
- package/dist/doctor/manifest.d.ts +20 -0
- package/dist/doctor/manifest.js +22 -0
- package/dist/doctor/types.d.ts +8 -0
- package/dist/eval/crud-telemetry.d.ts +59 -0
- package/dist/eval/crud-telemetry.js +307 -0
- package/dist/eval/health.d.ts +52 -0
- package/dist/eval/health.js +240 -0
- package/dist/eval/readback.d.ts +38 -0
- package/dist/eval/readback.js +209 -0
- package/dist/eval/report.d.ts +53 -0
- package/dist/eval/report.js +161 -0
- package/dist/eval/run.d.ts +79 -0
- package/dist/eval/run.js +309 -0
- package/dist/events/attribution-constants.d.ts +13 -0
- package/dist/events/attribution-constants.js +18 -0
- package/dist/events/attribution-ledger.d.ts +43 -0
- package/dist/events/attribution-ledger.js +61 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/gitignore.js +5 -5
- package/dist/init-deposit/init-deposit.js +6 -0
- package/dist/init-deposit/refresh.js +5 -0
- package/dist/intake/issue-ingest.d.ts +11 -0
- package/dist/intake/issue-ingest.js +86 -11
- package/dist/layout/resolve.d.ts +2 -2
- package/dist/layout/resolve.js +2 -2
- package/dist/lifecycle/events.js +2 -0
- package/dist/policy/index.d.ts +2 -1
- package/dist/policy/index.js +15 -2
- package/dist/policy/plan-extensions.d.ts +31 -0
- package/dist/policy/plan-extensions.js +45 -0
- package/dist/policy/value-feedback.d.ts +56 -0
- package/dist/policy/value-feedback.js +284 -0
- package/dist/policy/wip.d.ts +2 -2
- package/dist/policy/wip.js +2 -2
- package/dist/preflight-cache/evaluate.js +4 -3
- package/dist/scope/audit-log.js +3 -3
- package/dist/scope/transition.js +16 -5
- package/dist/session/session-start.js +20 -0
- package/dist/slice/record.js +3 -3
- package/dist/swarm/routing.js +10 -3
- package/dist/triage/actions/candidates-log.d.ts +4 -4
- package/dist/triage/actions/candidates-log.js +4 -7
- package/dist/triage/bootstrap/gitignore.d.ts +7 -3
- package/dist/triage/bootstrap/gitignore.js +67 -37
- package/dist/triage/bootstrap/index.d.ts +1 -1
- package/dist/triage/bootstrap/index.js +4 -3
- package/dist/triage/bulk/index.d.ts +11 -2
- package/dist/triage/bulk/index.js +43 -6
- package/dist/triage/cache-path.d.ts +39 -0
- package/dist/triage/cache-path.js +98 -0
- package/dist/triage/help/registry-data.d.ts +53 -20
- package/dist/triage/help/registry-data.js +104 -20
- package/dist/triage/index.d.ts +2 -0
- package/dist/triage/index.js +2 -0
- package/dist/triage/queue/audit.js +2 -2
- package/dist/triage/queue/cache.js +2 -2
- package/dist/triage/reconcile/reconcile.js +4 -3
- package/dist/triage/scope/cli.d.ts +2 -1
- package/dist/triage/scope/cli.js +30 -6
- package/dist/triage/scope/mutations-core.js +3 -2
- package/dist/triage/scope/mutations.d.ts +10 -0
- package/dist/triage/scope/mutations.js +22 -0
- package/dist/triage/subscribe/index.d.ts +1 -1
- package/dist/triage/subscribe/index.js +4 -3
- package/dist/triage/summary/index.d.ts +2 -2
- package/dist/triage/summary/index.js +4 -3
- package/dist/triage/summary/reconcilable.d.ts +1 -1
- package/dist/triage/summary/reconcilable.js +3 -2
- package/dist/triage/welcome/constants.d.ts +3 -4
- package/dist/triage/welcome/constants.js +3 -4
- package/dist/triage/welcome/index.d.ts +1 -0
- package/dist/triage/welcome/index.js +1 -0
- package/dist/triage/welcome/onboard.d.ts +35 -0
- package/dist/triage/welcome/onboard.js +94 -0
- package/dist/triage/welcome/prior-state.js +3 -2
- package/dist/triage/welcome/summary.js +5 -4
- package/dist/umbrella-current-shape/index.d.ts +25 -1
- package/dist/umbrella-current-shape/index.js +84 -4
- package/dist/value/adoption-emit.d.ts +14 -0
- package/dist/value/adoption-emit.js +67 -0
- package/dist/value/adoption-registry.d.ts +62 -0
- package/dist/value/adoption-registry.js +214 -0
- package/dist/value/feedback-file.d.ts +67 -0
- package/dist/value/feedback-file.js +353 -0
- package/dist/value/friction-emit.d.ts +10 -0
- package/dist/value/friction-emit.js +24 -0
- package/dist/value/readback.d.ts +86 -0
- package/dist/value/readback.js +503 -0
- package/dist/vbrief-build/project-definition-io.d.ts +6 -0
- package/dist/vbrief-build/project-definition-io.js +7 -2
- package/dist/vbrief-validate/decomposition.js +1 -1
- package/dist/vbrief-validate/precutover.js +7 -3
- package/dist/wip-cap/evaluate.js +3 -0
- package/package.json +35 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { cacheGet } from "../cache/operations.js";
|
|
4
|
+
import { scan } from "../cache/scanner.js";
|
|
4
5
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
5
6
|
import { call } from "../scm/call.js";
|
|
6
7
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
@@ -11,6 +12,27 @@ import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, LEGACY_VBRIEF_VERSION, MI
|
|
|
11
12
|
import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
|
|
12
13
|
import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
|
|
13
14
|
export const INGEST_STATUSES = ["proposed", "pending", "active"];
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when the quarantine scanner hard-fails (credential-shaped content) on
|
|
17
|
+
* an ingested issue body/comment thread (#2306). Ingest MUST fail closed: emit
|
|
18
|
+
* nothing and propagate a non-zero exit rather than persisting the xBRIEF.
|
|
19
|
+
*/
|
|
20
|
+
export class ScannerHardFailError extends Error {
|
|
21
|
+
issueNumber;
|
|
22
|
+
flags;
|
|
23
|
+
constructor(issueNumber, flags) {
|
|
24
|
+
const details = flags
|
|
25
|
+
.filter((f) => f.severity === "hard-fail")
|
|
26
|
+
.map((f) => f.detail)
|
|
27
|
+
.join("; ");
|
|
28
|
+
super(`issue:ingest refused #${issueNumber}: quarantine scanner hard-fail` +
|
|
29
|
+
(details.length > 0 ? ` (${details})` : "") +
|
|
30
|
+
" -- nothing written.");
|
|
31
|
+
this.name = "ScannerHardFailError";
|
|
32
|
+
this.issueNumber = issueNumber;
|
|
33
|
+
this.flags = flags;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
14
36
|
/** Enriched on issues after `fetchIssue` when the comment thread is non-empty (#2143). */
|
|
15
37
|
export const ISSUE_COMMENT_THREAD_KEY = "issueCommentThread";
|
|
16
38
|
const STATUS_MAP = {
|
|
@@ -311,7 +333,14 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
311
333
|
};
|
|
312
334
|
if (overviewSource.length > 0) {
|
|
313
335
|
warnBodyControlCharacters(number, overviewSource);
|
|
314
|
-
|
|
336
|
+
// #2306: quarantine-scan untrusted body + comment-thread content before it
|
|
337
|
+
// is persisted as agent-facing scope authority. Fail closed on a credential
|
|
338
|
+
// hard-fail; otherwise persist the fenced/quarantined transform.
|
|
339
|
+
const scanResult = scan(overviewSource);
|
|
340
|
+
if (!scanResult.passed) {
|
|
341
|
+
throw new ScannerHardFailError(number, scanResult.flags);
|
|
342
|
+
}
|
|
343
|
+
narratives.Overview = scanResult.transformed_content;
|
|
315
344
|
}
|
|
316
345
|
if (labelNames.length > 0) {
|
|
317
346
|
narratives.Labels = labelNames.join(", ");
|
|
@@ -369,6 +398,19 @@ export function fetchFromCache(repo, number, options = {}) {
|
|
|
369
398
|
if (typeof issue.html_url === "string" && issue.html_url.length > 0) {
|
|
370
399
|
issue.url = issue.html_url;
|
|
371
400
|
}
|
|
401
|
+
// #2306: consume the cache entry's SCANNED content.md (fenced/quarantined at
|
|
402
|
+
// cache-put) rather than the raw body, so the cache read path cannot bypass
|
|
403
|
+
// the quarantine transform. When content.md is absent (e.g. a credential
|
|
404
|
+
// hard-fail deleted it), the raw body falls through and is re-scanned in
|
|
405
|
+
// buildIssueVbrief.
|
|
406
|
+
if (result.contentPath !== null) {
|
|
407
|
+
try {
|
|
408
|
+
issue.body = readFileSync(result.contentPath, "utf8");
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// fall back to the raw body (re-scanned downstream)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
372
414
|
return issue;
|
|
373
415
|
}
|
|
374
416
|
catch {
|
|
@@ -514,9 +556,25 @@ export function ingestBulk(issues, options) {
|
|
|
514
556
|
created: [],
|
|
515
557
|
duplicate: [],
|
|
516
558
|
dryrun: [],
|
|
559
|
+
failed: [],
|
|
517
560
|
};
|
|
518
561
|
for (const issue of filtered) {
|
|
519
|
-
|
|
562
|
+
let ingested;
|
|
563
|
+
try {
|
|
564
|
+
ingested = ingestOne(issue, { ...options, existingRefs: refs });
|
|
565
|
+
}
|
|
566
|
+
catch (exc) {
|
|
567
|
+
// #2306: a per-issue quarantine hard-fail must not sink the whole batch;
|
|
568
|
+
// record it, emit nothing for that issue, and surface a non-zero exit
|
|
569
|
+
// upstream via the `failed` bucket.
|
|
570
|
+
if (exc instanceof ScannerHardFailError) {
|
|
571
|
+
summary.failed.push(`#${exc.issueNumber}`);
|
|
572
|
+
process.stderr.write(`${exc.message}\n`);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
throw exc;
|
|
576
|
+
}
|
|
577
|
+
const [result, path, _msg] = ingested;
|
|
520
578
|
const rel = path !== null ? path.replace(`${options.vbriefDir}/`, "").replace(/\\/g, "/") : "";
|
|
521
579
|
summary[result].push(rel);
|
|
522
580
|
if (result === "created" && path !== null) {
|
|
@@ -581,7 +639,8 @@ export function issueIngestMain(args) {
|
|
|
581
639
|
const created = summary.created;
|
|
582
640
|
const duplicate = summary.duplicate;
|
|
583
641
|
const dryrun = summary.dryrun;
|
|
584
|
-
|
|
642
|
+
const failed = summary.failed ?? [];
|
|
643
|
+
process.stdout.write(`issue:ingest bulk summary: ${created.length} created, ${duplicate.length} duplicate, ${dryrun.length} dry-run, ${failed.length} refused (total considered: ${summary.total})\n`);
|
|
585
644
|
for (const entry of created) {
|
|
586
645
|
process.stdout.write(` CREATED ${entry}\n`);
|
|
587
646
|
}
|
|
@@ -591,19 +650,35 @@ export function issueIngestMain(args) {
|
|
|
591
650
|
for (const entry of duplicate) {
|
|
592
651
|
process.stdout.write(` SKIP ${entry} (already has scope vBRIEF)\n`);
|
|
593
652
|
}
|
|
594
|
-
|
|
653
|
+
for (const entry of failed) {
|
|
654
|
+
process.stdout.write(` REFUSED ${entry} (quarantine scanner hard-fail; nothing written)\n`);
|
|
655
|
+
}
|
|
656
|
+
// #2306: fail closed on any quarantine hard-fail in the batch.
|
|
657
|
+
return failed.length > 0 ? 2 : 0;
|
|
595
658
|
}
|
|
596
659
|
const issue = fetchIssue(repo, args.number, { cwd: projectRoot });
|
|
597
660
|
if (issue === null) {
|
|
598
661
|
return 2;
|
|
599
662
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
663
|
+
let result;
|
|
664
|
+
let msg;
|
|
665
|
+
try {
|
|
666
|
+
[result, , msg] = ingestOne(issue, {
|
|
667
|
+
vbriefDir,
|
|
668
|
+
status,
|
|
669
|
+
repoUrl,
|
|
670
|
+
dryRun: args.dryRun,
|
|
671
|
+
cwd: projectRoot,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
catch (exc) {
|
|
675
|
+
// #2306: fail closed -- emit nothing, non-zero exit on a quarantine hard-fail.
|
|
676
|
+
if (exc instanceof ScannerHardFailError) {
|
|
677
|
+
process.stderr.write(`${exc.message}\n`);
|
|
678
|
+
return 2;
|
|
679
|
+
}
|
|
680
|
+
throw exc;
|
|
681
|
+
}
|
|
607
682
|
process.stdout.write(`${msg}\n`);
|
|
608
683
|
return result === "duplicate" ? 1 : 0;
|
|
609
684
|
}
|
package/dist/layout/resolve.d.ts
CHANGED
|
@@ -51,9 +51,9 @@ export declare function resolveLifecycleRoot(projectRoot: string): string;
|
|
|
51
51
|
* (e.g. `<root>/<xbrief|vbrief>/active`). Layout-aware (#2109 part 2a).
|
|
52
52
|
*/
|
|
53
53
|
export declare function resolveLifecycleFolder(projectRoot: string, folder: string): string;
|
|
54
|
-
/** Absolute path to the
|
|
54
|
+
/** Absolute path to the layout-aware `.eval/` directory for version-eval results (#1703). */
|
|
55
55
|
export declare function resolveEvalDir(projectRoot: string): string;
|
|
56
|
-
/** Absolute path
|
|
56
|
+
/** Absolute path under `.eval/` for version-eval artefacts (not triage working-set). */
|
|
57
57
|
export declare function resolveEvalPath(projectRoot: string, ...segments: string[]): string;
|
|
58
58
|
/** Absolute path to the resolved lifecycle `.audit` directory (#2109 part 2a). */
|
|
59
59
|
export declare function resolveAuditDir(projectRoot: string): string;
|
package/dist/layout/resolve.js
CHANGED
|
@@ -101,11 +101,11 @@ export function resolveLifecycleRoot(projectRoot) {
|
|
|
101
101
|
export function resolveLifecycleFolder(projectRoot, folder) {
|
|
102
102
|
return join(resolveLifecycleRoot(projectRoot), folder);
|
|
103
103
|
}
|
|
104
|
-
/** Absolute path to the
|
|
104
|
+
/** Absolute path to the layout-aware `.eval/` directory for version-eval results (#1703). */
|
|
105
105
|
export function resolveEvalDir(projectRoot) {
|
|
106
106
|
return join(resolveLifecycleRoot(projectRoot), ".eval");
|
|
107
107
|
}
|
|
108
|
-
/** Absolute path
|
|
108
|
+
/** Absolute path under `.eval/` for version-eval artefacts (not triage working-set). */
|
|
109
109
|
export function resolveEvalPath(projectRoot, ...segments) {
|
|
110
110
|
return join(resolveEvalDir(projectRoot), ...segments);
|
|
111
111
|
}
|
package/dist/lifecycle/events.js
CHANGED
|
@@ -3,10 +3,12 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { contentRoot } from "../content-root.js";
|
|
6
|
+
import { ATTRIBUTION_REQUIRED_PAYLOAD } from "../events/attribution-constants.js";
|
|
6
7
|
/** Default event log location (project-local). */
|
|
7
8
|
export const DEFAULT_EVENT_LOG = join(".deft-cache", "events.jsonl");
|
|
8
9
|
const BEHAVIORAL_CATEGORY = "behavioral";
|
|
9
10
|
const REQUIRED_BEHAVIORAL_PAYLOAD = {
|
|
11
|
+
...ATTRIBUTION_REQUIRED_PAYLOAD,
|
|
10
12
|
"session:interrupted": ["session_id", "reason"],
|
|
11
13
|
"session:resumed": ["session_id", "interrupted_id"],
|
|
12
14
|
"plan:approved": ["plan_ref", "approver"],
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./decisions.js";
|
|
|
5
5
|
export * from "./disclosure.js";
|
|
6
6
|
export * from "./plan-extensions.js";
|
|
7
7
|
export * from "./resolve.js";
|
|
8
|
+
export * from "./value-feedback.js";
|
|
8
9
|
export * from "./wip.js";
|
|
9
10
|
export declare const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
|
|
10
11
|
export declare const FIELD_WIP_CAP = "plan.policy.wipCap";
|
|
@@ -29,7 +30,7 @@ export interface PolicyField {
|
|
|
29
30
|
}
|
|
30
31
|
/** Walk registered inspectors and return one row per field (#1148). */
|
|
31
32
|
export declare function inspectAllPolicies(projectRoot: string): PolicyField[];
|
|
32
|
-
/** Look up a single registered field by canonical dotted-path name. */
|
|
33
|
+
/** Look up a single registered field by canonical dotted-path name (or CLI alias). */
|
|
33
34
|
export declare function inspectOnePolicy(name: string, projectRoot: string): PolicyField | null;
|
|
34
35
|
/** Return canonical names of every registered typed-policy field. */
|
|
35
36
|
export declare function registeredPolicyNames(): string[];
|
package/dist/policy/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readPlanPolicy } from "./plan-extensions.js";
|
|
2
2
|
import { coerceLegacyNarrative, LEGACY_NARRATIVE_KEY, loadProjectDefinition } from "./resolve.js";
|
|
3
|
+
import { FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, inspectValueFeedback, } from "./value-feedback.js";
|
|
3
4
|
import { DEFAULT_WIP_CAP } from "./wip.js";
|
|
4
5
|
export * from "./agents-md-advisory.js";
|
|
5
6
|
export * from "./autonomy.js";
|
|
@@ -8,6 +9,7 @@ export * from "./decisions.js";
|
|
|
8
9
|
export * from "./disclosure.js";
|
|
9
10
|
export * from "./plan-extensions.js";
|
|
10
11
|
export * from "./resolve.js";
|
|
12
|
+
export * from "./value-feedback.js";
|
|
11
13
|
export * from "./wip.js";
|
|
12
14
|
export const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
|
|
13
15
|
export const FIELD_WIP_CAP = "plan.policy.wipCap";
|
|
@@ -213,6 +215,15 @@ function inspectSwarmSubagentBackend(data) {
|
|
|
213
215
|
source: "default-on-error",
|
|
214
216
|
};
|
|
215
217
|
}
|
|
218
|
+
function inspectValueFeedbackField(data) {
|
|
219
|
+
const field = inspectValueFeedback(data);
|
|
220
|
+
return {
|
|
221
|
+
name: field.name,
|
|
222
|
+
current: field.current,
|
|
223
|
+
default: field.default,
|
|
224
|
+
source: field.source,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
216
227
|
const REGISTERED_POLICIES = [
|
|
217
228
|
inspectAllowDirectCommits,
|
|
218
229
|
inspectWipCap,
|
|
@@ -225,16 +236,18 @@ const REGISTERED_POLICIES = [
|
|
|
225
236
|
emptyIsTyped: true,
|
|
226
237
|
}),
|
|
227
238
|
inspectSwarmSubagentBackend,
|
|
239
|
+
inspectValueFeedbackField,
|
|
228
240
|
];
|
|
229
241
|
/** Walk registered inspectors and return one row per field (#1148). */
|
|
230
242
|
export function inspectAllPolicies(projectRoot) {
|
|
231
243
|
const [data] = loadProjectDefinition(projectRoot);
|
|
232
244
|
return REGISTERED_POLICIES.map((inspect) => inspect(data));
|
|
233
245
|
}
|
|
234
|
-
/** Look up a single registered field by canonical dotted-path name. */
|
|
246
|
+
/** Look up a single registered field by canonical dotted-path name (or CLI alias). */
|
|
235
247
|
export function inspectOnePolicy(name, projectRoot) {
|
|
248
|
+
const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS ? FIELD_VALUE_FEEDBACK : name;
|
|
236
249
|
for (const field of inspectAllPolicies(projectRoot)) {
|
|
237
|
-
if (field.name ===
|
|
250
|
+
if (field.name === normalized)
|
|
238
251
|
return field;
|
|
239
252
|
}
|
|
240
253
|
return null;
|
|
@@ -32,4 +32,35 @@ export declare function readPlanCompletedNote(plan: unknown): unknown;
|
|
|
32
32
|
* bare block alongside the namespaced one.
|
|
33
33
|
*/
|
|
34
34
|
export declare function migrateLegacyPolicyKey(plan: Record<string, unknown>): void;
|
|
35
|
+
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
36
|
+
export declare const SHADOWABLE_PLAN_EXTENSIONS: ReadonlyArray<{
|
|
37
|
+
readonly namespacedKey: string;
|
|
38
|
+
readonly legacyKey: string;
|
|
39
|
+
}>;
|
|
40
|
+
/** A plan-extension key whose bare form is silently shadowed by the namespaced form. */
|
|
41
|
+
export interface ShadowedPlanExtension {
|
|
42
|
+
/** The namespaced key that wins the read (e.g. `x-directive/policy`). */
|
|
43
|
+
readonly namespacedKey: string;
|
|
44
|
+
/** The bare legacy key that is silently ignored (e.g. `policy`). */
|
|
45
|
+
readonly legacyKey: string;
|
|
46
|
+
/**
|
|
47
|
+
* Best-effort list of sub-keys present in the shadowed bare object (e.g.
|
|
48
|
+
* `["triageScope", "wipCap"]`). Empty when the bare value is not an object.
|
|
49
|
+
*/
|
|
50
|
+
readonly shadowedSubKeys: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect every plan-extension key where a bare (legacy) block coexists with the
|
|
54
|
+
* namespaced form (#2301). Because `readPlanExtension` is namespace-first, the
|
|
55
|
+
* bare block is never read once the namespaced key exists -- edits to it take no
|
|
56
|
+
* effect. Detecting the coexistence lets callers emit a loud diagnostic instead
|
|
57
|
+
* of the silent no-op that the #2295 onboarding trap exhibited.
|
|
58
|
+
*/
|
|
59
|
+
export declare function detectShadowedPlanExtensions(plan: unknown): ShadowedPlanExtension[];
|
|
60
|
+
/**
|
|
61
|
+
* Render a human-readable, loud diagnostic for a single shadowed plan-extension
|
|
62
|
+
* key. The message is surface-agnostic (no leading tag) so each caller can
|
|
63
|
+
* prefix it (`[policy:show]`, a doctor finding, ...).
|
|
64
|
+
*/
|
|
65
|
+
export declare function describeShadowedPlanExtension(shadow: ShadowedPlanExtension): string;
|
|
35
66
|
//# sourceMappingURL=plan-extensions.d.ts.map
|
|
@@ -54,4 +54,49 @@ export function migrateLegacyPolicyKey(plan) {
|
|
|
54
54
|
delete plan[LEGACY_PLAN_POLICY_KEY];
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
/** The namespaced/legacy key pairs a bare block can silently shadow (#2301). */
|
|
58
|
+
export const SHADOWABLE_PLAN_EXTENSIONS = [
|
|
59
|
+
{ namespacedKey: PLAN_POLICY_KEY, legacyKey: LEGACY_PLAN_POLICY_KEY },
|
|
60
|
+
{ namespacedKey: PLAN_COMPLETED_NOTE_KEY, legacyKey: LEGACY_PLAN_COMPLETED_NOTE_KEY },
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* Detect every plan-extension key where a bare (legacy) block coexists with the
|
|
64
|
+
* namespaced form (#2301). Because `readPlanExtension` is namespace-first, the
|
|
65
|
+
* bare block is never read once the namespaced key exists -- edits to it take no
|
|
66
|
+
* effect. Detecting the coexistence lets callers emit a loud diagnostic instead
|
|
67
|
+
* of the silent no-op that the #2295 onboarding trap exhibited.
|
|
68
|
+
*/
|
|
69
|
+
export function detectShadowedPlanExtensions(plan) {
|
|
70
|
+
const planObj = asPlanObject(plan);
|
|
71
|
+
if (planObj === null) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const shadows = [];
|
|
75
|
+
for (const { namespacedKey, legacyKey } of SHADOWABLE_PLAN_EXTENSIONS) {
|
|
76
|
+
if (planObj[namespacedKey] === undefined || planObj[legacyKey] === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const legacyValue = planObj[legacyKey];
|
|
80
|
+
const shadowedSubKeys = typeof legacyValue === "object" && legacyValue !== null && !Array.isArray(legacyValue)
|
|
81
|
+
? Object.keys(legacyValue)
|
|
82
|
+
: [];
|
|
83
|
+
shadows.push({ namespacedKey, legacyKey, shadowedSubKeys });
|
|
84
|
+
}
|
|
85
|
+
return shadows;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Render a human-readable, loud diagnostic for a single shadowed plan-extension
|
|
89
|
+
* key. The message is surface-agnostic (no leading tag) so each caller can
|
|
90
|
+
* prefix it (`[policy:show]`, a doctor finding, ...).
|
|
91
|
+
*/
|
|
92
|
+
export function describeShadowedPlanExtension(shadow) {
|
|
93
|
+
const subKeys = shadow.shadowedSubKeys.length > 0
|
|
94
|
+
? ` Shadowed field(s): ${shadow.shadowedSubKeys
|
|
95
|
+
.map((k) => `plan.${shadow.legacyKey}.${k}`)
|
|
96
|
+
.join(", ")}.`
|
|
97
|
+
: "";
|
|
98
|
+
return (`bare \`plan.${shadow.legacyKey}\` coexists with namespaced \`plan.${shadow.namespacedKey}\`; ` +
|
|
99
|
+
`the bare block is IGNORED (namespaced-first read) so edits to it silently take no effect.${subKeys} ` +
|
|
100
|
+
`Fold its values into \`plan.${shadow.namespacedKey}\` and delete \`plan.${shadow.legacyKey}\`.`);
|
|
101
|
+
}
|
|
57
102
|
//# sourceMappingURL=plan-extensions.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Canonical registered policy field name (matches other FIELD_* dotted paths). */
|
|
2
|
+
export declare const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
|
|
3
|
+
/** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
|
|
4
|
+
export declare const FIELD_VALUE_FEEDBACK_CLI_ALIAS = "valueFeedback";
|
|
5
|
+
export declare const DEFAULT_VALUE_FEEDBACK_ENABLED = false;
|
|
6
|
+
/** Sub-flag defaults applied when the master flag is enabled (#1709 tiered-cost decision). */
|
|
7
|
+
export declare const VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED: {
|
|
8
|
+
readonly emitEvents: true;
|
|
9
|
+
readonly sessionLine: true;
|
|
10
|
+
readonly upstreamPrompt: false;
|
|
11
|
+
};
|
|
12
|
+
export type ValueFeedbackSubFlag = keyof typeof VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED;
|
|
13
|
+
export interface ValueFeedbackConfig {
|
|
14
|
+
readonly enabled: boolean;
|
|
15
|
+
readonly emitEvents: boolean;
|
|
16
|
+
readonly sessionLine: boolean;
|
|
17
|
+
readonly upstreamPrompt: boolean;
|
|
18
|
+
}
|
|
19
|
+
export type ValueFeedbackSource = "typed" | "default" | "default-on-error";
|
|
20
|
+
export interface ValueFeedbackResolved extends ValueFeedbackConfig {
|
|
21
|
+
readonly source: ValueFeedbackSource;
|
|
22
|
+
readonly error: string | null;
|
|
23
|
+
}
|
|
24
|
+
export declare const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE: string;
|
|
25
|
+
/** Validate a `plan.policy.valueFeedback` payload. */
|
|
26
|
+
export declare function validateValueFeedback(value: unknown): string[];
|
|
27
|
+
/** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
|
|
28
|
+
export declare function resolveValueFeedback(projectRoot: string): ValueFeedbackResolved;
|
|
29
|
+
/** Master gate: when `enabled` is false, every downstream path is rejected. */
|
|
30
|
+
export declare function isValueFeedbackPathAllowed(path: ValueFeedbackSubFlag, policy: ValueFeedbackResolved): boolean;
|
|
31
|
+
/** Resolved per-path gate booleans for policy:show and enable status output. */
|
|
32
|
+
export declare function valueFeedbackPathGates(policy: ValueFeedbackResolved): Record<ValueFeedbackSubFlag, boolean>;
|
|
33
|
+
/** Human-readable status line for CLI enable/show surfaces. */
|
|
34
|
+
export declare function formatValueFeedbackStatusLine(policy: ValueFeedbackResolved): string;
|
|
35
|
+
export interface ValueFeedbackPolicyField {
|
|
36
|
+
readonly name: typeof FIELD_VALUE_FEEDBACK;
|
|
37
|
+
readonly current: ValueFeedbackConfig;
|
|
38
|
+
readonly default: ValueFeedbackConfig;
|
|
39
|
+
readonly source: string;
|
|
40
|
+
}
|
|
41
|
+
/** Inspector row for `policy:show --field=valueFeedback`. */
|
|
42
|
+
export declare function inspectValueFeedback(data: Record<string, unknown> | null): ValueFeedbackPolicyField;
|
|
43
|
+
export interface EnableValueFeedbackOptions {
|
|
44
|
+
readonly confirm: boolean;
|
|
45
|
+
readonly actor?: string;
|
|
46
|
+
readonly note?: string;
|
|
47
|
+
readonly subFlags?: Partial<Record<ValueFeedbackSubFlag, boolean>>;
|
|
48
|
+
}
|
|
49
|
+
export interface EnableValueFeedbackResult {
|
|
50
|
+
readonly exitCode: 0 | 1 | 2;
|
|
51
|
+
readonly stdout: string;
|
|
52
|
+
readonly changed: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
55
|
+
export declare function enableValueFeedback(projectRoot: string, options: EnableValueFeedbackOptions): EnableValueFeedbackResult;
|
|
56
|
+
//# sourceMappingURL=value-feedback.d.ts.map
|