@deftai/directive-core 0.70.0 → 0.71.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/branch/evaluate.js +7 -0
- package/dist/doctor/doctor-state.js +2 -2
- 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 +15 -5
- package/dist/intake/candidates-log.d.ts +0 -1
- package/dist/intake/candidates-log.js +5 -2
- package/dist/intake/issue-ingest.d.ts +8 -0
- package/dist/intake/issue-ingest.js +19 -1
- package/dist/intake/reconcile-issues.js +9 -2
- package/dist/layout/resolve.d.ts +2 -2
- package/dist/layout/resolve.js +2 -2
- package/dist/lifecycle/events.js +2 -0
- package/dist/orchestration/subagent-monitor.js +2 -2
- package/dist/policy/index.d.ts +2 -1
- package/dist/policy/index.js +15 -2
- package/dist/policy/value-feedback.d.ts +56 -0
- package/dist/policy/value-feedback.js +284 -0
- 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/task-surface/index.js +4 -1
- package/dist/triage/actions/candidates-log.d.ts +4 -4
- package/dist/triage/actions/candidates-log.js +4 -7
- package/dist/triage/actions/index.js +8 -32
- package/dist/triage/bootstrap/gitignore.d.ts +9 -3
- package/dist/triage/bootstrap/gitignore.js +77 -40
- package/dist/triage/bootstrap/index.d.ts +1 -1
- package/dist/triage/bootstrap/index.js +4 -3
- package/dist/triage/bulk/index.js +2 -2
- package/dist/triage/cache-path.d.ts +41 -0
- package/dist/triage/cache-path.js +115 -0
- package/dist/triage/help/registry-data.d.ts +48 -15
- package/dist/triage/help/registry-data.js +86 -15
- 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/mutations-core.js +3 -2
- 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 +2 -2
- package/dist/triage/welcome/constants.js +2 -2
- package/dist/triage/welcome/prior-state.js +3 -2
- package/dist/triage/welcome/summary.js +5 -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-validate/decomposition.js +1 -1
- package/dist/wip-cap/evaluate.js +3 -0
- package/package.json +35 -3
package/dist/branch/evaluate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordBypassSignal, recordGateCatch } from "../events/attribution-ledger.js";
|
|
1
2
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
2
3
|
import { ENV_BYPASS, resolvePolicy } from "../policy/resolve.js";
|
|
3
4
|
import { currentBranch, GitNotFoundError } from "./git.js";
|
|
@@ -42,6 +43,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
42
43
|
const defaultBranches = options.defaultBranches ?? DEFAULT_BRANCHES;
|
|
43
44
|
const allowMissingProjectDefinition = options.allowMissingProjectDefinition ?? false;
|
|
44
45
|
if (setupExemptionActive()) {
|
|
46
|
+
recordBypassSignal(projectRoot, "verify:branch", `${ENV_SETUP_EXEMPTION}=1 setup-interview exemption`);
|
|
45
47
|
return {
|
|
46
48
|
exitCode: 0,
|
|
47
49
|
message: "✓ deft branch-protection: setup-interview exemption active " +
|
|
@@ -97,6 +99,9 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
97
99
|
}
|
|
98
100
|
const result = resolvePolicy(projectRoot);
|
|
99
101
|
if (result.allowDirectCommits) {
|
|
102
|
+
if (result.source === "env-bypass") {
|
|
103
|
+
recordBypassSignal(projectRoot, "verify:branch", `${ENV_BYPASS}=1 on default branch '${branch}'`);
|
|
104
|
+
}
|
|
100
105
|
return {
|
|
101
106
|
exitCode: 0,
|
|
102
107
|
message: `⚠ deft branch-protection: on default branch '${branch}', but ` +
|
|
@@ -105,6 +110,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
105
110
|
}
|
|
106
111
|
if (result.source === "default-fail-closed" && result.error !== null) {
|
|
107
112
|
if (allowMissingProjectDefinition && result.error.includes("not found")) {
|
|
113
|
+
recordBypassSignal(projectRoot, "verify:branch", "--allow-missing-project-definition bootstrap on default branch");
|
|
108
114
|
return {
|
|
109
115
|
exitCode: 0,
|
|
110
116
|
message: "✓ deft branch-protection: PROJECT-DEFINITION missing AND " +
|
|
@@ -128,6 +134,7 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
128
134
|
recovery,
|
|
129
135
|
};
|
|
130
136
|
}
|
|
137
|
+
recordGateCatch(projectRoot, "verify:branch", `blocked commit on default branch '${branch}'`);
|
|
131
138
|
return {
|
|
132
139
|
exitCode: 1,
|
|
133
140
|
message: buildBlockMessage(branch, result),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
4
4
|
import { CLEAN_WINDOW_HOURS, DIRTY_WINDOW_HOURS, ENV_STATE_PATH } from "./constants.js";
|
|
5
5
|
const STATE_FILENAME = "doctor-state.json";
|
|
6
6
|
export function statePath(projectRoot) {
|
|
@@ -10,7 +10,7 @@ export function statePath(projectRoot) {
|
|
|
10
10
|
? join(process.env.HOME ?? projectRoot, override.slice(1))
|
|
11
11
|
: override;
|
|
12
12
|
}
|
|
13
|
-
return
|
|
13
|
+
return resolveTriageCachePath(projectRoot, STATE_FILENAME);
|
|
14
14
|
}
|
|
15
15
|
function parseIso(ts) {
|
|
16
16
|
if (typeof ts !== "string" || !ts) {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare const CRUD_METRICS_HISTORY_REL = "results/crud-metrics.jsonl";
|
|
2
|
+
export type CrudOperation = "create" | "read" | "update" | "delete";
|
|
3
|
+
export type ByteDiffMinimality = "surgical" | "whole-file-rewrite";
|
|
4
|
+
export interface CrudOperationMetric {
|
|
5
|
+
readonly directiveVersion: string;
|
|
6
|
+
readonly operation: CrudOperation;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly schemaValid: boolean;
|
|
9
|
+
readonly schemaErrors: readonly string[];
|
|
10
|
+
readonly fieldInventionCount: number;
|
|
11
|
+
readonly inventedKeys: readonly string[];
|
|
12
|
+
readonly byteDiffMinimality: ByteDiffMinimality | null;
|
|
13
|
+
readonly byteDiffChangedRatio: number | null;
|
|
14
|
+
readonly recordedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CrudResult {
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
readonly content?: string;
|
|
19
|
+
readonly error?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface InstrumentedVbriefCrudOptions {
|
|
22
|
+
readonly directiveVersion?: string;
|
|
23
|
+
readonly now?: () => Date;
|
|
24
|
+
}
|
|
25
|
+
/** Whole-file rewrites change at least half of the bytes or re-serialize with high drift. */
|
|
26
|
+
export declare const BYTE_DIFF_WHOLE_FILE_THRESHOLD = 0.5;
|
|
27
|
+
/** Ratio of edit distance to the longer string length. */
|
|
28
|
+
export declare function computeChangedByteRatio(before: string, after: string): number;
|
|
29
|
+
/** Classify whether an update preserved bytes (surgical) or rewrote the file. */
|
|
30
|
+
export declare function classifyByteDiffMinimality(before: string, after: string): {
|
|
31
|
+
readonly kind: ByteDiffMinimality;
|
|
32
|
+
readonly changedRatio: number;
|
|
33
|
+
};
|
|
34
|
+
/** Absolute path to the versioned CRUD metrics ledger (#1703 Tier 1). */
|
|
35
|
+
export declare function crudMetricsHistoryPath(projectRoot: string): string;
|
|
36
|
+
/** Append CRUD operation metrics to the versioned ledger (#1703 Tier 1). */
|
|
37
|
+
export declare function persistCrudMetrics(projectRoot: string, metrics: readonly CrudOperationMetric[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Instrumented vBRIEF/xBRIEF CRUD chokepoint (#1703 Tier 1).
|
|
40
|
+
* Every operation emits a version-tagged metric covering schema validity,
|
|
41
|
+
* field invention, and (for updates) byte-diff minimality.
|
|
42
|
+
* Delete intentionally succeeds on schema-invalid files so corrupt artifacts can be removed.
|
|
43
|
+
*/
|
|
44
|
+
export declare class InstrumentedVbriefCrud {
|
|
45
|
+
private readonly directiveVersion;
|
|
46
|
+
private readonly now;
|
|
47
|
+
private readonly metrics;
|
|
48
|
+
constructor(options?: InstrumentedVbriefCrudOptions);
|
|
49
|
+
getMetrics(): readonly CrudOperationMetric[];
|
|
50
|
+
clearMetrics(): void;
|
|
51
|
+
create(path: string, content: string): CrudResult;
|
|
52
|
+
read(path: string): CrudResult;
|
|
53
|
+
update(path: string, content: string, options?: {
|
|
54
|
+
trustedWrite?: boolean;
|
|
55
|
+
}): CrudResult;
|
|
56
|
+
delete(path: string): CrudResult;
|
|
57
|
+
private recordMetric;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=crud-telemetry.d.ts.map
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { readCorePackageVersion } from "../engine-version.js";
|
|
4
|
+
import { resolveEvalPath } from "../layout/resolve.js";
|
|
5
|
+
import { scanVbrief } from "../vbrief-validate/conformance.js";
|
|
6
|
+
import { validateVbriefSchema } from "../vbrief-validate/schema.js";
|
|
7
|
+
export const CRUD_METRICS_HISTORY_REL = "results/crud-metrics.jsonl";
|
|
8
|
+
/** Whole-file rewrites change at least half of the bytes or re-serialize with high drift. */
|
|
9
|
+
export const BYTE_DIFF_WHOLE_FILE_THRESHOLD = 0.5;
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
/** Edit distance between two UTF-8 strings (Levenshtein, two-row). */
|
|
14
|
+
function levenshteinDistance(before, after) {
|
|
15
|
+
if (before === after) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (before.length === 0) {
|
|
19
|
+
return after.length;
|
|
20
|
+
}
|
|
21
|
+
if (after.length === 0) {
|
|
22
|
+
return before.length;
|
|
23
|
+
}
|
|
24
|
+
let prev = Array.from({ length: after.length + 1 }, (_, index) => index);
|
|
25
|
+
let curr = new Array(after.length + 1).fill(0);
|
|
26
|
+
for (let row = 1; row <= before.length; row += 1) {
|
|
27
|
+
curr[0] = row;
|
|
28
|
+
for (let col = 1; col <= after.length; col += 1) {
|
|
29
|
+
const cost = before[row - 1] === after[col - 1] ? 0 : 1;
|
|
30
|
+
const up = prev[col] ?? 0;
|
|
31
|
+
const left = curr[col - 1] ?? 0;
|
|
32
|
+
const diag = prev[col - 1] ?? 0;
|
|
33
|
+
curr[col] = Math.min(up + 1, left + 1, diag + cost);
|
|
34
|
+
}
|
|
35
|
+
[prev, curr] = [curr, prev];
|
|
36
|
+
}
|
|
37
|
+
return prev[after.length] ?? 0;
|
|
38
|
+
}
|
|
39
|
+
/** Ratio of edit distance to the longer string length. */
|
|
40
|
+
export function computeChangedByteRatio(before, after) {
|
|
41
|
+
const maxLen = Math.max(before.length, after.length);
|
|
42
|
+
if (maxLen === 0) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return levenshteinDistance(before, after) / maxLen;
|
|
46
|
+
}
|
|
47
|
+
/** Classify whether an update preserved bytes (surgical) or rewrote the file. */
|
|
48
|
+
export function classifyByteDiffMinimality(before, after) {
|
|
49
|
+
const changedRatio = computeChangedByteRatio(before, after);
|
|
50
|
+
try {
|
|
51
|
+
const oldParsed = JSON.parse(before);
|
|
52
|
+
const newParsed = JSON.parse(after);
|
|
53
|
+
if (JSON.stringify(oldParsed) === JSON.stringify(newParsed) && changedRatio > 0) {
|
|
54
|
+
return { kind: "whole-file-rewrite", changedRatio };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Fall through to byte-ratio classification.
|
|
59
|
+
}
|
|
60
|
+
if (changedRatio >= BYTE_DIFF_WHOLE_FILE_THRESHOLD) {
|
|
61
|
+
return { kind: "whole-file-rewrite", changedRatio };
|
|
62
|
+
}
|
|
63
|
+
return { kind: "surgical", changedRatio };
|
|
64
|
+
}
|
|
65
|
+
function assessDocument(path, parsed) {
|
|
66
|
+
if (!isRecord(parsed)) {
|
|
67
|
+
return {
|
|
68
|
+
schemaValid: false,
|
|
69
|
+
schemaErrors: [`${path}: document must be a JSON object`],
|
|
70
|
+
inventedKeys: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const schemaErrors = validateVbriefSchema(parsed, path);
|
|
74
|
+
const findings = scanVbrief(path, parsed);
|
|
75
|
+
const inventedKeys = findings.map((finding) => finding.key);
|
|
76
|
+
return {
|
|
77
|
+
schemaValid: schemaErrors.length === 0,
|
|
78
|
+
schemaErrors,
|
|
79
|
+
inventedKeys,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function sanitizeInlineMessage(message) {
|
|
83
|
+
return message.replace(/\r?\n/g, " ");
|
|
84
|
+
}
|
|
85
|
+
function ensureParentDir(path) {
|
|
86
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
/** Absolute path to the versioned CRUD metrics ledger (#1703 Tier 1). */
|
|
89
|
+
export function crudMetricsHistoryPath(projectRoot) {
|
|
90
|
+
return resolveEvalPath(projectRoot, CRUD_METRICS_HISTORY_REL);
|
|
91
|
+
}
|
|
92
|
+
/** Append CRUD operation metrics to the versioned ledger (#1703 Tier 1). */
|
|
93
|
+
export function persistCrudMetrics(projectRoot, metrics) {
|
|
94
|
+
if (metrics.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const path = crudMetricsHistoryPath(projectRoot);
|
|
98
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
99
|
+
for (const metric of metrics) {
|
|
100
|
+
appendFileSync(path, `${JSON.stringify(metric)}\n`, "utf8");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Instrumented vBRIEF/xBRIEF CRUD chokepoint (#1703 Tier 1).
|
|
105
|
+
* Every operation emits a version-tagged metric covering schema validity,
|
|
106
|
+
* field invention, and (for updates) byte-diff minimality.
|
|
107
|
+
* Delete intentionally succeeds on schema-invalid files so corrupt artifacts can be removed.
|
|
108
|
+
*/
|
|
109
|
+
export class InstrumentedVbriefCrud {
|
|
110
|
+
directiveVersion;
|
|
111
|
+
now;
|
|
112
|
+
metrics = [];
|
|
113
|
+
constructor(options = {}) {
|
|
114
|
+
this.directiveVersion = options.directiveVersion ?? readCorePackageVersion();
|
|
115
|
+
this.now = options.now ?? (() => new Date());
|
|
116
|
+
}
|
|
117
|
+
getMetrics() {
|
|
118
|
+
return this.metrics;
|
|
119
|
+
}
|
|
120
|
+
clearMetrics() {
|
|
121
|
+
this.metrics.length = 0;
|
|
122
|
+
}
|
|
123
|
+
create(path, content) {
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(content);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const message = sanitizeInlineMessage(err instanceof Error ? err.message : String(err));
|
|
130
|
+
this.recordMetric({
|
|
131
|
+
operation: "create",
|
|
132
|
+
path,
|
|
133
|
+
schemaValid: false,
|
|
134
|
+
schemaErrors: [`${path}: invalid JSON -- ${message}`],
|
|
135
|
+
inventedKeys: [],
|
|
136
|
+
byteDiffMinimality: null,
|
|
137
|
+
byteDiffChangedRatio: null,
|
|
138
|
+
});
|
|
139
|
+
return { ok: false, error: message };
|
|
140
|
+
}
|
|
141
|
+
const assessment = assessDocument(path, parsed);
|
|
142
|
+
this.recordMetric({
|
|
143
|
+
operation: "create",
|
|
144
|
+
path,
|
|
145
|
+
schemaValid: assessment.schemaValid,
|
|
146
|
+
schemaErrors: assessment.schemaErrors,
|
|
147
|
+
inventedKeys: assessment.inventedKeys,
|
|
148
|
+
byteDiffMinimality: null,
|
|
149
|
+
byteDiffChangedRatio: null,
|
|
150
|
+
});
|
|
151
|
+
if (!assessment.schemaValid) {
|
|
152
|
+
return { ok: false, error: assessment.schemaErrors.join("; ") };
|
|
153
|
+
}
|
|
154
|
+
ensureParentDir(path);
|
|
155
|
+
writeFileSync(path, content, "utf8");
|
|
156
|
+
return { ok: true };
|
|
157
|
+
}
|
|
158
|
+
read(path) {
|
|
159
|
+
if (!existsSync(path)) {
|
|
160
|
+
this.recordMetric({
|
|
161
|
+
operation: "read",
|
|
162
|
+
path,
|
|
163
|
+
schemaValid: false,
|
|
164
|
+
schemaErrors: [`${path}: file not found`],
|
|
165
|
+
inventedKeys: [],
|
|
166
|
+
byteDiffMinimality: null,
|
|
167
|
+
byteDiffChangedRatio: null,
|
|
168
|
+
});
|
|
169
|
+
return { ok: false, error: "ENOENT" };
|
|
170
|
+
}
|
|
171
|
+
const content = readFileSync(path, "utf8");
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(content);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const message = sanitizeInlineMessage(err instanceof Error ? err.message : String(err));
|
|
178
|
+
this.recordMetric({
|
|
179
|
+
operation: "read",
|
|
180
|
+
path,
|
|
181
|
+
schemaValid: false,
|
|
182
|
+
schemaErrors: [`${path}: invalid JSON -- ${message}`],
|
|
183
|
+
inventedKeys: [],
|
|
184
|
+
byteDiffMinimality: null,
|
|
185
|
+
byteDiffChangedRatio: null,
|
|
186
|
+
});
|
|
187
|
+
return { ok: false, error: message };
|
|
188
|
+
}
|
|
189
|
+
const assessment = assessDocument(path, parsed);
|
|
190
|
+
this.recordMetric({
|
|
191
|
+
operation: "read",
|
|
192
|
+
path,
|
|
193
|
+
schemaValid: assessment.schemaValid,
|
|
194
|
+
schemaErrors: assessment.schemaErrors,
|
|
195
|
+
inventedKeys: assessment.inventedKeys,
|
|
196
|
+
byteDiffMinimality: null,
|
|
197
|
+
byteDiffChangedRatio: null,
|
|
198
|
+
});
|
|
199
|
+
if (!assessment.schemaValid) {
|
|
200
|
+
return { ok: false, error: assessment.schemaErrors.join("; ") };
|
|
201
|
+
}
|
|
202
|
+
return { ok: true, content };
|
|
203
|
+
}
|
|
204
|
+
update(path, content, options = {}) {
|
|
205
|
+
const previousBytes = options.trustedWrite || !existsSync(path) ? null : readFileSync(path, "utf8");
|
|
206
|
+
let parsed;
|
|
207
|
+
try {
|
|
208
|
+
parsed = JSON.parse(content);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const message = sanitizeInlineMessage(err instanceof Error ? err.message : String(err));
|
|
212
|
+
this.recordMetric({
|
|
213
|
+
operation: "update",
|
|
214
|
+
path,
|
|
215
|
+
schemaValid: false,
|
|
216
|
+
schemaErrors: [`${path}: invalid JSON -- ${message}`],
|
|
217
|
+
inventedKeys: [],
|
|
218
|
+
byteDiffMinimality: null,
|
|
219
|
+
byteDiffChangedRatio: null,
|
|
220
|
+
});
|
|
221
|
+
return { ok: false, error: message };
|
|
222
|
+
}
|
|
223
|
+
const assessment = assessDocument(path, parsed);
|
|
224
|
+
let byteDiffMinimality = null;
|
|
225
|
+
let byteDiffChangedRatio = null;
|
|
226
|
+
if (previousBytes !== null) {
|
|
227
|
+
const diff = classifyByteDiffMinimality(previousBytes, content);
|
|
228
|
+
byteDiffMinimality = diff.kind;
|
|
229
|
+
byteDiffChangedRatio = diff.changedRatio;
|
|
230
|
+
}
|
|
231
|
+
this.recordMetric({
|
|
232
|
+
operation: "update",
|
|
233
|
+
path,
|
|
234
|
+
schemaValid: assessment.schemaValid,
|
|
235
|
+
schemaErrors: assessment.schemaErrors,
|
|
236
|
+
inventedKeys: assessment.inventedKeys,
|
|
237
|
+
byteDiffMinimality,
|
|
238
|
+
byteDiffChangedRatio,
|
|
239
|
+
});
|
|
240
|
+
if (!assessment.schemaValid && !options.trustedWrite) {
|
|
241
|
+
return { ok: false, error: assessment.schemaErrors.join("; ") };
|
|
242
|
+
}
|
|
243
|
+
ensureParentDir(path);
|
|
244
|
+
writeFileSync(path, content, "utf8");
|
|
245
|
+
return { ok: true };
|
|
246
|
+
}
|
|
247
|
+
delete(path) {
|
|
248
|
+
if (!existsSync(path)) {
|
|
249
|
+
this.recordMetric({
|
|
250
|
+
operation: "delete",
|
|
251
|
+
path,
|
|
252
|
+
schemaValid: false,
|
|
253
|
+
schemaErrors: [`${path}: file not found`],
|
|
254
|
+
inventedKeys: [],
|
|
255
|
+
byteDiffMinimality: null,
|
|
256
|
+
byteDiffChangedRatio: null,
|
|
257
|
+
});
|
|
258
|
+
return { ok: false, error: "ENOENT" };
|
|
259
|
+
}
|
|
260
|
+
const content = readFileSync(path, "utf8");
|
|
261
|
+
let parsed;
|
|
262
|
+
try {
|
|
263
|
+
parsed = JSON.parse(content);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
const message = sanitizeInlineMessage(err instanceof Error ? err.message : String(err));
|
|
267
|
+
this.recordMetric({
|
|
268
|
+
operation: "delete",
|
|
269
|
+
path,
|
|
270
|
+
schemaValid: false,
|
|
271
|
+
schemaErrors: [`${path}: invalid JSON -- ${message}`],
|
|
272
|
+
inventedKeys: [],
|
|
273
|
+
byteDiffMinimality: null,
|
|
274
|
+
byteDiffChangedRatio: null,
|
|
275
|
+
});
|
|
276
|
+
unlinkSync(path);
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
const assessment = assessDocument(path, parsed);
|
|
280
|
+
this.recordMetric({
|
|
281
|
+
operation: "delete",
|
|
282
|
+
path,
|
|
283
|
+
schemaValid: assessment.schemaValid,
|
|
284
|
+
schemaErrors: assessment.schemaErrors,
|
|
285
|
+
inventedKeys: assessment.inventedKeys,
|
|
286
|
+
byteDiffMinimality: null,
|
|
287
|
+
byteDiffChangedRatio: null,
|
|
288
|
+
});
|
|
289
|
+
unlinkSync(path);
|
|
290
|
+
return { ok: true };
|
|
291
|
+
}
|
|
292
|
+
recordMetric(input) {
|
|
293
|
+
this.metrics.push({
|
|
294
|
+
directiveVersion: this.directiveVersion,
|
|
295
|
+
operation: input.operation,
|
|
296
|
+
path: input.path,
|
|
297
|
+
schemaValid: input.schemaValid,
|
|
298
|
+
schemaErrors: input.schemaErrors,
|
|
299
|
+
fieldInventionCount: input.inventedKeys.length,
|
|
300
|
+
inventedKeys: input.inventedKeys,
|
|
301
|
+
byteDiffMinimality: input.byteDiffMinimality,
|
|
302
|
+
byteDiffChangedRatio: input.byteDiffChangedRatio,
|
|
303
|
+
recordedAt: this.now().toISOString(),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=crud-telemetry.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export declare const HEALTH_SCHEMA_VERSION: 1;
|
|
2
|
+
export declare const HEALTH_HISTORY_REL = "results/health-history.jsonl";
|
|
3
|
+
/** One static Tier-0 gate probe aggregated into the health score. */
|
|
4
|
+
export interface GateProbeResult {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly pass: boolean;
|
|
8
|
+
readonly exitCode: number;
|
|
9
|
+
readonly detail?: string;
|
|
10
|
+
readonly skipped?: boolean;
|
|
11
|
+
readonly skipReason?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Evidence for a contradictory / unsatisfiable gate pair (#1694 class). */
|
|
14
|
+
export interface ContradictionEvidence {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly kind: "unsatisfiable-nudge";
|
|
17
|
+
readonly summary: string;
|
|
18
|
+
readonly signals: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** Versioned Tier-0 health run persisted for trending (#1703). */
|
|
21
|
+
export interface HealthReport {
|
|
22
|
+
readonly schemaVersion: typeof HEALTH_SCHEMA_VERSION;
|
|
23
|
+
readonly version: string;
|
|
24
|
+
readonly recordedAt: string;
|
|
25
|
+
readonly score: number;
|
|
26
|
+
readonly gates: readonly GateProbeResult[];
|
|
27
|
+
readonly contradictions: readonly ContradictionEvidence[];
|
|
28
|
+
}
|
|
29
|
+
export interface EvaluateHealthOptions {
|
|
30
|
+
readonly projectRoot?: string;
|
|
31
|
+
readonly persist?: boolean;
|
|
32
|
+
readonly now?: () => Date;
|
|
33
|
+
readonly frameworkSource?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface EvaluateHealthResult {
|
|
36
|
+
readonly code: 0 | 1 | 2;
|
|
37
|
+
readonly report: HealthReport | null;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
}
|
|
40
|
+
/** Absolute path to the versioned health history ledger. */
|
|
41
|
+
export declare function healthHistoryPath(projectRoot: string): string;
|
|
42
|
+
/** Detect the canonical wipCap unsatisfiable-nudge contradiction (#1694). */
|
|
43
|
+
export declare function detectWipCapUnsatisfiableNudge(projectRoot: string): ContradictionEvidence | null;
|
|
44
|
+
/** Run all registered contradictory-gate detectors. */
|
|
45
|
+
export declare function detectContradictoryGates(projectRoot: string): ContradictionEvidence[];
|
|
46
|
+
/** Compute a 0-100 score from gate pass rate minus contradiction penalty. */
|
|
47
|
+
export declare function computeHealthScore(gates: readonly GateProbeResult[], contradictions: readonly ContradictionEvidence[]): number;
|
|
48
|
+
/** Append one health run to the versioned ledger (#1703 Tier 0). */
|
|
49
|
+
export declare function persistHealthRun(projectRoot: string, report: HealthReport): void;
|
|
50
|
+
/** Aggregate Tier-0 static gates into a versioned framework health score (#1703). */
|
|
51
|
+
export declare function evaluateHealth(options?: EvaluateHealthOptions): EvaluateHealthResult;
|
|
52
|
+
//# sourceMappingURL=health.d.ts.map
|