@inerrata-corporation/errata 2.0.2-dev.982 → 2.0.2
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/consolidate-worker.mjs +20 -2
- package/errata.mjs +2282 -130
- package/package.json +1 -1
- package/pass-worker.mjs +51 -5
package/errata.mjs
CHANGED
|
@@ -64,7 +64,7 @@ function toCloudAttrs(attrs) {
|
|
|
64
64
|
}
|
|
65
65
|
return rest2;
|
|
66
66
|
}
|
|
67
|
-
var SEMANTIC_NODE_LABELS, CONTEXT_NODE_LABELS, CODE_NODE_LABELS, ALL_NODE_LABELS, STRUCTURAL_EDGES, CAUSAL_EDGES, RESOLUTION_EDGES, CONCEPTUAL_EDGES, EVIDENCE_EDGES, CODE_EDGES, BRIDGE_EDGES, IDENTITY_EDGES, JUSTIFICATION_EDGES, BELIEF_EDGES, ABSTRACTION_EDGES, DECOMPOSITION_EDGES, TRANSFER_EDGES, SOLUTION_STRUCTURE_EDGES, SUSPICION_EDGES, TAXONOMY_EDGES, TRIAGE_EDGES, GIT_EDGES, ALL_EDGE_TYPES, PAGERANK_EXCLUSIONS, EDGE_WEIGHT, EDGE_CONDUCTANCE_OVERRIDES, CAUSAL_PROTECT_EDGES, VALIDATION_SOURCES, TRUTH_KIND, ABSTRACTION_LEVEL, PROBLEM_RESOLUTION, DRIFT_KIND, TEST_PATH_RE, TEST_SCOPE_RE;
|
|
67
|
+
var SEMANTIC_NODE_LABELS, ANCHOR_REFUSAL_REASONS, ROUTING_REASONS, CONTEXT_NODE_LABELS, CODE_NODE_LABELS, ALL_NODE_LABELS, STRUCTURAL_EDGES, CAUSAL_EDGES, RESOLUTION_EDGES, CONCEPTUAL_EDGES, EVIDENCE_EDGES, CODE_EDGES, BRIDGE_EDGES, IDENTITY_EDGES, JUSTIFICATION_EDGES, BELIEF_EDGES, ABSTRACTION_EDGES, DECOMPOSITION_EDGES, TRANSFER_EDGES, SOLUTION_STRUCTURE_EDGES, SUSPICION_EDGES, TAXONOMY_EDGES, TRIAGE_EDGES, GIT_EDGES, ALL_EDGE_TYPES, PAGERANK_EXCLUSIONS, EDGE_WEIGHT, EDGE_CONDUCTANCE_OVERRIDES, CAUSAL_PROTECT_EDGES, VALIDATION_SOURCES, TRUTH_KIND, ABSTRACTION_LEVEL, PROBLEM_RESOLUTION, DRIFT_KIND, TEST_PATH_RE, TEST_SCOPE_RE;
|
|
68
68
|
var init_castalia = __esm({
|
|
69
69
|
"../../packages/shared/src/castalia.ts"() {
|
|
70
70
|
"use strict";
|
|
@@ -96,6 +96,43 @@ var init_castalia = __esm({
|
|
|
96
96
|
// truth (PLAN_PLASTICITY §2.3 / §4). truthKind/scope/groundedSupport in attrs.
|
|
97
97
|
"Claim"
|
|
98
98
|
];
|
|
99
|
+
ANCHOR_REFUSAL_REASONS = [
|
|
100
|
+
/** The spine probe ran CLEAN and did not confirm the public claim. */
|
|
101
|
+
"spine-refused",
|
|
102
|
+
/** No anchor tag at all — cannot positively confirm, fail closed. */
|
|
103
|
+
"untagged-failclosed",
|
|
104
|
+
/** The daemon itself tagged the anchor private — a deliberate claim, not a refusal. */
|
|
105
|
+
"daemon-tagged-private",
|
|
106
|
+
/** The probe threw or timed out — the system was blind, not decided (see routingProbeFailed). */
|
|
107
|
+
"probe-failed"
|
|
108
|
+
];
|
|
109
|
+
ROUTING_REASONS = [
|
|
110
|
+
/** Harness org (CTF lane, e2e battery, canary) — forced org-private. */
|
|
111
|
+
"harness-clamp",
|
|
112
|
+
/** A signed gateway write target named the destination. */
|
|
113
|
+
"signed-target-public",
|
|
114
|
+
"signed-target-org",
|
|
115
|
+
"signed-target-team",
|
|
116
|
+
/** No membrane in play (gate off, or a caller with no org) — collective. */
|
|
117
|
+
"membrane-off",
|
|
118
|
+
/** A public Domain/Algorithm of this name already exists — recognized topic. */
|
|
119
|
+
"label-public-exists",
|
|
120
|
+
/** A Domain/Algorithm naming no existing public topic — fails closed. */
|
|
121
|
+
"label-novel-failclosed",
|
|
122
|
+
/** The spine confirmed the anchor and no promotion gate stood in the way. */
|
|
123
|
+
"spine-public",
|
|
124
|
+
/** Spine-confirmed, but the rule-of-3 gate held it back for later graduation. */
|
|
125
|
+
"promotion-gate-denied",
|
|
126
|
+
/** The spine did not confirm a public anchor. `routingProbeFailed` says
|
|
127
|
+
* whether that was a verdict or a blind spot. */
|
|
128
|
+
"spine-private"
|
|
129
|
+
// NOTE: no `report-*` reason. Report-extracted nodes (D2) go through the same
|
|
130
|
+
// `routeOrgVisibility` decision as every other write, with the row's stored
|
|
131
|
+
// scope as the enforced target, so they carry `signed-target-*` /
|
|
132
|
+
// `harness-clamp` like anything else. The first D2 router (!1526) minted a
|
|
133
|
+
// `report-scope-inherited` reason from a scope field-copy; it was retired
|
|
134
|
+
// 2026-08-15 with zero nodes carrying it (dev count 0; prod extraction off).
|
|
135
|
+
];
|
|
99
136
|
CONTEXT_NODE_LABELS = [
|
|
100
137
|
"Language",
|
|
101
138
|
"Package",
|
|
@@ -251,7 +288,7 @@ var init_castalia = __esm({
|
|
|
251
288
|
DECOMPOSITION_EDGES = ["SPLIT_INTO"];
|
|
252
289
|
TRANSFER_EDGES = ["MAY_RESOLVE"];
|
|
253
290
|
SOLUTION_STRUCTURE_EDGES = ["BUILDS_ON", "SUPERSEDES", "ALTERNATIVE_TO"];
|
|
254
|
-
SUSPICION_EDGES = ["SUSPECTED_LINK"];
|
|
291
|
+
SUSPICION_EDGES = ["SUSPECTED_LINK", "REGRESSION_OF"];
|
|
255
292
|
TAXONOMY_EDGES = ["IS_A"];
|
|
256
293
|
TRIAGE_EDGES = ["TRIAGED_BY", "CONFIRMS", "INDICATES", "ROUTES_TO"];
|
|
257
294
|
GIT_EDGES = ["POINTS_AT", "PARENT", "AUTHORED_BY"];
|
|
@@ -316,8 +353,10 @@ var init_castalia = __esm({
|
|
|
316
353
|
"ALTERNATIVE_TO",
|
|
317
354
|
// symmetric peer marker — weight would let alternatives inflate each other
|
|
318
355
|
// NB: BUILDS_ON is NOT excluded — it flows extension→base so foundational solutions rank up.
|
|
319
|
-
"SUSPECTED_LINK"
|
|
356
|
+
"SUSPECTED_LINK",
|
|
320
357
|
// Somnus hypothesis (PLAN_SOMNUS_V2 §4) — never flows rank; navigation-invisible.
|
|
358
|
+
"REGRESSION_OF"
|
|
359
|
+
// recurrence marker (RG-recognize) — a question about a close, not signal-flow.
|
|
321
360
|
]);
|
|
322
361
|
EDGE_WEIGHT = {
|
|
323
362
|
// Causal — high signal
|
|
@@ -430,6 +469,7 @@ var init_castalia = __esm({
|
|
|
430
469
|
ALTERNATIVE_TO: 0,
|
|
431
470
|
// Suspicion — a hypothesis, weightless + PageRank-excluded (never flows rank / EIG).
|
|
432
471
|
SUSPECTED_LINK: 0,
|
|
472
|
+
REGRESSION_OF: 0,
|
|
433
473
|
// Git — topology/authorship, weightless (not domain signal-flow)
|
|
434
474
|
POINTS_AT: 0,
|
|
435
475
|
PARENT: 0,
|
|
@@ -472,6 +512,76 @@ var init_castalia = __esm({
|
|
|
472
512
|
}
|
|
473
513
|
});
|
|
474
514
|
|
|
515
|
+
// ../../packages/shared/src/graph-events.ts
|
|
516
|
+
function unregisteredGraphEventKeys(event, attrKeys) {
|
|
517
|
+
const vocab = GRAPH_EVENT_VOCABULARIES[event];
|
|
518
|
+
if (!vocab) return [`<unregistered event: ${event}>`];
|
|
519
|
+
const exact = new Set(vocab.keys ?? []);
|
|
520
|
+
const offenders = [];
|
|
521
|
+
for (const key of attrKeys) {
|
|
522
|
+
if (exact.has(key)) continue;
|
|
523
|
+
if (vocab.prefixes.some((prefix) => key.startsWith(prefix))) continue;
|
|
524
|
+
offenders.push(key);
|
|
525
|
+
}
|
|
526
|
+
return offenders;
|
|
527
|
+
}
|
|
528
|
+
function assertGraphEventVocabulary(event, attrs) {
|
|
529
|
+
const env2 = typeof process !== "undefined" ? process.env["NODE_ENV"] : void 0;
|
|
530
|
+
if (env2 === "production") return;
|
|
531
|
+
const offenders = unregisteredGraphEventKeys(event, Object.keys(attrs));
|
|
532
|
+
if (offenders.length === 0) return;
|
|
533
|
+
const message = `graph event '${event}' emits unregistered attribute key(s): ${offenders.join(", ")} \u2014 register the vocabulary and its obligated consumer in @inerrata-corporation/shared graph-events.ts (contribution contract D17)`;
|
|
534
|
+
console.error(message);
|
|
535
|
+
throw new UnregisteredGraphEventError(message);
|
|
536
|
+
}
|
|
537
|
+
var GRAPH_EVENT_VOCABULARIES, UnregisteredGraphEventError;
|
|
538
|
+
var init_graph_events = __esm({
|
|
539
|
+
"../../packages/shared/src/graph-events.ts"() {
|
|
540
|
+
"use strict";
|
|
541
|
+
GRAPH_EVENT_VOCABULARIES = {
|
|
542
|
+
"graph.ingest": {
|
|
543
|
+
// 'queue.' and 'skew.' (queue-health.ts, process-cumulative levels) were
|
|
544
|
+
// found by this registry's FIRST enforcement run to have no consumer
|
|
545
|
+
// anywhere in the tree — registered with that fact stated rather than
|
|
546
|
+
// silently admitted, so the next graph-health pass either gives them a
|
|
547
|
+
// trigger or retires them (D12: a record nothing reads is the defect).
|
|
548
|
+
prefixes: ["ingest.", "sanitize.", "queue.", "skew."],
|
|
549
|
+
consumer: "graph-health.mjs ingest triggers (edge-reject RATE, reinforcedFraction band, the volume floor) + the 'Graph \u2014 Ingest funnel & integrity' board. queue.*/skew.* currently have NO standing consumer (found unread 2026-08-14) \u2014 trigger or retirement owed"
|
|
550
|
+
},
|
|
551
|
+
"graph.twin": {
|
|
552
|
+
prefixes: [],
|
|
553
|
+
keys: ["rewritten", "already-generic", "rejected", "failed", "low-content", "threw", "failureFraction"],
|
|
554
|
+
consumer: "failureFraction is the alertable number (twin-outcomes.ts); its graph-health.mjs trigger is pending a live baseline"
|
|
555
|
+
},
|
|
556
|
+
"graph.burst": {
|
|
557
|
+
prefixes: ["burst."],
|
|
558
|
+
consumer: "the 'Graph \u2014 Demand loop (voids & hypotheses)' board \u2014 misses always emitted, hits sampled 1-in-10"
|
|
559
|
+
},
|
|
560
|
+
"graph.promotion": {
|
|
561
|
+
prefixes: ["promotion."],
|
|
562
|
+
consumer: "graph-health.mjs promotion triggers (receipt-absence floor, kCountErrors > 0) + probe:mechanism-liveness reading the durable pgboss.job.output twin"
|
|
563
|
+
},
|
|
564
|
+
"graph.census": {
|
|
565
|
+
prefixes: [
|
|
566
|
+
"nodes.",
|
|
567
|
+
"tier.",
|
|
568
|
+
"edges.",
|
|
569
|
+
"suspectedLinks.",
|
|
570
|
+
"voids.",
|
|
571
|
+
"membrane.",
|
|
572
|
+
"prose.",
|
|
573
|
+
"overlay."
|
|
574
|
+
],
|
|
575
|
+
keys: ["communities", "landmarks", "ingestSeq", "censusPgErrored"],
|
|
576
|
+
consumer: "graph-health.mjs census triggers (reversedResolution, communities floor, byVisibility.other drift) + the 'Graph \u2014 Metabolism (nightly pipeline)' board"
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
UnregisteredGraphEventError = class extends Error {
|
|
580
|
+
name = "UnregisteredGraphEventError";
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
475
585
|
// ../../packages/shared/src/canonical/hash.ts
|
|
476
586
|
import { createHash } from "node:crypto";
|
|
477
587
|
function sha256(input) {
|
|
@@ -15979,6 +16089,7 @@ __export(src_exports, {
|
|
|
15979
16089
|
ALL_EDGE_TYPES: () => ALL_EDGE_TYPES,
|
|
15980
16090
|
ALL_NODE_LABELS: () => ALL_NODE_LABELS,
|
|
15981
16091
|
AMBIGUOUS_ALIASES: () => AMBIGUOUS_ALIASES,
|
|
16092
|
+
ANCHOR_REFUSAL_REASONS: () => ANCHOR_REFUSAL_REASONS,
|
|
15982
16093
|
BELIEF_EDGES: () => BELIEF_EDGES,
|
|
15983
16094
|
BRIDGE_EDGES: () => BRIDGE_EDGES,
|
|
15984
16095
|
CAUSAL_EDGES: () => CAUSAL_EDGES,
|
|
@@ -16000,6 +16111,7 @@ __export(src_exports, {
|
|
|
16000
16111
|
EDGE_WEIGHT: () => EDGE_WEIGHT,
|
|
16001
16112
|
EVIDENCE_EDGES: () => EVIDENCE_EDGES,
|
|
16002
16113
|
GIT_EDGES: () => GIT_EDGES,
|
|
16114
|
+
GRAPH_EVENT_VOCABULARIES: () => GRAPH_EVENT_VOCABULARIES,
|
|
16003
16115
|
IDENTITY_EDGES: () => IDENTITY_EDGES,
|
|
16004
16116
|
INGEST_EXTRACTION_SOURCES: () => INGEST_EXTRACTION_SOURCES,
|
|
16005
16117
|
INGEST_SOURCES: () => INGEST_SOURCES,
|
|
@@ -16016,6 +16128,7 @@ __export(src_exports, {
|
|
|
16016
16128
|
QUARANTINE_EXTRACTION_SOURCE_PREFIX: () => QUARANTINE_EXTRACTION_SOURCE_PREFIX,
|
|
16017
16129
|
RESOLUTION_EDGES: () => RESOLUTION_EDGES,
|
|
16018
16130
|
RE_LEADING_ARTICLE: () => RE_LEADING_ARTICLE,
|
|
16131
|
+
ROUTING_REASONS: () => ROUTING_REASONS,
|
|
16019
16132
|
RouteContextCountWireSchema: () => RouteContextCountWireSchema,
|
|
16020
16133
|
SEMANTIC_NODE_LABELS: () => SEMANTIC_NODE_LABELS,
|
|
16021
16134
|
SOLUTION_STRUCTURE_EDGES: () => SOLUTION_STRUCTURE_EDGES,
|
|
@@ -16027,9 +16140,11 @@ __export(src_exports, {
|
|
|
16027
16140
|
TRANSFER_EDGES: () => TRANSFER_EDGES,
|
|
16028
16141
|
TRIAGE_EDGES: () => TRIAGE_EDGES,
|
|
16029
16142
|
TRUTH_KIND: () => TRUTH_KIND,
|
|
16143
|
+
UnregisteredGraphEventError: () => UnregisteredGraphEventError,
|
|
16030
16144
|
VALIDATION_SOURCES: () => VALIDATION_SOURCES,
|
|
16031
16145
|
aliasesLongestFirst: () => aliasesLongestFirst,
|
|
16032
16146
|
allEntities: () => allEntities,
|
|
16147
|
+
assertGraphEventVocabulary: () => assertGraphEventVocabulary,
|
|
16033
16148
|
canonicalClaimId: () => canonicalClaimId,
|
|
16034
16149
|
canonicalJSON: () => canonicalJSON,
|
|
16035
16150
|
canonicalize: () => canonicalize,
|
|
@@ -16091,6 +16206,7 @@ __export(src_exports, {
|
|
|
16091
16206
|
summaryRejectionReason: () => summaryRejectionReason,
|
|
16092
16207
|
toCloudAttrs: () => toCloudAttrs,
|
|
16093
16208
|
toolCanonicalId: () => toolCanonicalId,
|
|
16209
|
+
unregisteredGraphEventKeys: () => unregisteredGraphEventKeys,
|
|
16094
16210
|
validateCastaliaPayload: () => validateCastaliaPayload,
|
|
16095
16211
|
versionlessPurl: () => versionlessPurl,
|
|
16096
16212
|
vetSidecarSummaries: () => vetSidecarSummaries
|
|
@@ -16099,6 +16215,7 @@ var init_src = __esm({
|
|
|
16099
16215
|
"../../packages/shared/src/index.ts"() {
|
|
16100
16216
|
"use strict";
|
|
16101
16217
|
init_castalia();
|
|
16218
|
+
init_graph_events();
|
|
16102
16219
|
init_identity();
|
|
16103
16220
|
init_wire();
|
|
16104
16221
|
init_edge_rules();
|
|
@@ -16617,7 +16734,13 @@ var init_store = __esm({
|
|
|
16617
16734
|
// the cloud door's `to: [Solution]` rule is untouched. The `from` constraint
|
|
16618
16735
|
// stays: the reversed (Solution)-FIXED_BY->(...) splash bug is the reason
|
|
16619
16736
|
// this rule exists at all.
|
|
16620
|
-
FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] }
|
|
16737
|
+
FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] },
|
|
16738
|
+
// RG-recognize: a NEW open Problem re-observing a RESOLVED one ("this is
|
|
16739
|
+
// back — the fix didn't hold"). Local-only for now: the instance drain's
|
|
16740
|
+
// INSTANCE_EDGES allowlist doesn't ship it, and the cloud door's matrix
|
|
16741
|
+
// doesn't know it — crossing the membrane is a deliberate later step, never
|
|
16742
|
+
// a side effect (the half-shipped-edge-type lesson).
|
|
16743
|
+
REGRESSION_OF: { from: ["Problem"], to: ["Problem"] }
|
|
16621
16744
|
};
|
|
16622
16745
|
SCHEMA_VERSION = 6;
|
|
16623
16746
|
SCHEMA_SQL = `
|
|
@@ -18850,11 +18973,16 @@ function foldDuplicateProblem(store, dup, survivor, ts) {
|
|
|
18850
18973
|
if (!surv) return;
|
|
18851
18974
|
const sources = new Set(surv.attrs["sources"] ?? []);
|
|
18852
18975
|
for (const s of dup.attrs["sources"] ?? []) sources.add(s);
|
|
18976
|
+
const dupSeq = dup.attrs["createdAtSeq"] ?? store.currentIngestSeq();
|
|
18977
|
+
const exposure = priorExposure(store, survivor.id, dupSeq);
|
|
18978
|
+
const expField = `reinforce${exposure[0].toUpperCase()}${exposure.slice(1)}Count`;
|
|
18853
18979
|
store.updateNode(survivor.id, {
|
|
18854
18980
|
attrs: {
|
|
18855
18981
|
...surv.attrs,
|
|
18856
18982
|
sources: [...sources],
|
|
18857
|
-
corroborations: corroborations(surv) + corroborations(dup) + 1
|
|
18983
|
+
corroborations: corroborations(surv) + corroborations(dup) + 1,
|
|
18984
|
+
[expField]: (surv.attrs[expField] ?? 0) + 1,
|
|
18985
|
+
lastReinforceExposure: exposure
|
|
18858
18986
|
},
|
|
18859
18987
|
cumulativeHits: (surv.cumulativeHits ?? 0) + (dup.cumulativeHits ?? 0),
|
|
18860
18988
|
lastUpdatedAt: ts
|
|
@@ -19018,7 +19146,22 @@ function ingestDesignProblem(store, flag, opts) {
|
|
|
19018
19146
|
const existing = store.getNode(problemId);
|
|
19019
19147
|
let created = false;
|
|
19020
19148
|
let corroborated = false;
|
|
19149
|
+
let recurredResolved = false;
|
|
19150
|
+
let reopenedFromRegression = false;
|
|
19021
19151
|
if (existing) {
|
|
19152
|
+
const resolvedUnretracted = existing.attrs["resolvedAt"] != null && existing.attrs["resolvedAs"] == null;
|
|
19153
|
+
const recurAttrs = () => {
|
|
19154
|
+
recurredResolved = true;
|
|
19155
|
+
const sources2 = existing.attrs["recurrenceSources"] ?? [];
|
|
19156
|
+
return {
|
|
19157
|
+
resolutionSuspect: true,
|
|
19158
|
+
recurredAt: opts.ts,
|
|
19159
|
+
recurrences: (Number(existing.attrs["recurrences"]) || 0) + 1,
|
|
19160
|
+
// RG-reopen: the door touch carries its session — the reopen check
|
|
19161
|
+
// after the update counts DISTINCT sources against REOPEN_MIN_SOURCES.
|
|
19162
|
+
...sources2.includes(opts.source) ? {} : { recurrenceSources: [...sources2, opts.source].slice(-8) }
|
|
19163
|
+
};
|
|
19164
|
+
};
|
|
19022
19165
|
const sources = existing.attrs["sources"] ?? [];
|
|
19023
19166
|
if (!sources.includes(opts.source)) {
|
|
19024
19167
|
corroborated = true;
|
|
@@ -19027,6 +19170,9 @@ function ingestDesignProblem(store, flag, opts) {
|
|
|
19027
19170
|
store.updateNode(problemId, {
|
|
19028
19171
|
attrs: {
|
|
19029
19172
|
...existing.attrs,
|
|
19173
|
+
// An INDEPENDENT source re-deriving a resolved problem marks
|
|
19174
|
+
// immediately — that is the strongest recurrence evidence there is.
|
|
19175
|
+
...resolvedUnretracted ? recurAttrs() : {},
|
|
19030
19176
|
sources: [...sources, opts.source],
|
|
19031
19177
|
corroborations: corroborations2,
|
|
19032
19178
|
...promoted ? { provisional: false } : {},
|
|
@@ -19052,6 +19198,13 @@ function ingestDesignProblem(store, flag, opts) {
|
|
|
19052
19198
|
...reinforcedSeqAttrs(store, existing.label),
|
|
19053
19199
|
lastUpdatedAt: opts.ts
|
|
19054
19200
|
});
|
|
19201
|
+
if (recurredResolved) reopenedFromRegression = maybeReopenFromRegression(store, problemId, opts.ts);
|
|
19202
|
+
} else if (resolvedUnretracted && opts.ts - (Number(existing.attrs["recurredAt"]) || 0) > RECURRENCE_DEBOUNCE_MS) {
|
|
19203
|
+
store.updateNode(problemId, {
|
|
19204
|
+
attrs: { ...existing.attrs, ...recurAttrs() },
|
|
19205
|
+
lastUpdatedAt: opts.ts
|
|
19206
|
+
});
|
|
19207
|
+
reopenedFromRegression = maybeReopenFromRegression(store, problemId, opts.ts);
|
|
19055
19208
|
}
|
|
19056
19209
|
} else {
|
|
19057
19210
|
created = true;
|
|
@@ -19063,7 +19216,8 @@ function ingestDesignProblem(store, flag, opts) {
|
|
|
19063
19216
|
designProblemId: problemId,
|
|
19064
19217
|
sources: [opts.source],
|
|
19065
19218
|
corroborations: 0,
|
|
19066
|
-
scope: {}
|
|
19219
|
+
scope: {},
|
|
19220
|
+
...opts.hostHarness ? { hostHarness: opts.hostHarness } : {}
|
|
19067
19221
|
})
|
|
19068
19222
|
);
|
|
19069
19223
|
}
|
|
@@ -19106,7 +19260,17 @@ function ingestDesignProblem(store, flag, opts) {
|
|
|
19106
19260
|
const linkText = flag.cause?.trim() ? `${statement}
|
|
19107
19261
|
${flag.cause.trim()}` : statement;
|
|
19108
19262
|
const linkedPackages = linkProblemToPackages(store, problemId, linkText, opts.ts).linked;
|
|
19109
|
-
return {
|
|
19263
|
+
return {
|
|
19264
|
+
problemId,
|
|
19265
|
+
created,
|
|
19266
|
+
corroborated,
|
|
19267
|
+
rootCauseId,
|
|
19268
|
+
solutionId,
|
|
19269
|
+
anchored,
|
|
19270
|
+
linkedPackages,
|
|
19271
|
+
...recurredResolved ? { recurredResolved } : {},
|
|
19272
|
+
...reopenedFromRegression ? { reopenedFromRegression } : {}
|
|
19273
|
+
};
|
|
19110
19274
|
}
|
|
19111
19275
|
function canonicalizePatternText(name2) {
|
|
19112
19276
|
const stripped = name2.replace(/^\s*pattern(?:\s*[×x]\s*\d+)?\s*(?:\(exemplar\))?\s*[:—–-]\s*/i, "").replace(/\s+/g, " ").trim();
|
|
@@ -19252,22 +19416,61 @@ function tokenJaccard(a, b) {
|
|
|
19252
19416
|
return inter / (sa.size + sb.size - inter);
|
|
19253
19417
|
}
|
|
19254
19418
|
function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
|
|
19419
|
+
return foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind).foldedId;
|
|
19420
|
+
}
|
|
19421
|
+
function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
|
|
19422
|
+
const report = { ...EMPTY_FOLD_REPORT };
|
|
19255
19423
|
const stmt = statement.trim();
|
|
19256
|
-
if (!isAnchorableCodePath(relPath))
|
|
19424
|
+
if (!isAnchorableCodePath(relPath)) {
|
|
19425
|
+
report.noFile = true;
|
|
19426
|
+
return report;
|
|
19427
|
+
}
|
|
19257
19428
|
const file2 = resolveFileNode(store, relPath, workspaceId2);
|
|
19258
|
-
if (!file2)
|
|
19429
|
+
if (!file2) {
|
|
19430
|
+
report.noFile = true;
|
|
19431
|
+
return report;
|
|
19432
|
+
}
|
|
19259
19433
|
const selfId = identityId({ kind: "DesignProblem", statement: stmt });
|
|
19434
|
+
let stmtVec = null;
|
|
19260
19435
|
let best = null;
|
|
19436
|
+
let bestScout = null;
|
|
19261
19437
|
for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
|
|
19262
19438
|
const cand = store.getNode(e.from);
|
|
19263
19439
|
if (!cand || cand.label !== "Problem" || cand.attrs["resolvedAt"]) continue;
|
|
19264
19440
|
if (cand.id === selfId) continue;
|
|
19265
19441
|
const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
|
|
19266
|
-
if (candKind !== (kind === "constraint" ? "constraint" : "problem"))
|
|
19442
|
+
if (candKind !== (kind === "constraint" ? "constraint" : "problem")) {
|
|
19443
|
+
report.kindVetoed++;
|
|
19444
|
+
continue;
|
|
19445
|
+
}
|
|
19446
|
+
report.candidates++;
|
|
19267
19447
|
const score2 = tokenJaccard(stmt, cand.description);
|
|
19448
|
+
if (score2 > report.bestJaccard) report.bestJaccard = score2;
|
|
19268
19449
|
if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) best = { node: cand, score: score2 };
|
|
19450
|
+
if (!best) {
|
|
19451
|
+
stmtVec ??= embed(stmt);
|
|
19452
|
+
const hc = cosine(stmtVec, embed(cand.description));
|
|
19453
|
+
if (hc > report.bestHashCos) report.bestHashCos = hc;
|
|
19454
|
+
if ((hc >= SAME_ANCHOR_SCOUT_MIN_COSINE || score2 >= SAME_ANCHOR_NOMINATE_JACCARD) && (!bestScout || hc > bestScout.hashCos)) {
|
|
19455
|
+
bestScout = { node: cand, hashCos: hc, jaccard: score2 };
|
|
19456
|
+
}
|
|
19457
|
+
}
|
|
19269
19458
|
}
|
|
19270
|
-
if (!best)
|
|
19459
|
+
if (!best && bestScout && bestScout.hashCos >= SAME_ANCHOR_SCOUT_AUTO_COSINE) {
|
|
19460
|
+
best = { node: bestScout.node, score: bestScout.jaccard };
|
|
19461
|
+
report.foldedBy = "scout";
|
|
19462
|
+
}
|
|
19463
|
+
if (!best) {
|
|
19464
|
+
if (bestScout) {
|
|
19465
|
+
report.nominate = {
|
|
19466
|
+
targetId: bestScout.node.id,
|
|
19467
|
+
jaccard: bestScout.jaccard,
|
|
19468
|
+
hashCos: bestScout.hashCos
|
|
19469
|
+
};
|
|
19470
|
+
}
|
|
19471
|
+
return report;
|
|
19472
|
+
}
|
|
19473
|
+
report.foldedBy ??= "jaccard";
|
|
19271
19474
|
const existing = best.node;
|
|
19272
19475
|
const sources = existing.attrs["sources"] ?? [];
|
|
19273
19476
|
if (!sources.includes(source)) {
|
|
@@ -19289,7 +19492,79 @@ function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, sou
|
|
|
19289
19492
|
lastUpdatedAt: ts
|
|
19290
19493
|
});
|
|
19291
19494
|
}
|
|
19292
|
-
|
|
19495
|
+
report.foldedId = existing.id;
|
|
19496
|
+
return report;
|
|
19497
|
+
}
|
|
19498
|
+
function findResolvedAnchorMatch(store, relPath, workspaceId2, statement, kind = "problem") {
|
|
19499
|
+
const stmt = statement.trim();
|
|
19500
|
+
if (!isAnchorableCodePath(relPath)) return null;
|
|
19501
|
+
const file2 = resolveFileNode(store, relPath, workspaceId2);
|
|
19502
|
+
if (!file2) return null;
|
|
19503
|
+
const selfId = identityId({ kind: "DesignProblem", statement: stmt });
|
|
19504
|
+
let best = null;
|
|
19505
|
+
for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
|
|
19506
|
+
const cand = store.getNode(e.from);
|
|
19507
|
+
if (!cand || cand.label !== "Problem") continue;
|
|
19508
|
+
if (cand.attrs["resolvedAt"] == null || cand.attrs["resolvedAs"] != null) continue;
|
|
19509
|
+
if (cand.attrs["mergedInto"] !== void 0) continue;
|
|
19510
|
+
if (cand.id === selfId) continue;
|
|
19511
|
+
const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
|
|
19512
|
+
if (candKind !== (kind === "constraint" ? "constraint" : "problem")) continue;
|
|
19513
|
+
const score2 = tokenJaccard(stmt, cand.description);
|
|
19514
|
+
if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) {
|
|
19515
|
+
best = { id: cand.id, score: score2 };
|
|
19516
|
+
}
|
|
19517
|
+
}
|
|
19518
|
+
return best?.id ?? null;
|
|
19519
|
+
}
|
|
19520
|
+
function maybeReopenFromRegression(store, nodeId, ts) {
|
|
19521
|
+
const n = store.getNode(nodeId);
|
|
19522
|
+
if (!n) return false;
|
|
19523
|
+
if (n.attrs["resolvedAt"] == null || n.attrs["resolvedAs"] != null) return false;
|
|
19524
|
+
const sources = n.attrs["recurrenceSources"] ?? [];
|
|
19525
|
+
if (new Set(sources).size < REOPEN_MIN_SOURCES) return false;
|
|
19526
|
+
const attrs = { ...n.attrs };
|
|
19527
|
+
delete attrs["resolvedAt"];
|
|
19528
|
+
delete attrs["resolutionSuspect"];
|
|
19529
|
+
attrs["reopenedFrom"] = "regression";
|
|
19530
|
+
attrs["reopenedAt"] = ts;
|
|
19531
|
+
store.updateNode(nodeId, { attrs, lastUpdatedAt: ts });
|
|
19532
|
+
return true;
|
|
19533
|
+
}
|
|
19534
|
+
function markResolvedRecurrence(store, nodeId, ts, source) {
|
|
19535
|
+
const n = store.getNode(nodeId);
|
|
19536
|
+
if (!n) return { marked: false, reopened: false };
|
|
19537
|
+
if (n.attrs["resolvedAt"] == null || n.attrs["resolvedAs"] != null) {
|
|
19538
|
+
return { marked: false, reopened: false };
|
|
19539
|
+
}
|
|
19540
|
+
const sources = n.attrs["recurrenceSources"] ?? [];
|
|
19541
|
+
store.updateNode(nodeId, {
|
|
19542
|
+
attrs: {
|
|
19543
|
+
...n.attrs,
|
|
19544
|
+
resolutionSuspect: true,
|
|
19545
|
+
recurredAt: ts,
|
|
19546
|
+
recurrences: (Number(n.attrs["recurrences"]) || 0) + 1,
|
|
19547
|
+
...source && !sources.includes(source) ? { recurrenceSources: [...sources, source].slice(-RECURRENCE_SOURCES_CAP) } : {}
|
|
19548
|
+
},
|
|
19549
|
+
lastUpdatedAt: ts
|
|
19550
|
+
});
|
|
19551
|
+
const reopened = source ? maybeReopenFromRegression(store, nodeId, ts) : false;
|
|
19552
|
+
return { marked: true, reopened };
|
|
19553
|
+
}
|
|
19554
|
+
function confirmFixHeld(store, solutionId, ts) {
|
|
19555
|
+
let acknowledged = 0;
|
|
19556
|
+
for (const e of store.inEdges(solutionId, ["SOLVED_BY", "FIXED_BY"])) {
|
|
19557
|
+
const p = store.getNode(e.from);
|
|
19558
|
+
if (!p || p.label !== "Problem") continue;
|
|
19559
|
+
if (p.attrs["resolutionSuspect"] !== true || p.attrs["resolvedAt"] == null) continue;
|
|
19560
|
+
const attrs = { ...p.attrs };
|
|
19561
|
+
delete attrs["resolutionSuspect"];
|
|
19562
|
+
attrs["fixHeldConfirmedAt"] = ts;
|
|
19563
|
+
delete attrs["recurrenceSources"];
|
|
19564
|
+
store.updateNode(p.id, { attrs, lastUpdatedAt: ts });
|
|
19565
|
+
acknowledged++;
|
|
19566
|
+
}
|
|
19567
|
+
return acknowledged;
|
|
19293
19568
|
}
|
|
19294
19569
|
function isFragmentFixNote(note) {
|
|
19295
19570
|
const t = note.trim();
|
|
@@ -19692,7 +19967,7 @@ function priorExposure(store, nodeId, atSeq) {
|
|
|
19692
19967
|
if ((n.attrs["evictedCount"] ?? 0) > 0) return "evicted";
|
|
19693
19968
|
return "unshown";
|
|
19694
19969
|
}
|
|
19695
|
-
var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
|
|
19970
|
+
var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, EMPTY_FOLD_REPORT, SAME_ANCHOR_SCOUT_AUTO_COSINE, SAME_ANCHOR_SCOUT_MIN_COSINE, SAME_ANCHOR_NOMINATE_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
|
|
19696
19971
|
var init_design_problem = __esm({
|
|
19697
19972
|
"../../packages/local-graph/src/design-problem.ts"() {
|
|
19698
19973
|
"use strict";
|
|
@@ -19705,6 +19980,7 @@ var init_design_problem = __esm({
|
|
|
19705
19980
|
init_problem_dedup();
|
|
19706
19981
|
DESIGN_PROMOTE_AT = 1;
|
|
19707
19982
|
STATEMENT_ALIAS_CAP = 5;
|
|
19983
|
+
RECURRENCE_DEBOUNCE_MS = 60 * 60 * 1e3;
|
|
19708
19984
|
PATTERN_DEDUP_COSINE = 0.85;
|
|
19709
19985
|
PATTERN_ALIAS_CAP = 5;
|
|
19710
19986
|
DEDUP_STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -19731,6 +20007,21 @@ var init_design_problem = __esm({
|
|
|
19731
20007
|
"where"
|
|
19732
20008
|
]);
|
|
19733
20009
|
SAME_ANCHOR_DEDUP_JACCARD = 0.6;
|
|
20010
|
+
EMPTY_FOLD_REPORT = {
|
|
20011
|
+
foldedId: null,
|
|
20012
|
+
foldedBy: null,
|
|
20013
|
+
noFile: false,
|
|
20014
|
+
candidates: 0,
|
|
20015
|
+
kindVetoed: 0,
|
|
20016
|
+
bestJaccard: 0,
|
|
20017
|
+
bestHashCos: 0,
|
|
20018
|
+
nominate: null
|
|
20019
|
+
};
|
|
20020
|
+
SAME_ANCHOR_SCOUT_AUTO_COSINE = 0.85;
|
|
20021
|
+
SAME_ANCHOR_SCOUT_MIN_COSINE = 0.5;
|
|
20022
|
+
SAME_ANCHOR_NOMINATE_JACCARD = 0.3;
|
|
20023
|
+
REOPEN_MIN_SOURCES = 2;
|
|
20024
|
+
RECURRENCE_SOURCES_CAP = 8;
|
|
19734
20025
|
CITABLE_PRIOR_LABELS = /* @__PURE__ */ new Set([
|
|
19735
20026
|
"Solution",
|
|
19736
20027
|
"RootCause",
|
|
@@ -21973,6 +22264,160 @@ var init_community2 = __esm({
|
|
|
21973
22264
|
}
|
|
21974
22265
|
});
|
|
21975
22266
|
|
|
22267
|
+
// ../../packages/local-graph/src/fold-candidates.ts
|
|
22268
|
+
function markFoldCandidate(store, problemId, targetId, info2) {
|
|
22269
|
+
if (problemId === targetId) return false;
|
|
22270
|
+
const n = store.getNode(problemId);
|
|
22271
|
+
if (!openProblem(n)) return false;
|
|
22272
|
+
if (n.attrs["foldCandidate"] !== void 0) return false;
|
|
22273
|
+
const rejected = n.attrs["foldRejected"] ?? [];
|
|
22274
|
+
if (rejected.includes(targetId)) return false;
|
|
22275
|
+
if (!openProblem(store.getNode(targetId))) return false;
|
|
22276
|
+
const mark = {
|
|
22277
|
+
targetId,
|
|
22278
|
+
...info2.hashCos !== void 0 ? { hashCos: info2.hashCos } : {},
|
|
22279
|
+
...info2.jaccard !== void 0 ? { jaccard: info2.jaccard } : {},
|
|
22280
|
+
source: info2.source,
|
|
22281
|
+
ts: info2.ts,
|
|
22282
|
+
markedAtSeq: store.currentIngestSeq()
|
|
22283
|
+
};
|
|
22284
|
+
store.updateNode(problemId, { attrs: { ...n.attrs, foldCandidate: mark }, lastUpdatedAt: info2.ts });
|
|
22285
|
+
return true;
|
|
22286
|
+
}
|
|
22287
|
+
function findWorkspaceFoldCandidate(store, workspaceId2, statement, kind, opts) {
|
|
22288
|
+
const stmtVec = embed(statement);
|
|
22289
|
+
const wantConstraint = kind === "constraint";
|
|
22290
|
+
const open2 = store.findNodesByLabel("Problem").filter(
|
|
22291
|
+
(p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.id !== opts?.excludeId && (p.attrs["workspaceId"] === void 0 || p.attrs["workspaceId"] === workspaceId2) && isConstraintProblem(p) === wantConstraint
|
|
22292
|
+
).sort((a, b) => (b.lastUpdatedAt ?? b.createdAt) - (a.lastUpdatedAt ?? a.createdAt)).slice(0, opts?.scanCap ?? WORKSPACE_SCAN_CAP);
|
|
22293
|
+
let best = null;
|
|
22294
|
+
for (const p of open2) {
|
|
22295
|
+
const c = cosine(stmtVec, embed(p.description));
|
|
22296
|
+
if (c >= FOLD_SCOUT_MIN_COSINE && (!best || c > best.hashCos)) best = { targetId: p.id, hashCos: c };
|
|
22297
|
+
}
|
|
22298
|
+
return best;
|
|
22299
|
+
}
|
|
22300
|
+
function followMerges(store, id) {
|
|
22301
|
+
let cur = store.getNode(id);
|
|
22302
|
+
for (let hop = 0; hop < 3 && cur && cur.attrs["mergedInto"] !== void 0; hop++) {
|
|
22303
|
+
cur = store.getNode(String(cur.attrs["mergedInto"]));
|
|
22304
|
+
}
|
|
22305
|
+
return cur ?? null;
|
|
22306
|
+
}
|
|
22307
|
+
function clearCandidate(store, node2, ts, rejectTargetId) {
|
|
22308
|
+
const attrs = { ...node2.attrs };
|
|
22309
|
+
delete attrs["foldCandidate"];
|
|
22310
|
+
if (rejectTargetId) {
|
|
22311
|
+
const rejected = attrs["foldRejected"] ?? [];
|
|
22312
|
+
if (!rejected.includes(rejectTargetId)) {
|
|
22313
|
+
attrs["foldRejected"] = [...rejected, rejectTargetId].slice(-FOLD_REJECTED_CAP);
|
|
22314
|
+
}
|
|
22315
|
+
}
|
|
22316
|
+
store.updateNode(node2.id, { attrs, lastUpdatedAt: ts });
|
|
22317
|
+
}
|
|
22318
|
+
async function resolveFoldCandidates(store, opts) {
|
|
22319
|
+
const report = {
|
|
22320
|
+
examined: 0,
|
|
22321
|
+
folded: 0,
|
|
22322
|
+
foldedEmbedding: 0,
|
|
22323
|
+
foldedJudge: 0,
|
|
22324
|
+
rejected: 0,
|
|
22325
|
+
standing: 0,
|
|
22326
|
+
judgeErrors: 0,
|
|
22327
|
+
retargeted: 0
|
|
22328
|
+
};
|
|
22329
|
+
const limit = opts.limit ?? 8;
|
|
22330
|
+
const marked = store.findNodesByLabel("Problem").filter((p) => openProblem(p) && p.attrs["foldCandidate"] !== void 0).sort((a, b) => {
|
|
22331
|
+
const ma = a.attrs["foldCandidate"].ts ?? 0;
|
|
22332
|
+
const mb = b.attrs["foldCandidate"].ts ?? 0;
|
|
22333
|
+
return ma - mb;
|
|
22334
|
+
}).slice(0, limit);
|
|
22335
|
+
for (const node2 of marked) {
|
|
22336
|
+
const fresh = store.getNode(node2.id);
|
|
22337
|
+
if (!openProblem(fresh)) continue;
|
|
22338
|
+
report.examined++;
|
|
22339
|
+
const mark = fresh.attrs["foldCandidate"];
|
|
22340
|
+
let target = followMerges(store, mark.targetId);
|
|
22341
|
+
if (target && target.id !== mark.targetId) report.retargeted++;
|
|
22342
|
+
if (!openProblem(target)) {
|
|
22343
|
+
clearCandidate(store, fresh, opts.ts);
|
|
22344
|
+
report.rejected++;
|
|
22345
|
+
continue;
|
|
22346
|
+
}
|
|
22347
|
+
if (isConstraintProblem(fresh) !== isConstraintProblem(target)) {
|
|
22348
|
+
clearCandidate(store, fresh, opts.ts, target.id);
|
|
22349
|
+
report.rejected++;
|
|
22350
|
+
continue;
|
|
22351
|
+
}
|
|
22352
|
+
let cos = null;
|
|
22353
|
+
if (fresh.embedding.length > 0 && fresh.embedding.length === target.embedding.length && (fresh.attrs["embeddingVersion"] ?? "") === (target.attrs["embeddingVersion"] ?? "")) {
|
|
22354
|
+
cos = cosine(fresh.embedding, target.embedding);
|
|
22355
|
+
}
|
|
22356
|
+
if (cos !== null && cos >= FOLD_EMBED_AUTO_COSINE) {
|
|
22357
|
+
foldPair(store, fresh, target, opts.ts);
|
|
22358
|
+
report.folded++;
|
|
22359
|
+
report.foldedEmbedding++;
|
|
22360
|
+
continue;
|
|
22361
|
+
}
|
|
22362
|
+
if (cos !== null && cos < FOLD_JUDGE_MIN_COSINE) {
|
|
22363
|
+
clearCandidate(store, fresh, opts.ts, target.id);
|
|
22364
|
+
report.rejected++;
|
|
22365
|
+
continue;
|
|
22366
|
+
}
|
|
22367
|
+
if (!opts.judge) {
|
|
22368
|
+
report.standing++;
|
|
22369
|
+
continue;
|
|
22370
|
+
}
|
|
22371
|
+
let verdict = null;
|
|
22372
|
+
try {
|
|
22373
|
+
verdict = await opts.judge(fresh.description, target.description);
|
|
22374
|
+
} catch {
|
|
22375
|
+
verdict = null;
|
|
22376
|
+
}
|
|
22377
|
+
if (verdict === "same") {
|
|
22378
|
+
foldPair(store, fresh, target, opts.ts);
|
|
22379
|
+
report.folded++;
|
|
22380
|
+
report.foldedJudge++;
|
|
22381
|
+
} else if (verdict === "distinct") {
|
|
22382
|
+
clearCandidate(store, fresh, opts.ts, target.id);
|
|
22383
|
+
report.rejected++;
|
|
22384
|
+
} else {
|
|
22385
|
+
report.judgeErrors++;
|
|
22386
|
+
report.standing++;
|
|
22387
|
+
}
|
|
22388
|
+
}
|
|
22389
|
+
return report;
|
|
22390
|
+
}
|
|
22391
|
+
function foldPair(store, a, b, ts) {
|
|
22392
|
+
const corro = (n) => Number(n.attrs["corroborations"] ?? 0);
|
|
22393
|
+
const survivor = corro(a) !== corro(b) ? corro(a) > corro(b) ? a : b : a.createdAt <= b.createdAt ? a : b;
|
|
22394
|
+
const dup = survivor === a ? b : a;
|
|
22395
|
+
for (const n of [a, b]) {
|
|
22396
|
+
if (n.attrs["foldCandidate"] !== void 0) clearCandidate(store, n, ts);
|
|
22397
|
+
}
|
|
22398
|
+
const freshSurvivor = store.getNode(survivor.id);
|
|
22399
|
+
const freshDup = store.getNode(dup.id);
|
|
22400
|
+
if (!freshSurvivor || !freshDup) return;
|
|
22401
|
+
foldDuplicateProblem(store, freshDup, freshSurvivor, ts);
|
|
22402
|
+
}
|
|
22403
|
+
var FOLD_SCOUT_AUTO_COSINE, FOLD_SCOUT_MIN_COSINE, FOLD_SCOUT_MIN_JACCARD, FOLD_EMBED_AUTO_COSINE, FOLD_JUDGE_MIN_COSINE, FOLD_REJECTED_CAP, WORKSPACE_SCAN_CAP, openProblem;
|
|
22404
|
+
var init_fold_candidates = __esm({
|
|
22405
|
+
"../../packages/local-graph/src/fold-candidates.ts"() {
|
|
22406
|
+
"use strict";
|
|
22407
|
+
init_src3();
|
|
22408
|
+
init_design_problem();
|
|
22409
|
+
init_problem_dedup();
|
|
22410
|
+
FOLD_SCOUT_AUTO_COSINE = 0.85;
|
|
22411
|
+
FOLD_SCOUT_MIN_COSINE = 0.5;
|
|
22412
|
+
FOLD_SCOUT_MIN_JACCARD = 0.3;
|
|
22413
|
+
FOLD_EMBED_AUTO_COSINE = 0.88;
|
|
22414
|
+
FOLD_JUDGE_MIN_COSINE = 0.5;
|
|
22415
|
+
FOLD_REJECTED_CAP = 8;
|
|
22416
|
+
WORKSPACE_SCAN_CAP = 400;
|
|
22417
|
+
openProblem = (n) => !!n && n.label === "Problem" && n.attrs["resolvedAt"] == null && n.attrs["mergedInto"] === void 0;
|
|
22418
|
+
}
|
|
22419
|
+
});
|
|
22420
|
+
|
|
21976
22421
|
// ../../packages/local-graph/src/tools.ts
|
|
21977
22422
|
function toolNodeId(name2) {
|
|
21978
22423
|
return `tool:${name2}`;
|
|
@@ -22253,6 +22698,50 @@ var init_mechanism_liveness = __esm({
|
|
|
22253
22698
|
STALL_MIN_RUNS = 3;
|
|
22254
22699
|
STALL_MIN_SPAN_MS = 60 * 60 * 1e3;
|
|
22255
22700
|
MECHANISMS = [
|
|
22701
|
+
{
|
|
22702
|
+
id: "refute-local-stamp",
|
|
22703
|
+
what: "stamps a harvested refute witness onto the LOCAL node (refutedAt/refutations/reason)",
|
|
22704
|
+
// Stamped at harvest, so the capture pass carries the effect counter.
|
|
22705
|
+
pass: "capture",
|
|
22706
|
+
// Refutes are rare by design (a verdict, not a lean-on) — the bar is "the
|
|
22707
|
+
// field is written at all", the revisitAnsweredWhen precedent. RG-mark
|
|
22708
|
+
// ships the producer; RG-recall is the ranking consumer — until it lands,
|
|
22709
|
+
// THIS descriptor is the consumer, which is exactly the condition the
|
|
22710
|
+
// probe exists to keep visible rather than let rot silently.
|
|
22711
|
+
fed: {
|
|
22712
|
+
labels: ["Problem", "Solution", "RootCause", "Pattern"],
|
|
22713
|
+
attr: "refutedAt",
|
|
22714
|
+
minFraction: 0
|
|
22715
|
+
},
|
|
22716
|
+
effectCounter: "refutesStamped",
|
|
22717
|
+
note: "RG-mark; reason text is local-only (wire ships nodeId+witnessKey)"
|
|
22718
|
+
},
|
|
22719
|
+
{
|
|
22720
|
+
id: "regression-recognize",
|
|
22721
|
+
what: "marks a resolved problem as resolutionSuspect when it recurs at the door",
|
|
22722
|
+
pass: "capture",
|
|
22723
|
+
// Regressions are rare and precious — written-at-all bar, like refutedAt.
|
|
22724
|
+
fed: {
|
|
22725
|
+
labels: ["Problem"],
|
|
22726
|
+
attr: "recurredAt",
|
|
22727
|
+
minFraction: 0
|
|
22728
|
+
},
|
|
22729
|
+
effectCounter: "recurrencesMarked",
|
|
22730
|
+
note: "RG-recognize tier A (exact-id door); tiers B/C add REGRESSION_OF edges; reopen is RG-reopen's"
|
|
22731
|
+
},
|
|
22732
|
+
{
|
|
22733
|
+
id: "regression-reopen",
|
|
22734
|
+
what: "flips a marked close back open once two DISTINCT sessions witnessed the recurrence",
|
|
22735
|
+
pass: "capture",
|
|
22736
|
+
// Reopens should be rarer than recurrences by construction — written-at-all.
|
|
22737
|
+
fed: {
|
|
22738
|
+
labels: ["Problem"],
|
|
22739
|
+
attr: "reopenedFrom",
|
|
22740
|
+
minFraction: 0
|
|
22741
|
+
},
|
|
22742
|
+
effectCounter: "reopenedFromRegression",
|
|
22743
|
+
note: "RG-reopen; the machine sweep never counts as a witness \u2014 only sessions do"
|
|
22744
|
+
},
|
|
22256
22745
|
{
|
|
22257
22746
|
id: "fix-candidate-ask",
|
|
22258
22747
|
what: "asks an agent whether an edit to a problem's anchor fixed it",
|
|
@@ -22383,6 +22872,32 @@ var init_mechanism_liveness = __esm({
|
|
|
22383
22872
|
backlogCounter: "anchorHintsStanding",
|
|
22384
22873
|
note: "drains as hinted files change; contentChangedAt is forward-only so the backlog moves with real edits"
|
|
22385
22874
|
},
|
|
22875
|
+
{
|
|
22876
|
+
id: "fold-candidate-resolve",
|
|
22877
|
+
what: "resolves parked paraphrase-fold nominations (embedding-certain bands auto, ambiguous band via the equivalence judge)",
|
|
22878
|
+
// The settle boundary's fold-judge pass is the sole consumer of
|
|
22879
|
+
// attrs.foldCandidate; producers are the file/workspace scouts (capture)
|
|
22880
|
+
// and the settle near-miss band (mergeProblemsByEmbedding).
|
|
22881
|
+
pass: "fold-judge",
|
|
22882
|
+
// Conditional mark, rare by design (a nomination is a near-miss event) —
|
|
22883
|
+
// the bar is "the field is written at all", the revisit-sweep precedent.
|
|
22884
|
+
fed: { labels: ["Problem"], attr: "foldCandidate", minFraction: 0 },
|
|
22885
|
+
effectCounter: "folded",
|
|
22886
|
+
// Standing nominations ARE the judge-less backlog — without the gauge a
|
|
22887
|
+
// deployment with no judge configured reads as "nothing to fold".
|
|
22888
|
+
backlogCounter: "standing",
|
|
22889
|
+
note: "standing > 0 with folded 0 = the judge lane is unconfigured (ERRATA_FOLD_JUDGE / azure env), not an empty queue"
|
|
22890
|
+
},
|
|
22891
|
+
{
|
|
22892
|
+
id: "cite-anchor-accrual",
|
|
22893
|
+
what: "accrues cite-context anchor evidence on anchorless cited priors; two distinct sessions promote a cite-confirmed ANCHORED_AT",
|
|
22894
|
+
pass: "capture",
|
|
22895
|
+
// Conditional attr: only anchorless semantic priors cited from a known
|
|
22896
|
+
// working file carry hints — "written at all" is the honest floor.
|
|
22897
|
+
fed: { labels: ["Problem"], attr: "citeAnchorHints", minFraction: 0 },
|
|
22898
|
+
effectCounter: "citeAnchorsPromoted",
|
|
22899
|
+
note: "the supply-side fix for corroboration 0-for-8: promoted anchors are what the overlap gate intersects on the NEXT citation"
|
|
22900
|
+
},
|
|
22386
22901
|
{
|
|
22387
22902
|
id: "wm-reinforce-exposure",
|
|
22388
22903
|
what: "labels each paraphrase re-derivation with the prior's exposure (shown/evicted/unshown)",
|
|
@@ -22503,6 +23018,12 @@ __export(src_exports2, {
|
|
|
22503
23018
|
CREDIT_FAMILY: () => CREDIT_FAMILY,
|
|
22504
23019
|
DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
|
|
22505
23020
|
FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
|
|
23021
|
+
FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
|
|
23022
|
+
FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
|
|
23023
|
+
FOLD_REJECTED_CAP: () => FOLD_REJECTED_CAP,
|
|
23024
|
+
FOLD_SCOUT_AUTO_COSINE: () => FOLD_SCOUT_AUTO_COSINE,
|
|
23025
|
+
FOLD_SCOUT_MIN_COSINE: () => FOLD_SCOUT_MIN_COSINE,
|
|
23026
|
+
FOLD_SCOUT_MIN_JACCARD: () => FOLD_SCOUT_MIN_JACCARD,
|
|
22506
23027
|
JOINT_COMMUNITY_EDGES: () => JOINT_COMMUNITY_EDGES,
|
|
22507
23028
|
JOINT_COMMUNITY_LABELS: () => JOINT_COMMUNITY_LABELS,
|
|
22508
23029
|
MAX_ANCHOR_FILES: () => MAX_ANCHOR_FILES,
|
|
@@ -22512,11 +23033,17 @@ __export(src_exports2, {
|
|
|
22512
23033
|
PERCOLATING_DRIFT_EDGES: () => PERCOLATING_DRIFT_EDGES,
|
|
22513
23034
|
PERCOLATING_EDGES: () => PERCOLATING_EDGES,
|
|
22514
23035
|
PERCOLATING_LABELS: () => PERCOLATING_LABELS,
|
|
23036
|
+
RECURRENCE_DEBOUNCE_MS: () => RECURRENCE_DEBOUNCE_MS,
|
|
23037
|
+
REOPEN_MIN_SOURCES: () => REOPEN_MIN_SOURCES,
|
|
22515
23038
|
SAME_ANCHOR_DEDUP_JACCARD: () => SAME_ANCHOR_DEDUP_JACCARD,
|
|
23039
|
+
SAME_ANCHOR_NOMINATE_JACCARD: () => SAME_ANCHOR_NOMINATE_JACCARD,
|
|
23040
|
+
SAME_ANCHOR_SCOUT_AUTO_COSINE: () => SAME_ANCHOR_SCOUT_AUTO_COSINE,
|
|
23041
|
+
SAME_ANCHOR_SCOUT_MIN_COSINE: () => SAME_ANCHOR_SCOUT_MIN_COSINE,
|
|
22516
23042
|
STALL_MIN_RUNS: () => STALL_MIN_RUNS,
|
|
22517
23043
|
STALL_MIN_SPAN_MS: () => STALL_MIN_SPAN_MS,
|
|
22518
23044
|
SqliteGraphStore: () => SqliteGraphStore,
|
|
22519
23045
|
WM_CALIBRATION_LABELS: () => WM_CALIBRATION_LABELS,
|
|
23046
|
+
WORKSPACE_SCAN_CAP: () => WORKSPACE_SCAN_CAP,
|
|
22520
23047
|
addDependency: () => addDependency,
|
|
22521
23048
|
aggregateClaimConfidence: () => aggregateClaimConfidence,
|
|
22522
23049
|
anchorProblemToDiff: () => anchorProblemToDiff,
|
|
@@ -22540,6 +23067,7 @@ __export(src_exports2, {
|
|
|
22540
23067
|
communityInductionRequests: () => communityInductionRequests,
|
|
22541
23068
|
communitySeeds: () => communitySeeds,
|
|
22542
23069
|
compareSemver: () => compareSemver,
|
|
23070
|
+
confirmFixHeld: () => confirmFixHeld,
|
|
22543
23071
|
corroborateClaim: () => corroborateClaim,
|
|
22544
23072
|
creditAssignment: () => creditAssignment,
|
|
22545
23073
|
crystallize: () => crystallize,
|
|
@@ -22554,9 +23082,12 @@ __export(src_exports2, {
|
|
|
22554
23082
|
findMintTimeDuplicate: () => findMintTimeDuplicate,
|
|
22555
23083
|
findOrCreatePackage: () => findOrCreatePackage,
|
|
22556
23084
|
findPath: () => findPath,
|
|
23085
|
+
findResolvedAnchorMatch: () => findResolvedAnchorMatch,
|
|
22557
23086
|
findReusableSolution: () => findReusableSolution,
|
|
23087
|
+
findWorkspaceFoldCandidate: () => findWorkspaceFoldCandidate,
|
|
22558
23088
|
flattenCloudCounts: () => flattenCloudCounts,
|
|
22559
23089
|
foldDuplicateProblem: () => foldDuplicateProblem,
|
|
23090
|
+
foldSameAnchorProblem: () => foldSameAnchorProblem,
|
|
22560
23091
|
formatMechanismStatus: () => formatMechanismStatus,
|
|
22561
23092
|
getClaim: () => getClaim,
|
|
22562
23093
|
harvestAbstractionFences: () => harvestAbstractionFences,
|
|
@@ -22576,10 +23107,13 @@ __export(src_exports2, {
|
|
|
22576
23107
|
localEdgeViolation: () => localEdgeViolation,
|
|
22577
23108
|
markDiscriminatorAsked: () => markDiscriminatorAsked,
|
|
22578
23109
|
markFixCandidates: () => markFixCandidates,
|
|
23110
|
+
markFoldCandidate: () => markFoldCandidate,
|
|
23111
|
+
markResolvedRecurrence: () => markResolvedRecurrence,
|
|
22579
23112
|
markRevisit: () => markRevisit,
|
|
22580
23113
|
matchLanguagesInText: () => matchLanguagesInText,
|
|
22581
23114
|
matchPackagesInText: () => matchPackagesInText,
|
|
22582
23115
|
matchSymbolsInText: () => matchSymbolsInText,
|
|
23116
|
+
maybeReopenFromRegression: () => maybeReopenFromRegression,
|
|
22583
23117
|
mergeCloudCounts: () => mergeCloudCounts,
|
|
22584
23118
|
mergeDuplicateProblems: () => mergeDuplicateProblems,
|
|
22585
23119
|
migrateProjectAlias: () => migrateProjectAlias,
|
|
@@ -22615,6 +23149,7 @@ __export(src_exports2, {
|
|
|
22615
23149
|
repairLegacyAnchorsFromStatement: () => repairLegacyAnchorsFromStatement,
|
|
22616
23150
|
resolveDesignProblemById: () => resolveDesignProblemById,
|
|
22617
23151
|
resolveDesignProblemByStatement: () => resolveDesignProblemByStatement,
|
|
23152
|
+
resolveFoldCandidates: () => resolveFoldCandidates,
|
|
22618
23153
|
resolveOsNode: () => resolveOsNode,
|
|
22619
23154
|
retractDesignProblemById: () => retractDesignProblemById,
|
|
22620
23155
|
retractDesignProblemByStatement: () => retractDesignProblemByStatement,
|
|
@@ -22653,6 +23188,7 @@ var init_src5 = __esm({
|
|
|
22653
23188
|
init_community2();
|
|
22654
23189
|
init_triage2();
|
|
22655
23190
|
init_problem_dedup();
|
|
23191
|
+
init_fold_candidates();
|
|
22656
23192
|
init_problem_package_link();
|
|
22657
23193
|
init_tools();
|
|
22658
23194
|
init_principle_sync();
|
|
@@ -22681,7 +23217,19 @@ function buildSnapshot(opts) {
|
|
|
22681
23217
|
anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
|
|
22682
23218
|
}));
|
|
22683
23219
|
const recentConstraints = stillOpen.filter((p) => isConstraintProblem(p)).sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 3);
|
|
22684
|
-
const
|
|
23220
|
+
const regressions = problems.filter((p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0).flatMap(
|
|
23221
|
+
(open2) => opts.store.outEdges(open2.id, ["REGRESSION_OF"]).flatMap((e) => {
|
|
23222
|
+
const closed = opts.store.getNode(e.to);
|
|
23223
|
+
if (!closed || closed.attrs["resolutionSuspect"] !== true || closed.attrs["resolvedAt"] == null) {
|
|
23224
|
+
return [];
|
|
23225
|
+
}
|
|
23226
|
+
const solEdge = opts.store.outEdges(closed.id, ["SOLVED_BY"])[0];
|
|
23227
|
+
const solution = solEdge ? opts.store.getNode(solEdge.to) ?? void 0 : void 0;
|
|
23228
|
+
return [{ open: open2, closed, ...solution ? { solution } : {} }];
|
|
23229
|
+
})
|
|
23230
|
+
).sort((a, b) => Number(b.closed.attrs["recurredAt"] ?? 0) - Number(a.closed.attrs["recurredAt"] ?? 0)).slice(0, 2);
|
|
23231
|
+
const regressionClosedIds = new Set(regressions.map((r) => r.closed.id));
|
|
23232
|
+
const recentResolved = problems.filter((p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null).filter((p) => !regressionClosedIds.has(p.id)).sort((a, b) => Number(b.attrs["resolvedAt"] ?? 0) - Number(a.attrs["resolvedAt"] ?? 0)).slice(0, 4).map((p) => {
|
|
22685
23233
|
const solEdge = opts.store.outEdges(p.id, ["SOLVED_BY"])[0];
|
|
22686
23234
|
const solution = solEdge ? opts.store.getNode(solEdge.to) ?? void 0 : void 0;
|
|
22687
23235
|
return { node: p, ...solution ? { solution } : {} };
|
|
@@ -22751,6 +23299,7 @@ function buildSnapshot(opts) {
|
|
|
22751
23299
|
recentProblems: recent2,
|
|
22752
23300
|
recentResolved,
|
|
22753
23301
|
recentConstraints,
|
|
23302
|
+
regressions,
|
|
22754
23303
|
...causalNudge ? { causalNudge } : {},
|
|
22755
23304
|
...opts.selfCiteSkips && opts.selfCiteSkips > 0 ? { selfCiteSkips: opts.selfCiteSkips } : {},
|
|
22756
23305
|
...domainNudge ? { domainNudge } : {},
|
|
@@ -22872,6 +23421,14 @@ function renderSnapshot(s) {
|
|
|
22872
23421
|
);
|
|
22873
23422
|
lines.push("");
|
|
22874
23423
|
}
|
|
23424
|
+
if (s.daemonDistress) {
|
|
23425
|
+
const mins = Math.max(1, Math.round((Date.now() - s.daemonDistress.sinceTs) / 6e4));
|
|
23426
|
+
lines.push("### \u{1F6A8} errata daemon distress");
|
|
23427
|
+
lines.push(
|
|
23428
|
+
`${s.daemonDistress.body} (ongoing ~${mins} min). TELL YOUR USER about this now \u2014 the daemon cannot fix everything itself, and this line may be its only witness. \`errata status\` has detail; a daemon restart (\`errata stop\` then \`errata start\`) is safe mid-session.`
|
|
23429
|
+
);
|
|
23430
|
+
lines.push("");
|
|
23431
|
+
}
|
|
22875
23432
|
if (s.needsRevisit.length > 0) {
|
|
22876
23433
|
lines.push("### \u26A0\uFE0F Needs revisit \u2014 a fact these rested on changed");
|
|
22877
23434
|
lines.push(
|
|
@@ -22907,7 +23464,7 @@ function renderSnapshot(s) {
|
|
|
22907
23464
|
lines.push("");
|
|
22908
23465
|
}
|
|
22909
23466
|
lines.push("### Recently observed problems in this workspace");
|
|
22910
|
-
if (s.recentProblems.length === 0 && s.recentResolved.length === 0 && s.recentConstraints.length === 0) {
|
|
23467
|
+
if (s.recentProblems.length === 0 && s.recentResolved.length === 0 && s.recentConstraints.length === 0 && s.regressions.length === 0) {
|
|
22911
23468
|
lines.push("- _none yet \u2014 errata is still building its model_");
|
|
22912
23469
|
} else {
|
|
22913
23470
|
if (s.recentProblems.length > 0) {
|
|
@@ -22929,6 +23486,22 @@ function renderSnapshot(s) {
|
|
|
22929
23486
|
);
|
|
22930
23487
|
}
|
|
22931
23488
|
}
|
|
23489
|
+
if (s.regressions.length > 0) {
|
|
23490
|
+
lines.push("_\u{1F501} RECURRED \u2014 a previously-solved problem appears to be back; did the fix hold?_");
|
|
23491
|
+
for (const g of s.regressions) {
|
|
23492
|
+
lines.push(`- **${g.open.description}** \u2014 \`${g.open.id}\`${tagOf(g.open)}`);
|
|
23493
|
+
lines.push(` - previously resolved as: ${g.closed.description.slice(0, 120)} \u2014 \`${g.closed.id}\``);
|
|
23494
|
+
if (g.solution) {
|
|
23495
|
+
lines.push(
|
|
23496
|
+
` - the fix on record: ${g.solution.description.slice(0, 120)} \u2014 \`${g.solution.id}\` \xB7 fix still holds \u2192 (confirm:[${g.solution.id}]) \xB7 hitting it again \u2192 flag it (a second independent witness reopens the close)`
|
|
23497
|
+
);
|
|
23498
|
+
} else {
|
|
23499
|
+
lines.push(
|
|
23500
|
+
` - no fix prose on record \xB7 hitting it again \u2192 flag it (a second independent witness reopens the close)`
|
|
23501
|
+
);
|
|
23502
|
+
}
|
|
23503
|
+
}
|
|
23504
|
+
}
|
|
22932
23505
|
if (s.recentConstraints.length > 0) {
|
|
22933
23506
|
lines.push("_Design tensions \u2014 constraints this work is shaped around, not defects to fix:_");
|
|
22934
23507
|
for (const c of s.recentConstraints) {
|
|
@@ -22963,12 +23536,31 @@ function renderSnapshot(s) {
|
|
|
22963
23536
|
}
|
|
22964
23537
|
lines.push("");
|
|
22965
23538
|
if (s.remote && s.remote.length > 0) {
|
|
23539
|
+
const stratumOf = (m) => {
|
|
23540
|
+
const v = m.attrs?.["stratum"];
|
|
23541
|
+
return v === "project" || v === "team" || v === "org" ? v : void 0;
|
|
23542
|
+
};
|
|
23543
|
+
const contributorOf = (m) => {
|
|
23544
|
+
if (stratumOf(m) === void 0) return void 0;
|
|
23545
|
+
const c = m.attrs?.["contributor"];
|
|
23546
|
+
if (!c || typeof c.label !== "string" || c.label.length === 0) return void 0;
|
|
23547
|
+
return { label: c.label, ...typeof c.team === "string" && c.team ? { team: c.team } : {} };
|
|
23548
|
+
};
|
|
23549
|
+
const hasOwnOrg = s.remote.some((m) => stratumOf(m) !== void 0);
|
|
22966
23550
|
lines.push("### From the Errata Network \u2014 collective priors");
|
|
22967
23551
|
lines.push(
|
|
22968
|
-
"_A small sample of knowledge
|
|
23552
|
+
"_A small sample of knowledge facing your stack/domain (not yet local \u2014 the Network holds far more; these ids seed deeper bursts). Unmarked entries are network-generalized: corroborated across teams, but not from this repo \u2014 treat as pattern and re-derive locally._"
|
|
22969
23553
|
);
|
|
23554
|
+
if (hasOwnOrg) {
|
|
23555
|
+
lines.push(
|
|
23556
|
+
"_Entries marked `[org]`/`[team]`/`[project]` are from YOUR org's membrane \u2014 specifics are real and checkable; someone here already hit this, so consider coordinating before re-deriving. A `\xB7 name` after the stratum is the authoring installation \u2014 who to coordinate with._"
|
|
23557
|
+
);
|
|
23558
|
+
}
|
|
22970
23559
|
for (const m of s.remote) {
|
|
22971
|
-
|
|
23560
|
+
const stratum = stratumOf(m);
|
|
23561
|
+
const contributor = contributorOf(m);
|
|
23562
|
+
const chip = stratum ? `\`[${stratum}${contributor ? ` \xB7 ${contributor.label}${contributor.team ? `/${contributor.team}` : ""}` : ""}]\` ` : "";
|
|
23563
|
+
lines.push(`- ${chip}**${m.label}:** ${m.description}${tagOf(m)}`);
|
|
22972
23564
|
}
|
|
22973
23565
|
lines.push("");
|
|
22974
23566
|
}
|
|
@@ -23165,6 +23757,7 @@ function assembleAgentContext(opts) {
|
|
|
23165
23757
|
});
|
|
23166
23758
|
if (opts.edgeElicitation) snapshot.edgeElicitation = opts.edgeElicitation;
|
|
23167
23759
|
if (opts.pendingUpdate) snapshot.pendingUpdate = opts.pendingUpdate;
|
|
23760
|
+
if (opts.daemonDistress) snapshot.daemonDistress = opts.daemonDistress;
|
|
23168
23761
|
if (opts.reviewItems?.length) snapshot.reviewItems = [...opts.reviewItems];
|
|
23169
23762
|
if (opts.statedIntent) snapshot.statedIntent = opts.statedIntent;
|
|
23170
23763
|
if (opts.remote && opts.remote.length > 0) {
|
|
@@ -23750,7 +24343,7 @@ function provenanceHeaders(provenance) {
|
|
|
23750
24343
|
...provenance.agentModel ? { "x-inerrata-agent-model": provenance.agentModel } : {}
|
|
23751
24344
|
};
|
|
23752
24345
|
}
|
|
23753
|
-
var asWireCount, INGEST_NODE_CHUNK, CloudClient, CloudError;
|
|
24346
|
+
var asWireCount, INGEST_NODE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
|
|
23754
24347
|
var init_client = __esm({
|
|
23755
24348
|
"../../packages/cloud-client/src/client.ts"() {
|
|
23756
24349
|
"use strict";
|
|
@@ -23758,6 +24351,7 @@ var init_client = __esm({
|
|
|
23758
24351
|
init_src();
|
|
23759
24352
|
asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
|
|
23760
24353
|
INGEST_NODE_CHUNK = 8;
|
|
24354
|
+
E6B_PUBLIC_CONTEXT_SENTINEL = "00000000-0000-4e6b-8000-000000000e6b";
|
|
23761
24355
|
CloudClient = class {
|
|
23762
24356
|
baseUrl;
|
|
23763
24357
|
apiKey;
|
|
@@ -24149,7 +24743,19 @@ var init_client = __esm({
|
|
|
24149
24743
|
if (q.seed?.length) qs.set("seed", q.seed.join(","));
|
|
24150
24744
|
if (q.q) qs.set("q", q.q);
|
|
24151
24745
|
if (q.channel) qs.set("channel", q.channel);
|
|
24152
|
-
const
|
|
24746
|
+
const publicCtx = process.env["ERRATA_PRIMING_PUBLIC_CONTEXT"];
|
|
24747
|
+
const usePublicCtx = Boolean(publicCtx && publicCtx !== "0" && q.channel === "priming");
|
|
24748
|
+
const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24749
|
+
const res = await this.json(
|
|
24750
|
+
"GET",
|
|
24751
|
+
`/v2/search?${qs.toString()}`,
|
|
24752
|
+
void 0,
|
|
24753
|
+
usePublicCtx ? {
|
|
24754
|
+
extraHeaders: {
|
|
24755
|
+
"X-Inerrata-Project-Id": UUID_RE2.test(publicCtx) ? publicCtx : E6B_PUBLIC_CONTEXT_SENTINEL
|
|
24756
|
+
}
|
|
24757
|
+
} : void 0
|
|
24758
|
+
);
|
|
24153
24759
|
const { nodes, edges } = this.toCastalia(res);
|
|
24154
24760
|
return {
|
|
24155
24761
|
nodes,
|
|
@@ -24313,7 +24919,8 @@ var init_client = __esm({
|
|
|
24313
24919
|
const headers = {
|
|
24314
24920
|
"content-type": "application/json",
|
|
24315
24921
|
accept: "application/json",
|
|
24316
|
-
...provenanceHeaders(this.provenance)
|
|
24922
|
+
...provenanceHeaders(this.provenance),
|
|
24923
|
+
...opts?.extraHeaders ?? {}
|
|
24317
24924
|
};
|
|
24318
24925
|
if (!opts?.skipAuth) {
|
|
24319
24926
|
const token = opts?.bearerToken ?? await this.authToken();
|
|
@@ -24527,6 +25134,7 @@ function defaultConfig() {
|
|
|
24527
25134
|
// user opts into sync at all.
|
|
24528
25135
|
consent: { sync: false, telemetry: false, contributePackages: true },
|
|
24529
25136
|
notifications: true,
|
|
25137
|
+
opsNotices: false,
|
|
24530
25138
|
onboardedAt: null,
|
|
24531
25139
|
machineId: null,
|
|
24532
25140
|
updateChannel: "dev",
|
|
@@ -24669,7 +25277,7 @@ var init_config = __esm({
|
|
|
24669
25277
|
"use strict";
|
|
24670
25278
|
init_paths();
|
|
24671
25279
|
LEGACY_CLOUD_URL = "https://inerrata-gateway.onrender.com";
|
|
24672
|
-
DEFAULT_CLOUD_URL = true ? "https://
|
|
25280
|
+
DEFAULT_CLOUD_URL = true ? "https://inerrata.dev" : DEV_CLOUD_URL;
|
|
24673
25281
|
}
|
|
24674
25282
|
});
|
|
24675
25283
|
|
|
@@ -28577,7 +29185,13 @@ function generalizeSymbols(text, lexicon, level = 1, fileLexicon) {
|
|
|
28577
29185
|
}
|
|
28578
29186
|
return generalize(out2, { level }).text;
|
|
28579
29187
|
}
|
|
28580
|
-
|
|
29188
|
+
function registerRenderedContributorLabels(labels) {
|
|
29189
|
+
for (const label of labels) {
|
|
29190
|
+
const trimmed = label.trim();
|
|
29191
|
+
if (trimmed.length >= 3) renderedContributorLabels.add(trimmed);
|
|
29192
|
+
}
|
|
29193
|
+
}
|
|
29194
|
+
var SYMBOL_LABELS2, KIND_PHRASE, BUILTIN_TYPE_NAMES, DOC_COMMENT_KINDS, MAX_DOC_CHARS, RE_RESERVED2, escapeRe4, RE_LEADING_ARTICLE2, CONVENTION_FILENAMES, CONVENTION_BASENAME_MIN_FILES, CONVENTION_STEMS, FILE_TOKEN_RE, STEM_TOKEN_RE, SOURCE_EXTENSIONS, TOKEN_RE3, renderedContributorLabels;
|
|
28581
29195
|
var init_generalize_graph = __esm({
|
|
28582
29196
|
"src/generalize-graph.ts"() {
|
|
28583
29197
|
"use strict";
|
|
@@ -28718,6 +29332,7 @@ var init_generalize_graph = __esm({
|
|
|
28718
29332
|
"cypher"
|
|
28719
29333
|
]);
|
|
28720
29334
|
TOKEN_RE3 = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
|
|
29335
|
+
renderedContributorLabels = /* @__PURE__ */ new Set();
|
|
28721
29336
|
}
|
|
28722
29337
|
});
|
|
28723
29338
|
|
|
@@ -29228,14 +29843,16 @@ function rankOpenProblemNeighbors(store, embedding, limit = 5) {
|
|
|
29228
29843
|
}
|
|
29229
29844
|
function mergeProblemsByEmbedding(store, opts) {
|
|
29230
29845
|
const minCosine = opts.minCosine ?? NEMORI_DEDUP_COSINE;
|
|
29231
|
-
const
|
|
29846
|
+
const NEAR_MISS_FLOOR = 0.72;
|
|
29847
|
+
const report = { clusters: 0, merged: 0, nearMissMarked: 0 };
|
|
29232
29848
|
const open2 = store.findNodesByLabel("Problem").filter(
|
|
29233
29849
|
(p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.embedding.length > 0
|
|
29234
29850
|
);
|
|
29235
29851
|
const corro = (n) => Number(n.attrs["corroborations"] ?? 0);
|
|
29236
29852
|
const consumed = /* @__PURE__ */ new Set();
|
|
29853
|
+
const seeds = opts.freshSince === void 0 ? open2 : open2.filter((p) => p.createdAt > opts.freshSince);
|
|
29237
29854
|
store.transaction(() => {
|
|
29238
|
-
for (const a of
|
|
29855
|
+
for (const a of seeds) {
|
|
29239
29856
|
if (consumed.has(a.id)) continue;
|
|
29240
29857
|
const cluster = [a];
|
|
29241
29858
|
for (const b of open2) {
|
|
@@ -29243,9 +29860,15 @@ function mergeProblemsByEmbedding(store, opts) {
|
|
|
29243
29860
|
if (b.embedding.length !== a.embedding.length) continue;
|
|
29244
29861
|
if ((a.attrs["embeddingVersion"] ?? "") !== (b.attrs["embeddingVersion"] ?? "")) continue;
|
|
29245
29862
|
if (isConstraintProblem(a) !== isConstraintProblem(b)) continue;
|
|
29246
|
-
|
|
29863
|
+
const cos = cosine(a.embedding, b.embedding);
|
|
29864
|
+
if (cos >= minCosine) {
|
|
29247
29865
|
cluster.push(b);
|
|
29248
29866
|
consumed.add(b.id);
|
|
29867
|
+
} else if (cos >= NEAR_MISS_FLOOR) {
|
|
29868
|
+
const [younger, elder] = a.createdAt >= b.createdAt ? [a, b] : [b, a];
|
|
29869
|
+
if (markFoldCandidate(store, younger.id, elder.id, { hashCos: cos, source: "settle-nearmiss", ts: opts.ts })) {
|
|
29870
|
+
report.nearMissMarked = (report.nearMissMarked ?? 0) + 1;
|
|
29871
|
+
}
|
|
29249
29872
|
}
|
|
29250
29873
|
}
|
|
29251
29874
|
if (cluster.length < 2) continue;
|
|
@@ -29260,6 +29883,50 @@ function mergeProblemsByEmbedding(store, opts) {
|
|
|
29260
29883
|
});
|
|
29261
29884
|
return report;
|
|
29262
29885
|
}
|
|
29886
|
+
function bindResolvedRegressions(store, opts) {
|
|
29887
|
+
const minCosine = opts.minCosine ?? NEMORI_DEDUP_COSINE;
|
|
29888
|
+
const watermarkRaw = store.getMeta?.("regression_sweep_at") ?? null;
|
|
29889
|
+
const full = watermarkRaw === null;
|
|
29890
|
+
const since = full ? 0 : Number(watermarkRaw) || 0;
|
|
29891
|
+
const live2 = (n) => n.attrs["mergedInto"] === void 0;
|
|
29892
|
+
const problems = store.findNodesByLabel("Problem").filter((p) => p.embedding.length > 0 && live2(p));
|
|
29893
|
+
const fresh = problems.filter((p) => p.attrs["resolvedAt"] == null && p.createdAt > since);
|
|
29894
|
+
const resolved = problems.filter(
|
|
29895
|
+
(p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null
|
|
29896
|
+
);
|
|
29897
|
+
const report = { scanned: fresh.length, bound: 0, full };
|
|
29898
|
+
if (fresh.length > 0 && resolved.length > 0) {
|
|
29899
|
+
for (const f of fresh) {
|
|
29900
|
+
let best = null;
|
|
29901
|
+
for (const r of resolved) {
|
|
29902
|
+
if (r.embedding.length !== f.embedding.length) continue;
|
|
29903
|
+
if ((r.attrs["embeddingVersion"] ?? "") !== (f.attrs["embeddingVersion"] ?? "")) continue;
|
|
29904
|
+
if (isConstraintProblem(f) !== isConstraintProblem(r)) continue;
|
|
29905
|
+
const s = cosine(f.embedding, r.embedding);
|
|
29906
|
+
if (s >= minCosine && (!best || s > best.s)) best = { node: r, s };
|
|
29907
|
+
}
|
|
29908
|
+
if (!best) continue;
|
|
29909
|
+
if (store.outEdges(f.id, ["REGRESSION_OF"]).some((e) => e.to === best.node.id)) continue;
|
|
29910
|
+
if (!markResolvedRecurrence(store, best.node.id, opts.ts).marked) continue;
|
|
29911
|
+
store.mergeEdge({
|
|
29912
|
+
id: `edge_${digest({ from: f.id, type: "REGRESSION_OF", to: best.node.id })}`.slice(0, 24),
|
|
29913
|
+
from: f.id,
|
|
29914
|
+
to: best.node.id,
|
|
29915
|
+
type: "REGRESSION_OF",
|
|
29916
|
+
confidence: 0.5,
|
|
29917
|
+
extractionSource: "daemon-extracted",
|
|
29918
|
+
createdAt: opts.ts,
|
|
29919
|
+
lastSeenAt: opts.ts,
|
|
29920
|
+
navSuccesses: 0,
|
|
29921
|
+
navFailures: 0,
|
|
29922
|
+
attrs: { provisional: true, machineProposed: true, cosine: Math.round(best.s * 100) / 100 }
|
|
29923
|
+
});
|
|
29924
|
+
report.bound++;
|
|
29925
|
+
}
|
|
29926
|
+
}
|
|
29927
|
+
store.setMeta?.("regression_sweep_at", String(opts.ts));
|
|
29928
|
+
return report;
|
|
29929
|
+
}
|
|
29263
29930
|
function bindCanonicalNeighbors(store, opts) {
|
|
29264
29931
|
const k = opts.k ?? 3;
|
|
29265
29932
|
const live2 = (n) => n.attrs["mergedInto"] === void 0;
|
|
@@ -30741,6 +31408,7 @@ __export(src_exports4, {
|
|
|
30741
31408
|
annotateResolution: () => annotateResolution,
|
|
30742
31409
|
attributeRootCause: () => attributeRootCause,
|
|
30743
31410
|
bindCanonicalNeighbors: () => bindCanonicalNeighbors,
|
|
31411
|
+
bindResolvedRegressions: () => bindResolvedRegressions,
|
|
30744
31412
|
closeProblem: () => closeProblem,
|
|
30745
31413
|
codeAnchorProjection: () => codeAnchorProjection,
|
|
30746
31414
|
commandSignature: () => commandSignature,
|
|
@@ -39686,6 +40354,8 @@ var init_reconcile = __esm({
|
|
|
39686
40354
|
var mcp_exports = {};
|
|
39687
40355
|
__export(mcp_exports, {
|
|
39688
40356
|
INSTRUCTIONS: () => INSTRUCTIONS,
|
|
40357
|
+
RG_REFUTED_DAMP: () => RG_REFUTED_DAMP,
|
|
40358
|
+
RG_SUSPECT_BOOST: () => RG_SUSPECT_BOOST,
|
|
39689
40359
|
createMcpHandler: () => createMcpHandler,
|
|
39690
40360
|
runMcpServer: () => runMcpServer,
|
|
39691
40361
|
runTool: () => runTool,
|
|
@@ -39757,6 +40427,22 @@ function takeDiscriminatorNudge(path2) {
|
|
|
39757
40427
|
shared.close();
|
|
39758
40428
|
}
|
|
39759
40429
|
}
|
|
40430
|
+
function hitState(n) {
|
|
40431
|
+
const reasons = n.attrs["refuteReasons"];
|
|
40432
|
+
if (n.attrs["resolutionSuspect"] === true && n.attrs["resolvedAt"] != null) {
|
|
40433
|
+
return { state: "regression-suspect", factor: RG_SUSPECT_BOOST };
|
|
40434
|
+
}
|
|
40435
|
+
if (n.attrs["refutedAt"] != null) {
|
|
40436
|
+
return { state: "refuted", ...reasons?.[0] ? { refutedWhy: reasons[0] } : {}, factor: RG_REFUTED_DAMP };
|
|
40437
|
+
}
|
|
40438
|
+
if (n.attrs["reopenedFrom"] === "regression") {
|
|
40439
|
+
return { state: "reopened-regression", factor: RG_SUSPECT_BOOST };
|
|
40440
|
+
}
|
|
40441
|
+
if (n.attrs["resolvedAt"] != null && n.attrs["resolvedAs"] == null) {
|
|
40442
|
+
return { state: "resolved", factor: 1 };
|
|
40443
|
+
}
|
|
40444
|
+
return { factor: 1 };
|
|
40445
|
+
}
|
|
39760
40446
|
function commentAnchorId(store, commentId) {
|
|
39761
40447
|
const out2 = store.outEdges(commentId, ["EXPLAINS", "ANNOTATES"]);
|
|
39762
40448
|
if (out2.length > 0) return out2[0].to;
|
|
@@ -39821,14 +40507,16 @@ function searchGraph(store, query, limit) {
|
|
|
39821
40507
|
}
|
|
39822
40508
|
add(node2, matched);
|
|
39823
40509
|
}
|
|
39824
|
-
return [...merged.values()].sort(
|
|
39825
|
-
(a, b) => rankTier(a.node) - rankTier(b.node) || b.matched - a.matched || b.node.pageRank - a.node.pageRank
|
|
39826
|
-
).slice(0, limit).map(({ node: node2, via }) => ({
|
|
40510
|
+
return [...merged.values()].map((e) => ({ ...e, rg: hitState(e.node) })).sort(
|
|
40511
|
+
(a, b) => rankTier(a.node) - rankTier(b.node) || b.matched - a.matched || b.node.pageRank * b.rg.factor - a.node.pageRank * a.rg.factor
|
|
40512
|
+
).slice(0, limit).map(({ node: node2, via, rg }) => ({
|
|
39827
40513
|
id: node2.id,
|
|
39828
40514
|
label: node2.label,
|
|
39829
40515
|
description: node2.description,
|
|
39830
40516
|
pageRank: node2.pageRank,
|
|
39831
|
-
...via ? { via } : {}
|
|
40517
|
+
...via ? { via } : {},
|
|
40518
|
+
...rg.state ? { state: rg.state } : {},
|
|
40519
|
+
...rg.refutedWhy ? { refutedWhy: rg.refutedWhy } : {}
|
|
39832
40520
|
}));
|
|
39833
40521
|
}
|
|
39834
40522
|
function runTool(name2, args2, store, ctx = {}) {
|
|
@@ -39970,7 +40658,10 @@ function unresolved(args2) {
|
|
|
39970
40658
|
const q = args2["qname"] ?? args2["nodeId"];
|
|
39971
40659
|
return { found: false, reason: "unresolved", query: typeof q === "string" ? q : "" };
|
|
39972
40660
|
}
|
|
39973
|
-
function createMcpHandler(store, ctx = {}) {
|
|
40661
|
+
function createMcpHandler(store, ctx = {}, options = {}) {
|
|
40662
|
+
const bareToolNames = options.toolNameStyle === "bare";
|
|
40663
|
+
const publicToolName = (canonical) => bareToolNames ? canonical.replace(/^errata\./, "") : canonical;
|
|
40664
|
+
const canonicalToolName = (presented) => bareToolNames && presented && !presented.includes(".") ? `errata.${presented}` : presented;
|
|
39974
40665
|
let auditReadRun = 0;
|
|
39975
40666
|
let toolCallIndex = 0;
|
|
39976
40667
|
let lastAuditNudgeAt = Number.NEGATIVE_INFINITY;
|
|
@@ -40000,7 +40691,7 @@ function createMcpHandler(store, ctx = {}) {
|
|
|
40000
40691
|
id,
|
|
40001
40692
|
result: {
|
|
40002
40693
|
tools: TOOLS.map((t) => ({
|
|
40003
|
-
name: t.name,
|
|
40694
|
+
name: publicToolName(t.name),
|
|
40004
40695
|
description: t.description,
|
|
40005
40696
|
inputSchema: t.inputSchema
|
|
40006
40697
|
}))
|
|
@@ -40008,7 +40699,8 @@ function createMcpHandler(store, ctx = {}) {
|
|
|
40008
40699
|
};
|
|
40009
40700
|
case "tools/call": {
|
|
40010
40701
|
const params = req.params ?? {};
|
|
40011
|
-
const
|
|
40702
|
+
const requestedToolName = canonicalToolName(params.name);
|
|
40703
|
+
const tool = TOOLS.find((t) => t.name === requestedToolName);
|
|
40012
40704
|
if (!tool) {
|
|
40013
40705
|
return {
|
|
40014
40706
|
jsonrpc: "2.0",
|
|
@@ -40020,8 +40712,8 @@ function createMcpHandler(store, ctx = {}) {
|
|
|
40020
40712
|
const content = [
|
|
40021
40713
|
{ type: "text", text: JSON.stringify(out2, null, 2) }
|
|
40022
40714
|
];
|
|
40023
|
-
const isEnrichmentTool =
|
|
40024
|
-
const isTriageTool =
|
|
40715
|
+
const isEnrichmentTool = requestedToolName === "errata.pending_enrichment" || requestedToolName === "errata.annotate_resolution";
|
|
40716
|
+
const isTriageTool = requestedToolName === "errata.pending_discriminators" || requestedToolName === "errata.triage";
|
|
40025
40717
|
toolCallIndex++;
|
|
40026
40718
|
auditReadRun = isEnrichmentTool ? 0 : auditReadRun + 1;
|
|
40027
40719
|
if (!isEnrichmentTool) {
|
|
@@ -40087,13 +40779,13 @@ function buildToolContext(workspaceRoot) {
|
|
|
40087
40779
|
}
|
|
40088
40780
|
return base;
|
|
40089
40781
|
}
|
|
40090
|
-
async function runMcpServer(workspaceRoot) {
|
|
40782
|
+
async function runMcpServer(workspaceRoot, options = {}) {
|
|
40091
40783
|
const paths = workspacePaths(workspaceRoot);
|
|
40092
40784
|
const store = openGraphStore({ path: paths.castalia });
|
|
40093
40785
|
const handle2 = createMcpHandler(store, {
|
|
40094
40786
|
...buildToolContext(workspaceRoot),
|
|
40095
40787
|
sharedDbPath: sharedStorePath()
|
|
40096
|
-
});
|
|
40788
|
+
}, options);
|
|
40097
40789
|
process.stdin.setEncoding("utf8");
|
|
40098
40790
|
let buffer = "";
|
|
40099
40791
|
process.stdin.on("data", (chunk) => {
|
|
@@ -40121,7 +40813,7 @@ async function runMcpServer(workspaceRoot) {
|
|
|
40121
40813
|
if (resp) process.stdout.write(JSON.stringify(resp) + "\n");
|
|
40122
40814
|
}
|
|
40123
40815
|
}
|
|
40124
|
-
var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, DISCRIMINATOR_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, TOOLS;
|
|
40816
|
+
var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, DISCRIMINATOR_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, RG_REFUTED_DAMP, RG_SUSPECT_BOOST, TOOLS;
|
|
40125
40817
|
var init_mcp = __esm({
|
|
40126
40818
|
"src/mcp.ts"() {
|
|
40127
40819
|
"use strict";
|
|
@@ -40185,6 +40877,8 @@ var init_mcp = __esm({
|
|
|
40185
40877
|
AUDIT_NUDGE_COOLDOWN = 12;
|
|
40186
40878
|
DISCRIMINATOR_NUDGE_COOLDOWN = 40;
|
|
40187
40879
|
AUDIT_FLAG_NUDGE = "\u26A1 errata \u2014 reading/auditing? anything you notice that's off, flag it inline as `[!one line]` (`[?\u2026]` = TODO) \u2014 no tool call, we harvest it.";
|
|
40880
|
+
RG_REFUTED_DAMP = 0.75;
|
|
40881
|
+
RG_SUSPECT_BOOST = 1.25;
|
|
40188
40882
|
TOOLS = [
|
|
40189
40883
|
{
|
|
40190
40884
|
name: "errata.switch_context",
|
|
@@ -52676,6 +53370,7 @@ function watch(paths, options = {}) {
|
|
|
52676
53370
|
var esm_default = { watch, FSWatcher };
|
|
52677
53371
|
|
|
52678
53372
|
// src/engine.ts
|
|
53373
|
+
init_generalize_graph();
|
|
52679
53374
|
init_src();
|
|
52680
53375
|
init_src2();
|
|
52681
53376
|
init_src5();
|
|
@@ -52837,6 +53532,91 @@ function openEventLog(opts) {
|
|
|
52837
53532
|
return new EventLog(opts);
|
|
52838
53533
|
}
|
|
52839
53534
|
|
|
53535
|
+
// src/llm-provider.ts
|
|
53536
|
+
function chatProvider() {
|
|
53537
|
+
return (process.env["EXTRACTION_PROVIDER"] ?? "azure").toLowerCase() === "anthropic" ? "anthropic" : "azure";
|
|
53538
|
+
}
|
|
53539
|
+
function deploymentFor(model) {
|
|
53540
|
+
const key = `MODEL_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_OPENAI`;
|
|
53541
|
+
return process.env[key] ?? process.env["AZURE_OPENAI_DEPLOYMENT"];
|
|
53542
|
+
}
|
|
53543
|
+
function isChatConfigured(model) {
|
|
53544
|
+
if (chatProvider() === "azure") {
|
|
53545
|
+
return Boolean(
|
|
53546
|
+
process.env["AZURE_OPENAI_API_KEY"] && process.env["AZURE_OPENAI_ENDPOINT"] && deploymentFor(model)
|
|
53547
|
+
);
|
|
53548
|
+
}
|
|
53549
|
+
return Boolean(process.env["ANTHROPIC_API_KEY"]);
|
|
53550
|
+
}
|
|
53551
|
+
async function chat(req) {
|
|
53552
|
+
const provider = chatProvider();
|
|
53553
|
+
try {
|
|
53554
|
+
if (provider === "azure") {
|
|
53555
|
+
const endpoint = (process.env["AZURE_OPENAI_ENDPOINT"] ?? "").replace(/\/+$/, "");
|
|
53556
|
+
const version2 = process.env["AZURE_OPENAI_API_VERSION"] ?? "2024-10-21";
|
|
53557
|
+
const deployment = deploymentFor(req.model);
|
|
53558
|
+
if (!endpoint || !deployment) {
|
|
53559
|
+
console.warn("[errata] llm: azure selected but endpoint/deployment missing \u2014 skipping");
|
|
53560
|
+
return null;
|
|
53561
|
+
}
|
|
53562
|
+
const resp2 = await fetch(
|
|
53563
|
+
`${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${version2}`,
|
|
53564
|
+
{
|
|
53565
|
+
method: "POST",
|
|
53566
|
+
headers: {
|
|
53567
|
+
"api-key": process.env["AZURE_OPENAI_API_KEY"] ?? "",
|
|
53568
|
+
"content-type": "application/json"
|
|
53569
|
+
},
|
|
53570
|
+
body: JSON.stringify({
|
|
53571
|
+
messages: [
|
|
53572
|
+
...req.system ? [{ role: "system", content: req.system }] : [],
|
|
53573
|
+
{ role: "user", content: req.user }
|
|
53574
|
+
],
|
|
53575
|
+
// `max_completion_tokens`: the newer deployments reject `max_tokens`.
|
|
53576
|
+
max_completion_tokens: req.maxTokens
|
|
53577
|
+
})
|
|
53578
|
+
}
|
|
53579
|
+
);
|
|
53580
|
+
if (!resp2.ok) {
|
|
53581
|
+
console.warn(
|
|
53582
|
+
`[errata] llm: azure ${resp2.status} \u2014 ${(await resp2.text().catch(() => "")).slice(0, 200)}`
|
|
53583
|
+
);
|
|
53584
|
+
return null;
|
|
53585
|
+
}
|
|
53586
|
+
const json3 = await resp2.json();
|
|
53587
|
+
return json3.choices?.[0]?.message?.content ?? null;
|
|
53588
|
+
}
|
|
53589
|
+
const resp = await fetch("https://api.anthropic.com/v1/messages", {
|
|
53590
|
+
method: "POST",
|
|
53591
|
+
headers: {
|
|
53592
|
+
"x-api-key": process.env["ANTHROPIC_API_KEY"] ?? "",
|
|
53593
|
+
"anthropic-version": "2023-06-01",
|
|
53594
|
+
"content-type": "application/json"
|
|
53595
|
+
},
|
|
53596
|
+
body: JSON.stringify({
|
|
53597
|
+
model: req.model,
|
|
53598
|
+
max_tokens: req.maxTokens,
|
|
53599
|
+
...req.system ? { system: req.system } : {},
|
|
53600
|
+
messages: [{ role: "user", content: req.user }]
|
|
53601
|
+
})
|
|
53602
|
+
});
|
|
53603
|
+
if (!resp.ok) {
|
|
53604
|
+
console.warn(
|
|
53605
|
+
`[errata] llm: anthropic ${resp.status} \u2014 ${(await resp.text().catch(() => "")).slice(0, 200)}`
|
|
53606
|
+
);
|
|
53607
|
+
return null;
|
|
53608
|
+
}
|
|
53609
|
+
const json2 = await resp.json();
|
|
53610
|
+
return json2.content?.find((c) => c.type === "text")?.text ?? null;
|
|
53611
|
+
} catch (err2) {
|
|
53612
|
+
console.warn(
|
|
53613
|
+
`[errata] llm: ${provider} transport failed \u2014`,
|
|
53614
|
+
err2 instanceof Error ? err2.message : err2
|
|
53615
|
+
);
|
|
53616
|
+
return null;
|
|
53617
|
+
}
|
|
53618
|
+
}
|
|
53619
|
+
|
|
52840
53620
|
// src/engine.ts
|
|
52841
53621
|
init_src11();
|
|
52842
53622
|
init_src6();
|
|
@@ -52844,9 +53624,10 @@ init_src10();
|
|
|
52844
53624
|
init_review2();
|
|
52845
53625
|
|
|
52846
53626
|
// src/turn.ts
|
|
52847
|
-
import { closeSync, existsSync as existsSync13, fstatSync, openSync, readdirSync as readdirSync6, readSync, statSync as statSync4 } from "node:fs";
|
|
53627
|
+
import { closeSync, existsSync as existsSync13, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync4 } from "node:fs";
|
|
52848
53628
|
import { basename as basename3, dirname as dirname8, join as join16 } from "node:path";
|
|
52849
53629
|
import { homedir as homedir4 } from "node:os";
|
|
53630
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
52850
53631
|
function readFrom(path2, fromByte, maxBytes) {
|
|
52851
53632
|
let fd;
|
|
52852
53633
|
try {
|
|
@@ -52964,6 +53745,8 @@ function parseDesignResolutions(text) {
|
|
|
52964
53745
|
}
|
|
52965
53746
|
return out2;
|
|
52966
53747
|
}
|
|
53748
|
+
var OPENCODE_PARSER_PINNED_VERSION = "1.18.18";
|
|
53749
|
+
var OPENCODE_TRANSCRIPT_HEADER_TYPE = "errata-opencode-session";
|
|
52967
53750
|
var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
52968
53751
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
52969
53752
|
function isUserTurnBoundary(obj) {
|
|
@@ -52997,7 +53780,161 @@ function readAssistantTurnsFrom(transcriptPath, fromByte, includeThinking = true
|
|
|
52997
53780
|
losses: { parseFailures: parsed.parseFailures, ioFailed, bytesUnreachable: 0 }
|
|
52998
53781
|
};
|
|
52999
53782
|
}
|
|
53783
|
+
var CODEX_PARSER_PINNED_VERSION = "0.144.5";
|
|
53784
|
+
function detectHostHarness(raw2) {
|
|
53785
|
+
const lines = raw2.split(/\r?\n/);
|
|
53786
|
+
let checked = 0;
|
|
53787
|
+
for (const line of lines) {
|
|
53788
|
+
if (!line.trim()) continue;
|
|
53789
|
+
if (checked++ >= 5) break;
|
|
53790
|
+
let obj;
|
|
53791
|
+
try {
|
|
53792
|
+
obj = JSON.parse(line);
|
|
53793
|
+
} catch {
|
|
53794
|
+
continue;
|
|
53795
|
+
}
|
|
53796
|
+
if (obj.type === "response_item" || obj.type === "event_msg" || obj.type === "session_meta" || obj.type === "turn_context" || obj.type === "world_state") {
|
|
53797
|
+
return "codex";
|
|
53798
|
+
}
|
|
53799
|
+
if (obj.type === "gemini" || "$set" in obj || "projectHash" in obj && "sessionId" in obj || obj.type === "user" && !("message" in obj) && "content" in obj) {
|
|
53800
|
+
return "gemini";
|
|
53801
|
+
}
|
|
53802
|
+
if (obj.type === OPENCODE_TRANSCRIPT_HEADER_TYPE || obj.type === void 0 && "info" in obj && Array.isArray(obj["parts"]) && typeof obj["info"]?.["role"] === "string") {
|
|
53803
|
+
return "opencode";
|
|
53804
|
+
}
|
|
53805
|
+
if (obj.type === "user" || obj.type === "assistant" || obj.type === "summary") {
|
|
53806
|
+
return "claude_code";
|
|
53807
|
+
}
|
|
53808
|
+
}
|
|
53809
|
+
return "claude_code";
|
|
53810
|
+
}
|
|
53000
53811
|
function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false) {
|
|
53812
|
+
if (!raw2) return { turns: [], parseFailures: 0 };
|
|
53813
|
+
const harness = detectHostHarness(raw2);
|
|
53814
|
+
if (harness === "codex") return parseCodexAssistantTurns(raw2, skipFirstLineParseFailure);
|
|
53815
|
+
if (harness === "gemini") return parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure);
|
|
53816
|
+
if (harness === "opencode") return parseOpencodeAssistantTurns(raw2, skipFirstLineParseFailure);
|
|
53817
|
+
return parseClaudeAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure);
|
|
53818
|
+
}
|
|
53819
|
+
function parseOpencodeAssistantTurns(raw2, skipFirstLineParseFailure = false) {
|
|
53820
|
+
const lines = raw2.split(/\r?\n/);
|
|
53821
|
+
const byId = /* @__PURE__ */ new Map();
|
|
53822
|
+
let order = 0;
|
|
53823
|
+
let parseFailures = 0;
|
|
53824
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
53825
|
+
const line = lines[i2];
|
|
53826
|
+
if (!line) continue;
|
|
53827
|
+
let obj;
|
|
53828
|
+
try {
|
|
53829
|
+
obj = JSON.parse(line);
|
|
53830
|
+
} catch {
|
|
53831
|
+
if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
|
|
53832
|
+
continue;
|
|
53833
|
+
}
|
|
53834
|
+
if (!obj || typeof obj !== "object") continue;
|
|
53835
|
+
if (obj["type"] === OPENCODE_TRANSCRIPT_HEADER_TYPE) continue;
|
|
53836
|
+
const info2 = obj["info"];
|
|
53837
|
+
const parts2 = obj["parts"];
|
|
53838
|
+
if (!info2 || typeof info2 !== "object" || !Array.isArray(parts2)) continue;
|
|
53839
|
+
if (info2["role"] !== "assistant") continue;
|
|
53840
|
+
const texts = [];
|
|
53841
|
+
for (const p of parts2) {
|
|
53842
|
+
if (!p || p["type"] !== "text") continue;
|
|
53843
|
+
if (p["synthetic"] === true || p["ignored"] === true) continue;
|
|
53844
|
+
if (typeof p["text"] === "string" && p["text"].length > 0) texts.push(p["text"]);
|
|
53845
|
+
}
|
|
53846
|
+
if (texts.length === 0) continue;
|
|
53847
|
+
const id = typeof info2["id"] === "string" ? info2["id"] : `opencode-${i2}`;
|
|
53848
|
+
const prev = byId.get(id);
|
|
53849
|
+
byId.set(id, { text: texts.join("\n\n"), order: prev ? prev.order : order++ });
|
|
53850
|
+
}
|
|
53851
|
+
const turns = [...byId.entries()].sort((a, b) => a[1].order - b[1].order).map(([uuid3, v]) => ({ uuid: uuid3, text: v.text, hostHarness: "opencode" }));
|
|
53852
|
+
return { turns, parseFailures };
|
|
53853
|
+
}
|
|
53854
|
+
function parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure = false) {
|
|
53855
|
+
const lines = raw2.split(/\r?\n/);
|
|
53856
|
+
const byId = /* @__PURE__ */ new Map();
|
|
53857
|
+
let order = 0;
|
|
53858
|
+
let parseFailures = 0;
|
|
53859
|
+
const textOf = (content) => {
|
|
53860
|
+
if (typeof content === "string") return content;
|
|
53861
|
+
if (Array.isArray(content)) {
|
|
53862
|
+
const parts2 = [];
|
|
53863
|
+
for (const b of content) {
|
|
53864
|
+
if (b && typeof b["text"] === "string") parts2.push(b["text"]);
|
|
53865
|
+
}
|
|
53866
|
+
return parts2.length > 0 ? parts2.join("\n\n") : null;
|
|
53867
|
+
}
|
|
53868
|
+
return null;
|
|
53869
|
+
};
|
|
53870
|
+
const fold = (m, fallbackId) => {
|
|
53871
|
+
if (m["type"] !== "gemini") return;
|
|
53872
|
+
const text = textOf(m["content"]);
|
|
53873
|
+
if (text === null || text.length === 0) return;
|
|
53874
|
+
const id = typeof m["id"] === "string" ? m["id"] : fallbackId;
|
|
53875
|
+
const prev = byId.get(id);
|
|
53876
|
+
byId.set(id, { text, order: prev ? prev.order : order++ });
|
|
53877
|
+
};
|
|
53878
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
53879
|
+
const line = lines[i2];
|
|
53880
|
+
if (!line) continue;
|
|
53881
|
+
let obj;
|
|
53882
|
+
try {
|
|
53883
|
+
obj = JSON.parse(line);
|
|
53884
|
+
} catch {
|
|
53885
|
+
if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
|
|
53886
|
+
continue;
|
|
53887
|
+
}
|
|
53888
|
+
if (!obj || typeof obj !== "object") continue;
|
|
53889
|
+
const set2 = obj["$set"];
|
|
53890
|
+
if (set2 && typeof set2 === "object") {
|
|
53891
|
+
const msgs = set2["messages"];
|
|
53892
|
+
if (Array.isArray(msgs)) {
|
|
53893
|
+
for (let j = 0; j < msgs.length; j++) {
|
|
53894
|
+
const m = msgs[j];
|
|
53895
|
+
if (m && typeof m === "object") fold(m, `gemini-${i2}-${j}`);
|
|
53896
|
+
}
|
|
53897
|
+
}
|
|
53898
|
+
continue;
|
|
53899
|
+
}
|
|
53900
|
+
if (typeof obj["type"] === "string") fold(obj, `gemini-${i2}`);
|
|
53901
|
+
}
|
|
53902
|
+
const turns = [...byId.entries()].sort((a, b) => a[1].order - b[1].order).map(([uuid3, v]) => ({ uuid: uuid3, text: v.text, hostHarness: "gemini" }));
|
|
53903
|
+
return { turns, parseFailures };
|
|
53904
|
+
}
|
|
53905
|
+
function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false) {
|
|
53906
|
+
const turns = [];
|
|
53907
|
+
const lines = raw2.split(/\r?\n/);
|
|
53908
|
+
let parseFailures = 0;
|
|
53909
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
53910
|
+
const line = lines[i2];
|
|
53911
|
+
if (!line) continue;
|
|
53912
|
+
let obj;
|
|
53913
|
+
try {
|
|
53914
|
+
obj = JSON.parse(line);
|
|
53915
|
+
} catch {
|
|
53916
|
+
if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
|
|
53917
|
+
continue;
|
|
53918
|
+
}
|
|
53919
|
+
if (obj.type !== "response_item") continue;
|
|
53920
|
+
const payload = obj.payload;
|
|
53921
|
+
if (!payload || payload["type"] !== "message" || payload["role"] !== "assistant") continue;
|
|
53922
|
+
const content = payload["content"];
|
|
53923
|
+
if (!Array.isArray(content)) continue;
|
|
53924
|
+
const parts2 = [];
|
|
53925
|
+
for (const b of content) {
|
|
53926
|
+
if (b["type"] === "output_text" && typeof b["text"] === "string") parts2.push(b["text"]);
|
|
53927
|
+
}
|
|
53928
|
+
if (parts2.length === 0) continue;
|
|
53929
|
+
turns.push({
|
|
53930
|
+
uuid: typeof payload["id"] === "string" ? payload["id"] : `codex-${i2}`,
|
|
53931
|
+
text: parts2.join("\n\n"),
|
|
53932
|
+
hostHarness: "codex"
|
|
53933
|
+
});
|
|
53934
|
+
}
|
|
53935
|
+
return { turns, parseFailures };
|
|
53936
|
+
}
|
|
53937
|
+
function parseClaudeAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false) {
|
|
53001
53938
|
if (!raw2) return { turns: [], parseFailures: 0 };
|
|
53002
53939
|
const turns = [];
|
|
53003
53940
|
const lines = raw2.split(/\r?\n/);
|
|
@@ -53051,6 +53988,7 @@ function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure =
|
|
|
53051
53988
|
turns.push({
|
|
53052
53989
|
uuid: String(obj.uuid ?? i2),
|
|
53053
53990
|
text: parts2.join("\n\n"),
|
|
53991
|
+
hostHarness: "claude_code",
|
|
53054
53992
|
...lastFile ? { workingFile: lastFile } : {},
|
|
53055
53993
|
...provenance ? { workingFileProvenance: provenance } : {},
|
|
53056
53994
|
...editedFile && editedFileTurnSeq === turnSeq ? { editedFile } : {},
|
|
@@ -53118,6 +54056,237 @@ function subagentTranscripts(mainTranscriptPath, sessionId) {
|
|
|
53118
54056
|
refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
53119
54057
|
return refs;
|
|
53120
54058
|
}
|
|
54059
|
+
function codexSessionRoots(env2 = process.env, home = homedir4()) {
|
|
54060
|
+
const roots = /* @__PURE__ */ new Set();
|
|
54061
|
+
const explicit = env2["ERRATA_CODEX_SESSIONS_DIRS"];
|
|
54062
|
+
if (explicit) {
|
|
54063
|
+
for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
|
|
54064
|
+
}
|
|
54065
|
+
if (env2["CODEX_HOME"]) roots.add(join16(env2["CODEX_HOME"], "sessions"));
|
|
54066
|
+
roots.add(join16(home, ".codex", "sessions"));
|
|
54067
|
+
return [...roots];
|
|
54068
|
+
}
|
|
54069
|
+
function readCodexRolloutCwd(path2) {
|
|
54070
|
+
let fd;
|
|
54071
|
+
try {
|
|
54072
|
+
fd = openSync(path2, "r");
|
|
54073
|
+
const CAP = 1e6;
|
|
54074
|
+
const chunk = 65536;
|
|
54075
|
+
let acc = Buffer.alloc(0);
|
|
54076
|
+
let pos = 0;
|
|
54077
|
+
let nl = -1;
|
|
54078
|
+
while (acc.length < CAP) {
|
|
54079
|
+
const buf = Buffer.allocUnsafe(chunk);
|
|
54080
|
+
const n = readSync(fd, buf, 0, chunk, pos);
|
|
54081
|
+
if (n <= 0) break;
|
|
54082
|
+
acc = acc.length === 0 ? buf.subarray(0, n) : Buffer.concat([acc, buf.subarray(0, n)]);
|
|
54083
|
+
pos += n;
|
|
54084
|
+
nl = acc.indexOf(10);
|
|
54085
|
+
if (nl >= 0) break;
|
|
54086
|
+
}
|
|
54087
|
+
const firstLine = acc.toString("utf8", 0, nl >= 0 ? nl : acc.length);
|
|
54088
|
+
try {
|
|
54089
|
+
const o = JSON.parse(firstLine);
|
|
54090
|
+
const meta3 = o?.type === "session_meta" ? o.payload ?? o : o.payload ?? o;
|
|
54091
|
+
const cwd = meta3?.cwd ?? o?.cwd;
|
|
54092
|
+
if (typeof cwd === "string") return cwd;
|
|
54093
|
+
} catch {
|
|
54094
|
+
}
|
|
54095
|
+
const m = firstLine.match(/"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
54096
|
+
return m ? JSON.parse(`"${m[1]}"`) : null;
|
|
54097
|
+
} catch {
|
|
54098
|
+
return null;
|
|
54099
|
+
} finally {
|
|
54100
|
+
if (fd !== void 0) try {
|
|
54101
|
+
closeSync(fd);
|
|
54102
|
+
} catch {
|
|
54103
|
+
}
|
|
54104
|
+
}
|
|
54105
|
+
}
|
|
54106
|
+
function codexRolloutsForCwd(cwd, opts = {}) {
|
|
54107
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
54108
|
+
const limit = opts.limit ?? 25;
|
|
54109
|
+
const out2 = [];
|
|
54110
|
+
for (const root of codexSessionRoots(opts.env, opts.home)) {
|
|
54111
|
+
if (!existsSync13(root)) continue;
|
|
54112
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
54113
|
+
while (stack.length > 0) {
|
|
54114
|
+
const { dir, depth } = stack.pop();
|
|
54115
|
+
let names;
|
|
54116
|
+
try {
|
|
54117
|
+
names = readdirSync6(dir);
|
|
54118
|
+
} catch {
|
|
54119
|
+
continue;
|
|
54120
|
+
}
|
|
54121
|
+
for (const name2 of names) {
|
|
54122
|
+
const full = join16(dir, name2);
|
|
54123
|
+
let st;
|
|
54124
|
+
try {
|
|
54125
|
+
st = statSync4(full);
|
|
54126
|
+
} catch {
|
|
54127
|
+
continue;
|
|
54128
|
+
}
|
|
54129
|
+
if (st.isDirectory()) {
|
|
54130
|
+
if (depth < 3) stack.push({ dir: full, depth: depth + 1 });
|
|
54131
|
+
continue;
|
|
54132
|
+
}
|
|
54133
|
+
if (depth < 3) continue;
|
|
54134
|
+
if (!name2.startsWith("rollout-") || !name2.endsWith(".jsonl")) continue;
|
|
54135
|
+
if (st.mtimeMs < sinceMs) continue;
|
|
54136
|
+
if (readCodexRolloutCwd(full) !== cwd) continue;
|
|
54137
|
+
out2.push({ path: full, sessionId: `codex:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
|
|
54138
|
+
}
|
|
54139
|
+
}
|
|
54140
|
+
}
|
|
54141
|
+
out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
54142
|
+
return out2.slice(0, limit);
|
|
54143
|
+
}
|
|
54144
|
+
function geminiSessionRoots(env2 = process.env, home = homedir4()) {
|
|
54145
|
+
const roots = /* @__PURE__ */ new Set();
|
|
54146
|
+
const explicit = env2["ERRATA_GEMINI_SESSIONS_DIRS"];
|
|
54147
|
+
if (explicit) {
|
|
54148
|
+
for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
|
|
54149
|
+
}
|
|
54150
|
+
if (env2["GEMINI_CLI_HOME"]) roots.add(join16(env2["GEMINI_CLI_HOME"], ".gemini", "tmp"));
|
|
54151
|
+
roots.add(join16(home, ".gemini", "tmp"));
|
|
54152
|
+
return [...roots];
|
|
54153
|
+
}
|
|
54154
|
+
function geminiProjectHash(cwd) {
|
|
54155
|
+
return createHash13("sha256").update(cwd).digest("hex");
|
|
54156
|
+
}
|
|
54157
|
+
function geminiHashesFor(cwd) {
|
|
54158
|
+
const variants = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
|
|
54159
|
+
try {
|
|
54160
|
+
variants.add(realpathSync(cwd));
|
|
54161
|
+
} catch {
|
|
54162
|
+
}
|
|
54163
|
+
return new Set([...variants].map(geminiProjectHash));
|
|
54164
|
+
}
|
|
54165
|
+
function readGeminiTranscriptProjectHash(path2) {
|
|
54166
|
+
let fd;
|
|
54167
|
+
try {
|
|
54168
|
+
fd = openSync(path2, "r");
|
|
54169
|
+
const CAP = 65536;
|
|
54170
|
+
const buf = Buffer.allocUnsafe(CAP);
|
|
54171
|
+
const n = readSync(fd, buf, 0, CAP, 0);
|
|
54172
|
+
const nl = buf.indexOf(10);
|
|
54173
|
+
const firstLine = buf.toString("utf8", 0, nl >= 0 && nl < n ? nl : n);
|
|
54174
|
+
const o = JSON.parse(firstLine);
|
|
54175
|
+
return typeof o?.projectHash === "string" ? o.projectHash : null;
|
|
54176
|
+
} catch {
|
|
54177
|
+
return null;
|
|
54178
|
+
} finally {
|
|
54179
|
+
if (fd !== void 0) try {
|
|
54180
|
+
closeSync(fd);
|
|
54181
|
+
} catch {
|
|
54182
|
+
}
|
|
54183
|
+
}
|
|
54184
|
+
}
|
|
54185
|
+
function geminiTranscriptsForCwd(cwd, opts = {}) {
|
|
54186
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
54187
|
+
const limit = opts.limit ?? 25;
|
|
54188
|
+
const hashes = geminiHashesFor(cwd);
|
|
54189
|
+
const out2 = [];
|
|
54190
|
+
for (const root of geminiSessionRoots(opts.env, opts.home)) {
|
|
54191
|
+
if (!existsSync13(root)) continue;
|
|
54192
|
+
let slugs;
|
|
54193
|
+
try {
|
|
54194
|
+
slugs = readdirSync6(root);
|
|
54195
|
+
} catch {
|
|
54196
|
+
continue;
|
|
54197
|
+
}
|
|
54198
|
+
for (const slug2 of slugs) {
|
|
54199
|
+
const chats = join16(root, slug2, "chats");
|
|
54200
|
+
let names;
|
|
54201
|
+
try {
|
|
54202
|
+
names = readdirSync6(chats);
|
|
54203
|
+
} catch {
|
|
54204
|
+
continue;
|
|
54205
|
+
}
|
|
54206
|
+
for (const name2 of names) {
|
|
54207
|
+
if (!name2.startsWith("session-") || !name2.endsWith(".jsonl")) continue;
|
|
54208
|
+
const full = join16(chats, name2);
|
|
54209
|
+
let st;
|
|
54210
|
+
try {
|
|
54211
|
+
st = statSync4(full);
|
|
54212
|
+
} catch {
|
|
54213
|
+
continue;
|
|
54214
|
+
}
|
|
54215
|
+
if (!st.isFile() || st.mtimeMs < sinceMs) continue;
|
|
54216
|
+
const h = readGeminiTranscriptProjectHash(full);
|
|
54217
|
+
if (!h || !hashes.has(h)) continue;
|
|
54218
|
+
out2.push({ path: full, sessionId: `gemini:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
|
|
54219
|
+
}
|
|
54220
|
+
}
|
|
54221
|
+
}
|
|
54222
|
+
out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
54223
|
+
return out2.slice(0, limit);
|
|
54224
|
+
}
|
|
54225
|
+
function opencodeSessionRoots(env2 = process.env, home = homedir4()) {
|
|
54226
|
+
const roots = /* @__PURE__ */ new Set();
|
|
54227
|
+
const explicit = env2["ERRATA_OPENCODE_SESSIONS_DIRS"];
|
|
54228
|
+
if (explicit) {
|
|
54229
|
+
for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
|
|
54230
|
+
}
|
|
54231
|
+
if (env2["XDG_DATA_HOME"]) roots.add(join16(env2["XDG_DATA_HOME"], "opencode", "errata"));
|
|
54232
|
+
roots.add(join16(home, ".local", "share", "opencode", "errata"));
|
|
54233
|
+
return [...roots];
|
|
54234
|
+
}
|
|
54235
|
+
function readOpencodeTranscriptDirectory(path2) {
|
|
54236
|
+
let fd;
|
|
54237
|
+
try {
|
|
54238
|
+
fd = openSync(path2, "r");
|
|
54239
|
+
const buf = Buffer.allocUnsafe(8192);
|
|
54240
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
54241
|
+
const nl = buf.indexOf(10);
|
|
54242
|
+
const first = buf.toString("utf8", 0, nl >= 0 && nl < n ? nl : n);
|
|
54243
|
+
const o = JSON.parse(first);
|
|
54244
|
+
if (o?.type !== OPENCODE_TRANSCRIPT_HEADER_TYPE) return null;
|
|
54245
|
+
return typeof o.directory === "string" ? o.directory : null;
|
|
54246
|
+
} catch {
|
|
54247
|
+
return null;
|
|
54248
|
+
} finally {
|
|
54249
|
+
if (fd !== void 0) try {
|
|
54250
|
+
closeSync(fd);
|
|
54251
|
+
} catch {
|
|
54252
|
+
}
|
|
54253
|
+
}
|
|
54254
|
+
}
|
|
54255
|
+
function opencodeTranscriptsForCwd(cwd, opts = {}) {
|
|
54256
|
+
const sinceMs = opts.sinceMs ?? 0;
|
|
54257
|
+
const limit = opts.limit ?? 25;
|
|
54258
|
+
const want = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
|
|
54259
|
+
try {
|
|
54260
|
+
want.add(realpathSync(cwd));
|
|
54261
|
+
} catch {
|
|
54262
|
+
}
|
|
54263
|
+
const out2 = [];
|
|
54264
|
+
for (const root of opencodeSessionRoots(opts.env, opts.home)) {
|
|
54265
|
+
if (!existsSync13(root)) continue;
|
|
54266
|
+
let names;
|
|
54267
|
+
try {
|
|
54268
|
+
names = readdirSync6(root);
|
|
54269
|
+
} catch {
|
|
54270
|
+
continue;
|
|
54271
|
+
}
|
|
54272
|
+
for (const name2 of names) {
|
|
54273
|
+
if (!name2.endsWith(".jsonl")) continue;
|
|
54274
|
+
const full = join16(root, name2);
|
|
54275
|
+
let st;
|
|
54276
|
+
try {
|
|
54277
|
+
st = statSync4(full);
|
|
54278
|
+
} catch {
|
|
54279
|
+
continue;
|
|
54280
|
+
}
|
|
54281
|
+
if (!st.isFile() || st.mtimeMs < sinceMs) continue;
|
|
54282
|
+
const dir = readOpencodeTranscriptDirectory(full);
|
|
54283
|
+
if (!dir || !(want.has(dir) || want.has(dir.replace(/\/+$/, "")))) continue;
|
|
54284
|
+
out2.push({ path: full, sessionId: `opencode:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
|
|
54285
|
+
}
|
|
54286
|
+
}
|
|
54287
|
+
out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
54288
|
+
return out2.slice(0, limit);
|
|
54289
|
+
}
|
|
53121
54290
|
|
|
53122
54291
|
// src/prior-tags.ts
|
|
53123
54292
|
init_src();
|
|
@@ -53596,6 +54765,44 @@ function typePriorEdge(sourceLabel, targetLabel2, sentence = "") {
|
|
|
53596
54765
|
}
|
|
53597
54766
|
return "RELATES_TO";
|
|
53598
54767
|
}
|
|
54768
|
+
function refuteReason(statement) {
|
|
54769
|
+
const s = (statement ?? "").trim();
|
|
54770
|
+
return s.length > 0 ? s.slice(0, 240) : void 0;
|
|
54771
|
+
}
|
|
54772
|
+
function wireWitnessItems(items) {
|
|
54773
|
+
return items.map(({ nodeId, witnessKey }) => ({ nodeId, witnessKey }));
|
|
54774
|
+
}
|
|
54775
|
+
var REFUTE_REASONS_CAP = 5;
|
|
54776
|
+
var REFUTE_WITNESS_KEYS_CAP = 16;
|
|
54777
|
+
function applyLocalRefutations(store, refutes, ts) {
|
|
54778
|
+
const out2 = { stamped: 0, duplicate: 0, unknownNode: 0, stampedIds: [] };
|
|
54779
|
+
for (const r of refutes) {
|
|
54780
|
+
const node2 = store.getNode(r.nodeId);
|
|
54781
|
+
if (!node2) {
|
|
54782
|
+
out2.unknownNode++;
|
|
54783
|
+
continue;
|
|
54784
|
+
}
|
|
54785
|
+
const keys = node2.attrs["refuteWitnessKeys"] ?? [];
|
|
54786
|
+
if (keys.includes(r.witnessKey)) {
|
|
54787
|
+
out2.duplicate++;
|
|
54788
|
+
continue;
|
|
54789
|
+
}
|
|
54790
|
+
const reasons = node2.attrs["refuteReasons"] ?? [];
|
|
54791
|
+
store.updateNode(r.nodeId, {
|
|
54792
|
+
attrs: {
|
|
54793
|
+
...node2.attrs,
|
|
54794
|
+
refutedAt: ts,
|
|
54795
|
+
refutations: (Number(node2.attrs["refutations"]) || 0) + 1,
|
|
54796
|
+
...r.reason ? { refuteReasons: [r.reason, ...reasons].slice(0, REFUTE_REASONS_CAP) } : {},
|
|
54797
|
+
refuteWitnessKeys: [...keys, r.witnessKey].slice(-REFUTE_WITNESS_KEYS_CAP)
|
|
54798
|
+
},
|
|
54799
|
+
lastUpdatedAt: ts
|
|
54800
|
+
});
|
|
54801
|
+
out2.stamped++;
|
|
54802
|
+
out2.stampedIds.push(r.nodeId);
|
|
54803
|
+
}
|
|
54804
|
+
return out2;
|
|
54805
|
+
}
|
|
53599
54806
|
var PRIOR_TAG_INSTRUCTION = buildAgentInstruction();
|
|
53600
54807
|
function isEdgeElicitationEnabled() {
|
|
53601
54808
|
return (process.env["EDGE_ELICITATION_ENABLED"] ?? "true").toLowerCase() !== "false";
|
|
@@ -53634,7 +54841,52 @@ function readPrimingHandles(path2) {
|
|
|
53634
54841
|
function resolveHandle(store, handle2, handleMap) {
|
|
53635
54842
|
return handleMap[handle2]?.id ?? (store.getNode(handle2) ? handle2 : void 0);
|
|
53636
54843
|
}
|
|
53637
|
-
|
|
54844
|
+
var CITE_ANCHOR_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause", "Pattern", "Claim", "Technique", "AntiPattern"]);
|
|
54845
|
+
var CITE_ANCHOR_PROMOTE_SESSIONS = 2;
|
|
54846
|
+
var CITE_ANCHOR_MAX_FILES = 4;
|
|
54847
|
+
var CITE_ANCHOR_MAX_SESSIONS = 6;
|
|
54848
|
+
function accrueCiteAnchor(store, targetId, touched, sessionId, ts) {
|
|
54849
|
+
let fileId;
|
|
54850
|
+
for (const tid of touched) {
|
|
54851
|
+
if (store.getNode(tid)?.label === "File") {
|
|
54852
|
+
fileId = tid;
|
|
54853
|
+
break;
|
|
54854
|
+
}
|
|
54855
|
+
}
|
|
54856
|
+
if (!fileId) return { accrued: false, promoted: false };
|
|
54857
|
+
const node2 = store.getNode(targetId);
|
|
54858
|
+
if (!node2) return { accrued: false, promoted: false };
|
|
54859
|
+
const hints = { ...node2.attrs["citeAnchorHints"] ?? {} };
|
|
54860
|
+
const entry = hints[fileId] ?? [];
|
|
54861
|
+
if (entry.includes(sessionId)) return { accrued: false, promoted: false };
|
|
54862
|
+
if (hints[fileId] === void 0 && Object.keys(hints).length >= CITE_ANCHOR_MAX_FILES) {
|
|
54863
|
+
return { accrued: false, promoted: false };
|
|
54864
|
+
}
|
|
54865
|
+
const grown = [...entry, sessionId].slice(-CITE_ANCHOR_MAX_SESSIONS);
|
|
54866
|
+
let promoted = false;
|
|
54867
|
+
if (grown.length >= CITE_ANCHOR_PROMOTE_SESSIONS) {
|
|
54868
|
+
store.mergeEdge({
|
|
54869
|
+
id: `edge_${digest({ from: targetId, type: "ANCHORED_AT", to: fileId })}`.slice(0, 24),
|
|
54870
|
+
from: targetId,
|
|
54871
|
+
to: fileId,
|
|
54872
|
+
type: "ANCHORED_AT",
|
|
54873
|
+
confidence: 0.3,
|
|
54874
|
+
extractionSource: "agent-observed",
|
|
54875
|
+
createdAt: ts,
|
|
54876
|
+
lastSeenAt: ts,
|
|
54877
|
+
navSuccesses: 0,
|
|
54878
|
+
navFailures: 0,
|
|
54879
|
+
attrs: { provisional: true, captureTime: true, anchorProvenance: "cite-confirmed", citeSessions: grown }
|
|
54880
|
+
});
|
|
54881
|
+
delete hints[fileId];
|
|
54882
|
+
promoted = true;
|
|
54883
|
+
} else {
|
|
54884
|
+
hints[fileId] = grown;
|
|
54885
|
+
}
|
|
54886
|
+
store.updateNode(targetId, { attrs: { ...node2.attrs, citeAnchorHints: hints }, lastUpdatedAt: ts });
|
|
54887
|
+
return { accrued: true, promoted };
|
|
54888
|
+
}
|
|
54889
|
+
function mintPriorEdge(store, source, target, sentence, touched, ts, sessionId) {
|
|
53638
54890
|
if (target.id === source.id) return false;
|
|
53639
54891
|
const type = typePriorEdge(source.label, target.label, sentence);
|
|
53640
54892
|
const anchorIds = new Set(
|
|
@@ -53678,13 +54930,24 @@ function mintPriorEdge(store, source, target, sentence, touched, ts) {
|
|
|
53678
54930
|
} catch {
|
|
53679
54931
|
}
|
|
53680
54932
|
}
|
|
53681
|
-
|
|
54933
|
+
let citeAnchorAccrued = false;
|
|
54934
|
+
let citeAnchorPromoted = false;
|
|
54935
|
+
if (!corroborated && sessionId && CITE_ANCHOR_LABELS.has(target.label)) {
|
|
54936
|
+
try {
|
|
54937
|
+
const acc = accrueCiteAnchor(store, target.id, touched, sessionId, ts);
|
|
54938
|
+
citeAnchorAccrued = acc.accrued;
|
|
54939
|
+
citeAnchorPromoted = acc.promoted;
|
|
54940
|
+
} catch (err2) {
|
|
54941
|
+
console.warn("[errata] cite-anchor accrual failed (citation kept):", err2 instanceof Error ? err2.message : err2);
|
|
54942
|
+
}
|
|
54943
|
+
}
|
|
54944
|
+
return { corroborated, citeAnchorAccrued, citeAnchorPromoted };
|
|
53682
54945
|
}
|
|
53683
54946
|
function harvestInlineTags(store, text, opts) {
|
|
53684
54947
|
const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
|
|
53685
54948
|
const mintPriors = opts.mintPriors ?? true;
|
|
53686
54949
|
const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
|
|
53687
|
-
const plan = { priorEdges: 0, corroboratedEdges: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
|
|
54950
|
+
const plan = { priorEdges: 0, corroboratedEdges: 0, citeAnchorsAccrued: 0, citeAnchorsPromoted: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
|
|
53688
54951
|
const parsed = parseInlineTagsWithDispositions(text);
|
|
53689
54952
|
const tags = parsed.tags;
|
|
53690
54953
|
plan.dispositions = parsed.dispositions;
|
|
@@ -53856,7 +55119,8 @@ function harvestInlineTags(store, text, opts) {
|
|
|
53856
55119
|
if (nodeId) {
|
|
53857
55120
|
const witnessKey = `refute:${nodeId}:${digest({ s: tag.statement ?? "" })}`.slice(0, 72);
|
|
53858
55121
|
if (!plan.refutes.some((r) => r.witnessKey === witnessKey)) {
|
|
53859
|
-
|
|
55122
|
+
const reason = refuteReason(tag.statement);
|
|
55123
|
+
plan.refutes.push({ nodeId, witnessKey, ...reason ? { reason } : {} });
|
|
53860
55124
|
}
|
|
53861
55125
|
}
|
|
53862
55126
|
}
|
|
@@ -53891,7 +55155,8 @@ function harvestInlineTags(store, text, opts) {
|
|
|
53891
55155
|
} else {
|
|
53892
55156
|
const witnessKey = `refute:${targetId}:${digest({ s: tag.statement ?? "" })}`.slice(0, 72);
|
|
53893
55157
|
if (!plan.refutes.some((r) => r.witnessKey === witnessKey)) {
|
|
53894
|
-
|
|
55158
|
+
const reason = refuteReason(tag.statement);
|
|
55159
|
+
plan.refutes.push({ nodeId: targetId, witnessKey, ...reason ? { reason } : {} });
|
|
53895
55160
|
}
|
|
53896
55161
|
}
|
|
53897
55162
|
}
|
|
@@ -53910,10 +55175,12 @@ function harvestInlineTags(store, text, opts) {
|
|
|
53910
55175
|
}
|
|
53911
55176
|
}
|
|
53912
55177
|
if (mintPriors && source && target) {
|
|
53913
|
-
const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
|
|
55178
|
+
const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
|
|
53914
55179
|
if (m) {
|
|
53915
55180
|
plan.priorEdges++;
|
|
53916
55181
|
if (m.corroborated) plan.corroboratedEdges++;
|
|
55182
|
+
if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
|
|
55183
|
+
if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
|
|
53917
55184
|
}
|
|
53918
55185
|
} else if (target) {
|
|
53919
55186
|
if (!mintPriors) plan.priorEdgesSuppressed.flagOff++;
|
|
@@ -53923,10 +55190,12 @@ function harvestInlineTags(store, text, opts) {
|
|
|
53923
55190
|
const targetId = resolveCited(tag.kind, tag.handle);
|
|
53924
55191
|
const target = targetId ? store.getNode(targetId) : null;
|
|
53925
55192
|
if (target) {
|
|
53926
|
-
const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
|
|
55193
|
+
const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
|
|
53927
55194
|
if (m) {
|
|
53928
55195
|
plan.priorEdges++;
|
|
53929
55196
|
if (m.corroborated) plan.corroboratedEdges++;
|
|
55197
|
+
if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
|
|
55198
|
+
if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
|
|
53930
55199
|
}
|
|
53931
55200
|
}
|
|
53932
55201
|
} else if (tag.handle) {
|
|
@@ -55751,7 +57020,7 @@ init_paths();
|
|
|
55751
57020
|
init_src2();
|
|
55752
57021
|
init_paths();
|
|
55753
57022
|
import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "node:fs";
|
|
55754
|
-
import { createHash as
|
|
57023
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
55755
57024
|
import { join as join25 } from "node:path";
|
|
55756
57025
|
|
|
55757
57026
|
// src/git-remote.ts
|
|
@@ -55808,11 +57077,11 @@ function detectRepoLocator(root, remote) {
|
|
|
55808
57077
|
|
|
55809
57078
|
// src/profile.ts
|
|
55810
57079
|
function workspaceId(root) {
|
|
55811
|
-
return "wp_" +
|
|
57080
|
+
return "wp_" + createHash14("sha256").update(root).digest("hex").slice(0, 12);
|
|
55812
57081
|
}
|
|
55813
57082
|
function sessionOriginKey(sessionId) {
|
|
55814
57083
|
if (!sessionId) return void 0;
|
|
55815
|
-
return "ws_" +
|
|
57084
|
+
return "ws_" + createHash14("sha256").update(sessionId).digest("hex").slice(0, 12);
|
|
55816
57085
|
}
|
|
55817
57086
|
function refreshRepoLocator(root, profile) {
|
|
55818
57087
|
const detected = detectRepoLocator(root, profile.repoRemote);
|
|
@@ -56153,7 +57422,8 @@ var TITLES = {
|
|
|
56153
57422
|
"hotspot-problem": "Errata \xB7 high-impact problem",
|
|
56154
57423
|
"index-started": "Errata \xB7 indexing\u2026",
|
|
56155
57424
|
"index-completed": "Errata \xB7 index ready \u2713",
|
|
56156
|
-
"mechanism-stalled": "Errata \xB7 a mechanism is not working"
|
|
57425
|
+
"mechanism-stalled": "Errata \xB7 a mechanism is not working",
|
|
57426
|
+
"daemon-distress": "Errata \xB7 daemon health \u26A0"
|
|
56157
57427
|
};
|
|
56158
57428
|
var lastFired = /* @__PURE__ */ new Map();
|
|
56159
57429
|
var THROTTLE_MS = 3e4;
|
|
@@ -56169,6 +57439,14 @@ function notifyEvent(kind, body2, opts = {}) {
|
|
|
56169
57439
|
} catch {
|
|
56170
57440
|
}
|
|
56171
57441
|
}
|
|
57442
|
+
function notifyOpsEvent(kind, body2, opts = {}) {
|
|
57443
|
+
try {
|
|
57444
|
+
if (!loadConfig().opsNotices) return;
|
|
57445
|
+
} catch {
|
|
57446
|
+
return;
|
|
57447
|
+
}
|
|
57448
|
+
notifyEvent(kind, body2, opts);
|
|
57449
|
+
}
|
|
56172
57450
|
function notifyTick(delta) {
|
|
56173
57451
|
const { problemsResolved, reviewsTriggered } = delta;
|
|
56174
57452
|
if (problemsResolved > 0)
|
|
@@ -56258,11 +57536,30 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
|
|
|
56258
57536
|
}
|
|
56259
57537
|
|
|
56260
57538
|
// src/watch-census.ts
|
|
56261
|
-
var census = {
|
|
57539
|
+
var census = {
|
|
57540
|
+
dispatched: 0,
|
|
57541
|
+
emitted: 0,
|
|
57542
|
+
logged: 0,
|
|
57543
|
+
rejected: 0,
|
|
57544
|
+
since: Date.now(),
|
|
57545
|
+
breakerRecycles: 0,
|
|
57546
|
+
breakerQuarantined: [],
|
|
57547
|
+
topOffender: null
|
|
57548
|
+
};
|
|
57549
|
+
var QUARANTINE_LIST_CAP = 8;
|
|
56262
57550
|
var noteWatchDispatched = () => void census.dispatched++;
|
|
56263
57551
|
var noteWatchEmitted = () => void census.emitted++;
|
|
56264
57552
|
var noteWatchLogged = () => void census.logged++;
|
|
56265
57553
|
var noteWatchRejected = () => void census.rejected++;
|
|
57554
|
+
var noteBreakerRecycle = (_path) => void census.breakerRecycles++;
|
|
57555
|
+
var noteBreakerQuarantine = (path2) => {
|
|
57556
|
+
if (!census.breakerQuarantined.includes(path2) && census.breakerQuarantined.length < QUARANTINE_LIST_CAP) {
|
|
57557
|
+
census.breakerQuarantined.push(path2);
|
|
57558
|
+
}
|
|
57559
|
+
};
|
|
57560
|
+
var noteWindowOffender = (path2, count, windowMs) => {
|
|
57561
|
+
census.topOffender = { path: path2, count, windowMs, at: Date.now() };
|
|
57562
|
+
};
|
|
56266
57563
|
function watchCensus() {
|
|
56267
57564
|
return { ...census };
|
|
56268
57565
|
}
|
|
@@ -56270,9 +57567,14 @@ function watchCensusLine(c = watchCensus()) {
|
|
|
56270
57567
|
if (c.dispatched === 0 && c.emitted === 0) return null;
|
|
56271
57568
|
const mins = Math.max(1, Math.round((Date.now() - c.since) / 6e4));
|
|
56272
57569
|
const perMin = Math.round(c.dispatched / mins);
|
|
56273
|
-
|
|
56274
|
-
if (c.
|
|
56275
|
-
|
|
57570
|
+
let base = `${c.dispatched} OS event(s) \u2192 ${c.logged} logged (${perMin}/min, ${c.rejected} rejected here)`;
|
|
57571
|
+
if (c.breakerRecycles > 0 || c.breakerQuarantined.length > 0) {
|
|
57572
|
+
const q = c.breakerQuarantined.length > 0 ? `, ${c.breakerQuarantined.length} quarantined` : "";
|
|
57573
|
+
base += ` \xB7 breaker: ${c.breakerRecycles} recycled${q}`;
|
|
57574
|
+
}
|
|
57575
|
+
if (c.dispatched > 500 && c.dispatched / Math.max(c.logged, 1) > 1e4) {
|
|
57576
|
+
const top = c.topOffender ? ` \u2014 top: ${c.topOffender.path} (${c.topOffender.count}/${Math.round(c.topOffender.windowMs / 1e3)}s)` : "";
|
|
57577
|
+
return `${base} \u26A0 watcher is doing work with NO output \u2014 a watch target likely contains an ignored tree${top}`;
|
|
56276
57578
|
}
|
|
56277
57579
|
return base;
|
|
56278
57580
|
}
|
|
@@ -56314,6 +57616,11 @@ function createLivenessWatch(deps) {
|
|
|
56314
57616
|
watchEmitted: watch2.emitted,
|
|
56315
57617
|
watchLogged: watch2.logged,
|
|
56316
57618
|
watchRejected: watch2.rejected,
|
|
57619
|
+
// HZ-watch-breaker: a recycle count trending upward across hours is a
|
|
57620
|
+
// handle that keeps wedging — the restart-survivable trace the 8-16
|
|
57621
|
+
// phantom-rename storm never left.
|
|
57622
|
+
watchBreakerRecycles: watch2.breakerRecycles,
|
|
57623
|
+
watchBreakerQuarantined: watch2.breakerQuarantined.length,
|
|
56317
57624
|
// Concurrency and the longest single holder — the two readings that
|
|
56318
57625
|
// named the 6017s zombie pass. A pass legitimately running for hours
|
|
56319
57626
|
// and one wedged forever look identical without a trend.
|
|
@@ -56338,14 +57645,115 @@ function createLivenessWatch(deps) {
|
|
|
56338
57645
|
};
|
|
56339
57646
|
}
|
|
56340
57647
|
|
|
57648
|
+
// src/watch-breaker.ts
|
|
57649
|
+
function numEnv3(key, fallback) {
|
|
57650
|
+
const n = Number(process.env[key]);
|
|
57651
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
57652
|
+
}
|
|
57653
|
+
var BREAKER_WINDOW_MS = numEnv3("ERRATA_WATCH_BREAKER_WINDOW_MS", 1e4);
|
|
57654
|
+
var BREAKER_TRIP_SELF_EVENTS = numEnv3("ERRATA_WATCH_BREAKER_TRIP", 1e3);
|
|
57655
|
+
var BREAKER_MAX_RECYCLES = numEnv3("ERRATA_WATCH_BREAKER_RECYCLES", 2);
|
|
57656
|
+
var BREAKER_REWEDGE_TTL_MS = numEnv3("ERRATA_WATCH_BREAKER_TTL_MS", 6e5);
|
|
57657
|
+
var BREAKER_READD_DELAY_MS = numEnv3("ERRATA_WATCH_BREAKER_READD_MS", 250);
|
|
57658
|
+
var OFFENDER_FLOOR = 100;
|
|
57659
|
+
function normalizeWatchPath(p) {
|
|
57660
|
+
let s = p.startsWith("\\\\?\\") ? p.slice(4) : p;
|
|
57661
|
+
s = s.replace(/\\/g, "/");
|
|
57662
|
+
if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
|
|
57663
|
+
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
57664
|
+
}
|
|
57665
|
+
function createWatchBreaker(deps) {
|
|
57666
|
+
const now = deps.now ?? Date.now;
|
|
57667
|
+
const say = deps.onAction ?? (() => {
|
|
57668
|
+
});
|
|
57669
|
+
const windows2 = /* @__PURE__ */ new Map();
|
|
57670
|
+
let windowStart = now();
|
|
57671
|
+
const recycleHistory = /* @__PURE__ */ new Map();
|
|
57672
|
+
const quarantined = /* @__PURE__ */ new Set();
|
|
57673
|
+
const pendingReadds = /* @__PURE__ */ new Set();
|
|
57674
|
+
const rotate = (t) => {
|
|
57675
|
+
let top = null;
|
|
57676
|
+
for (const [path2, w] of windows2) {
|
|
57677
|
+
if (w.dispatched >= OFFENDER_FLOOR && (!top || w.dispatched > top.count)) {
|
|
57678
|
+
top = { path: path2, count: w.dispatched };
|
|
57679
|
+
}
|
|
57680
|
+
}
|
|
57681
|
+
if (top) noteWindowOffender(top.path, top.count, BREAKER_WINDOW_MS);
|
|
57682
|
+
windows2.clear();
|
|
57683
|
+
windowStart = t;
|
|
57684
|
+
};
|
|
57685
|
+
const trip = (path2, t) => {
|
|
57686
|
+
const history = (recycleHistory.get(path2) ?? []).filter(
|
|
57687
|
+
(ts) => t - ts < BREAKER_REWEDGE_TTL_MS
|
|
57688
|
+
);
|
|
57689
|
+
if (history.length >= BREAKER_MAX_RECYCLES) {
|
|
57690
|
+
quarantined.add(path2);
|
|
57691
|
+
recycleHistory.delete(path2);
|
|
57692
|
+
try {
|
|
57693
|
+
deps.unwatch(path2);
|
|
57694
|
+
} catch {
|
|
57695
|
+
}
|
|
57696
|
+
noteBreakerQuarantine(path2);
|
|
57697
|
+
say(
|
|
57698
|
+
`QUARANTINED ${path2} \u2014 re-wedged after ${history.length} recycle(s); unwatched for this session (reconcile covers its edits)`
|
|
57699
|
+
);
|
|
57700
|
+
return;
|
|
57701
|
+
}
|
|
57702
|
+
history.push(t);
|
|
57703
|
+
recycleHistory.set(path2, history);
|
|
57704
|
+
try {
|
|
57705
|
+
deps.unwatch(path2);
|
|
57706
|
+
} catch {
|
|
57707
|
+
}
|
|
57708
|
+
const timer = setTimeout(() => {
|
|
57709
|
+
pendingReadds.delete(timer);
|
|
57710
|
+
try {
|
|
57711
|
+
deps.readd(path2);
|
|
57712
|
+
} catch {
|
|
57713
|
+
}
|
|
57714
|
+
}, BREAKER_READD_DELAY_MS);
|
|
57715
|
+
timer.unref?.();
|
|
57716
|
+
pendingReadds.add(timer);
|
|
57717
|
+
noteBreakerRecycle(path2);
|
|
57718
|
+
say(
|
|
57719
|
+
`recycled ${path2} \u2014 ${BREAKER_TRIP_SELF_EVENTS}+ phantom self-events in ${Math.round(BREAKER_WINDOW_MS / 1e3)}s; handle closed + re-created`
|
|
57720
|
+
);
|
|
57721
|
+
};
|
|
57722
|
+
return {
|
|
57723
|
+
onRaw(event, evPath, watchedPath) {
|
|
57724
|
+
if (typeof watchedPath !== "string" || watchedPath.length === 0) return;
|
|
57725
|
+
const t = now();
|
|
57726
|
+
if (t - windowStart >= BREAKER_WINDOW_MS) rotate(t);
|
|
57727
|
+
let w = windows2.get(watchedPath);
|
|
57728
|
+
if (!w) {
|
|
57729
|
+
w = { dispatched: 0, self: 0, tripped: false };
|
|
57730
|
+
windows2.set(watchedPath, w);
|
|
57731
|
+
}
|
|
57732
|
+
w.dispatched++;
|
|
57733
|
+
const self = evPath == null || evPath === "" || typeof evPath === "string" && normalizeWatchPath(evPath) === normalizeWatchPath(watchedPath);
|
|
57734
|
+
if (!self) return;
|
|
57735
|
+
w.self++;
|
|
57736
|
+
if (!w.tripped && !quarantined.has(watchedPath) && w.self >= BREAKER_TRIP_SELF_EVENTS) {
|
|
57737
|
+
w.tripped = true;
|
|
57738
|
+
trip(watchedPath, t);
|
|
57739
|
+
}
|
|
57740
|
+
},
|
|
57741
|
+
stop() {
|
|
57742
|
+
for (const timer of pendingReadds) clearTimeout(timer);
|
|
57743
|
+
pendingReadds.clear();
|
|
57744
|
+
}
|
|
57745
|
+
};
|
|
57746
|
+
}
|
|
57747
|
+
|
|
56341
57748
|
// src/engine.ts
|
|
56342
|
-
var DAEMON_VERSION = true ? "2.0.2
|
|
57749
|
+
var DAEMON_VERSION = true ? "2.0.2" : "2.0.0-alpha.0";
|
|
56343
57750
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
56344
57751
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
56345
57752
|
var GIT_OP_MUTE_MS = 4e3;
|
|
56346
57753
|
var REINDEX_DEBOUNCE_MS = 200;
|
|
56347
57754
|
var IDENTITY_AUDIT_MAX_BYTES = 16 * 1024 * 1024;
|
|
56348
57755
|
var TURN_REPLAY_LOOKBACK_MS = 7 * 24 * 60 * 6e4;
|
|
57756
|
+
var CODEX_SWEEP_LOOKBACK_MS = 10 * 6e4;
|
|
56349
57757
|
function appendIdentityAudit(path2, record2, line) {
|
|
56350
57758
|
if (!record2.accepted && record2.score <= 0) return;
|
|
56351
57759
|
try {
|
|
@@ -56470,6 +57878,7 @@ function createWorkspaceEngine(opts) {
|
|
|
56470
57878
|
const cloud = opts.cloud;
|
|
56471
57879
|
const telemetry = new TelemetryRecorder(profile.id, DAEMON_VERSION);
|
|
56472
57880
|
let watcher = null;
|
|
57881
|
+
let watchBreaker = null;
|
|
56473
57882
|
let lastActivityTs = Date.now();
|
|
56474
57883
|
let stopGit = null;
|
|
56475
57884
|
let flushTimer = null;
|
|
@@ -56482,7 +57891,23 @@ function createWorkspaceEngine(opts) {
|
|
|
56482
57891
|
ignoreInitial: true,
|
|
56483
57892
|
persistent: true
|
|
56484
57893
|
});
|
|
56485
|
-
|
|
57894
|
+
watchBreaker = createWatchBreaker({
|
|
57895
|
+
unwatch: (p) => void watcher?.unwatch(p),
|
|
57896
|
+
readd: (p) => void watcher?.add(p),
|
|
57897
|
+
// Toast AND log: the autostarted daemon's stdout is a discarded stream
|
|
57898
|
+
// (the 8-01 unobservability finding), so console alone would make every
|
|
57899
|
+
// production breaker action invisible — the exact gauge-with-no-alarm
|
|
57900
|
+
// shape this mechanism exists to end.
|
|
57901
|
+
onAction: (line) => {
|
|
57902
|
+
console.warn(`[errata] watch-breaker: ${line}`);
|
|
57903
|
+
notifyOpsEvent("daemon-distress", `watch-breaker: ${line}`, { key: `breaker:${profile.id}` });
|
|
57904
|
+
}
|
|
57905
|
+
});
|
|
57906
|
+
watcher.on("raw", (event, evPath, opts2) => {
|
|
57907
|
+
noteWatchDispatched();
|
|
57908
|
+
const watchedPath = opts2 && typeof opts2 === "object" && "watchedPath" in opts2 ? opts2.watchedPath : void 0;
|
|
57909
|
+
watchBreaker?.onRaw(event, evPath, watchedPath);
|
|
57910
|
+
});
|
|
56486
57911
|
const onFs = (kind) => (path2) => {
|
|
56487
57912
|
noteWatchEmitted();
|
|
56488
57913
|
if (IGNORED_PATH.test(path2) || IGNORED_NOISE.test(path2)) {
|
|
@@ -56777,6 +58202,7 @@ function createWorkspaceEngine(opts) {
|
|
|
56777
58202
|
}) : null;
|
|
56778
58203
|
const doneRender = prebuilt ? null : markPass(`context-render-inline:${profile.name}`);
|
|
56779
58204
|
const pendingUpdate = opts.pendingUpdate?.() ?? null;
|
|
58205
|
+
const daemonDistress = opts.daemonDistress?.() ?? null;
|
|
56780
58206
|
const reviewItems = pickReviewItems();
|
|
56781
58207
|
const liveIntent = intents.mostRecent(Date.now());
|
|
56782
58208
|
maybeRefreshIntentPriors(liveIntent ?? null);
|
|
@@ -56794,6 +58220,7 @@ function createWorkspaceEngine(opts) {
|
|
|
56794
58220
|
...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
|
|
56795
58221
|
...prebuilt ? { snapshot: prebuilt } : {},
|
|
56796
58222
|
...pendingUpdate ? { pendingUpdate } : {},
|
|
58223
|
+
...daemonDistress ? { daemonDistress } : {},
|
|
56797
58224
|
...reviewItems.length ? { reviewItems } : {},
|
|
56798
58225
|
...liveIntent ? {
|
|
56799
58226
|
statedIntent: {
|
|
@@ -56845,6 +58272,7 @@ function createWorkspaceEngine(opts) {
|
|
|
56845
58272
|
contextDirty = false;
|
|
56846
58273
|
notifyDigest("recall");
|
|
56847
58274
|
};
|
|
58275
|
+
const normStackTokens = (xs) => (xs ?? []).map((t) => t.trim().toLowerCase().replace(/(?!^)[@\s].*$/, "")).filter(Boolean);
|
|
56848
58276
|
const maybeRefreshRemotePriors = () => {
|
|
56849
58277
|
if (opts.skipContextWriter || remoteInFlight) return;
|
|
56850
58278
|
const now = Date.now();
|
|
@@ -56855,10 +58283,13 @@ function createWorkspaceEngine(opts) {
|
|
|
56855
58283
|
remoteInFlight = true;
|
|
56856
58284
|
lastRemoteAt = now;
|
|
56857
58285
|
try {
|
|
56858
|
-
const norm =
|
|
58286
|
+
const norm = normStackTokens;
|
|
56859
58287
|
const seed = store.findNodesByLabel("Problem").sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 6).map((p) => p.id);
|
|
56860
58288
|
const q = {
|
|
56861
|
-
stack:
|
|
58289
|
+
// Languages ride the stack list: the cloud's context-affinity matcher
|
|
58290
|
+
// resolves each token against both the Package and Language
|
|
58291
|
+
// namespaces, and the profile keeps them in separate fields.
|
|
58292
|
+
stack: norm([...profile.stack ?? [], ...profile.languages ?? []]),
|
|
56862
58293
|
domains: norm(profile.domains),
|
|
56863
58294
|
kinds: ["Pattern", "Technique", "AntiPattern"],
|
|
56864
58295
|
...seed.length ? { seed } : {},
|
|
@@ -56870,6 +58301,13 @@ function createWorkspaceEngine(opts) {
|
|
|
56870
58301
|
};
|
|
56871
58302
|
cloud.search(q).then((res) => {
|
|
56872
58303
|
remotePriors = res.nodes ?? [];
|
|
58304
|
+
const contributorLabels = [];
|
|
58305
|
+
for (const n of remotePriors) {
|
|
58306
|
+
const c = n.attrs?.["contributor"];
|
|
58307
|
+
if (typeof c?.label === "string") contributorLabels.push(c.label);
|
|
58308
|
+
if (typeof c?.team === "string") contributorLabels.push(c.team);
|
|
58309
|
+
}
|
|
58310
|
+
if (contributorLabels.length > 0) registerRenderedContributorLabels(contributorLabels);
|
|
56873
58311
|
contextDirty = true;
|
|
56874
58312
|
}).catch(() => {
|
|
56875
58313
|
}).finally(() => {
|
|
@@ -56887,8 +58325,16 @@ function createWorkspaceEngine(opts) {
|
|
|
56887
58325
|
if (typeof cloud.search !== "function") return;
|
|
56888
58326
|
intentRemoteInFlight = true;
|
|
56889
58327
|
cloud.search({
|
|
56890
|
-
|
|
56891
|
-
|
|
58328
|
+
// VS-context-affinity: the intent lane used to send an empty context
|
|
58329
|
+
// while the priming lane sent the profile — opposite postures on the
|
|
58330
|
+
// same method. Under "union derived + stated, canonicalize once"
|
|
58331
|
+
// (VS-intent-fence) the stated intent IS the query and the derived
|
|
58332
|
+
// stack rides along as structured relevanceContext: the cloud no
|
|
58333
|
+
// longer concatenates these tokens into the embedding string, it
|
|
58334
|
+
// re-weights anchored hits with them (stated beats derived — the
|
|
58335
|
+
// intent text drives entry search, the stack only re-ranks).
|
|
58336
|
+
stack: normStackTokens([...profile?.stack ?? [], ...profile?.languages ?? []]),
|
|
58337
|
+
domains: normStackTokens(profile?.domains ?? []),
|
|
56892
58338
|
kinds: ["Pattern", "Technique", "AntiPattern", "Problem", "Solution"],
|
|
56893
58339
|
q: liveIntent,
|
|
56894
58340
|
channel: "intent",
|
|
@@ -57135,6 +58581,10 @@ function createWorkspaceEngine(opts) {
|
|
|
57135
58581
|
let exposureEvicted = 0;
|
|
57136
58582
|
let exposureUnshown = 0;
|
|
57137
58583
|
let corroboratedEdges = 0;
|
|
58584
|
+
let refutesStamped = 0;
|
|
58585
|
+
let recurrencesMarked = 0;
|
|
58586
|
+
let reopenedFromRegression = 0;
|
|
58587
|
+
let fixHeldConfirmed = 0;
|
|
57138
58588
|
const dispositions = emptyTagDispositions();
|
|
57139
58589
|
let unresolvedHandles = 0;
|
|
57140
58590
|
let priorEdgesFlagOff = 0;
|
|
@@ -57142,6 +58592,17 @@ function createWorkspaceEngine(opts) {
|
|
|
57142
58592
|
let touchedFileTurns = 0;
|
|
57143
58593
|
let touchedToolTurns = 0;
|
|
57144
58594
|
let anchorHintsUpgraded = 0;
|
|
58595
|
+
let foldAttempts = 0;
|
|
58596
|
+
let foldNoFile = 0;
|
|
58597
|
+
let foldNoCandidates = 0;
|
|
58598
|
+
let foldKindVeto = 0;
|
|
58599
|
+
let foldJaccardHit = 0;
|
|
58600
|
+
let foldScoutHit = 0;
|
|
58601
|
+
let foldNearMiss30 = 0;
|
|
58602
|
+
let foldNearMiss45 = 0;
|
|
58603
|
+
let foldCandidatesMarked = 0;
|
|
58604
|
+
let citeAnchorsAccrued = 0;
|
|
58605
|
+
let citeAnchorsPromoted = 0;
|
|
57145
58606
|
let linked = 0;
|
|
57146
58607
|
const t = Date.now();
|
|
57147
58608
|
let processedTurns = 0;
|
|
@@ -57193,11 +58654,25 @@ function createWorkspaceEngine(opts) {
|
|
|
57193
58654
|
const r = ingestDesignProblem(store, flag, {
|
|
57194
58655
|
workspaceId: profile.id,
|
|
57195
58656
|
source: sessionId,
|
|
57196
|
-
ts: t
|
|
58657
|
+
ts: t,
|
|
58658
|
+
hostHarness: turn.hostHarness
|
|
57197
58659
|
});
|
|
57198
58660
|
if (r.created || r.corroborated) minted++;
|
|
57199
58661
|
else if (r.rejected) tagsRejected++;
|
|
57200
58662
|
else tagsRestated++;
|
|
58663
|
+
if (r.recurredResolved) {
|
|
58664
|
+
recurrencesMarked++;
|
|
58665
|
+
if (r.reopenedFromRegression) {
|
|
58666
|
+
reopenedFromRegression++;
|
|
58667
|
+
console.warn(
|
|
58668
|
+
`[errata] REOPENED from regression: ${r.problemId} \u2014 second independent witness; the fix did not hold`
|
|
58669
|
+
);
|
|
58670
|
+
} else {
|
|
58671
|
+
console.warn(
|
|
58672
|
+
`[errata] REGRESSION candidate: a resolved problem recurred (${r.problemId}) \u2014 marked resolutionSuspect, not reopened`
|
|
58673
|
+
);
|
|
58674
|
+
}
|
|
58675
|
+
}
|
|
57201
58676
|
if (flag.kind !== "constraint") sessionLastProblem.set(sessionId, designProblemId(flag.problem));
|
|
57202
58677
|
if (r.created || r.corroborated) {
|
|
57203
58678
|
try {
|
|
@@ -57271,10 +58746,42 @@ function createWorkspaceEngine(opts) {
|
|
|
57271
58746
|
handleMap,
|
|
57272
58747
|
ts: t,
|
|
57273
58748
|
mintPriors: elicit,
|
|
58749
|
+
// WM-fold phase 3: cite-time anchor accrual keys on the session — two
|
|
58750
|
+
// DISTINCT sessions co-citing a prior from the same file promote a
|
|
58751
|
+
// real anchor; one session, or none, accrues silently.
|
|
58752
|
+
sessionId,
|
|
57274
58753
|
...touched.size > 0 ? { sessionTouchedIds: touched } : {}
|
|
57275
58754
|
});
|
|
57276
58755
|
priorEdges += plan.priorEdges;
|
|
57277
58756
|
corroboratedEdges += plan.corroboratedEdges;
|
|
58757
|
+
citeAnchorsAccrued += plan.citeAnchorsAccrued;
|
|
58758
|
+
citeAnchorsPromoted += plan.citeAnchorsPromoted;
|
|
58759
|
+
const refuteResult = applyLocalRefutations(store, plan.refutes, t);
|
|
58760
|
+
refutesStamped += refuteResult.stamped;
|
|
58761
|
+
for (const c of plan.corroborations) {
|
|
58762
|
+
const node2 = store.getNode(c.nodeId);
|
|
58763
|
+
if (node2?.label !== "Solution") continue;
|
|
58764
|
+
const held = confirmFixHeld(store, c.nodeId, t);
|
|
58765
|
+
if (held > 0) {
|
|
58766
|
+
fixHeldConfirmed += held;
|
|
58767
|
+
console.log(`[errata] fix-held confirmed: ${node2.id} \u2014 ${held} regression question(s) acknowledged`);
|
|
58768
|
+
}
|
|
58769
|
+
}
|
|
58770
|
+
for (const id of refuteResult.stampedIds) {
|
|
58771
|
+
const node2 = store.getNode(id);
|
|
58772
|
+
if (node2?.label !== "Solution") continue;
|
|
58773
|
+
for (const e of store.inEdges(id, ["SOLVED_BY", "FIXED_BY"])) {
|
|
58774
|
+
const touch = markResolvedRecurrence(store, e.from, t, sessionId);
|
|
58775
|
+
if (!touch.marked) continue;
|
|
58776
|
+
recurrencesMarked++;
|
|
58777
|
+
if (touch.reopened) {
|
|
58778
|
+
reopenedFromRegression++;
|
|
58779
|
+
console.warn(
|
|
58780
|
+
`[errata] REOPENED from regression: ${e.from} \u2014 its solution ${id} was refuted by a second independent witness`
|
|
58781
|
+
);
|
|
58782
|
+
}
|
|
58783
|
+
}
|
|
58784
|
+
}
|
|
57278
58785
|
addTagDispositions(dispositions, plan.dispositions);
|
|
57279
58786
|
unresolvedHandles += plan.unresolvedHandles.length;
|
|
57280
58787
|
priorEdgesFlagOff += plan.priorEdgesSuppressed.flagOff;
|
|
@@ -57294,26 +58801,93 @@ function createWorkspaceEngine(opts) {
|
|
|
57294
58801
|
const anchorProvenance = p.location?.path ? "witnessed" : "edited";
|
|
57295
58802
|
const hintPath = anchorPath ? void 0 : turnFile ?? wf;
|
|
57296
58803
|
const dedupPath = anchorPath ?? hintPath;
|
|
58804
|
+
let pendingNomination = null;
|
|
57297
58805
|
if (dedupPath) {
|
|
57298
58806
|
try {
|
|
57299
|
-
|
|
57300
|
-
|
|
58807
|
+
foldAttempts++;
|
|
58808
|
+
const fold = foldSameAnchorProblem(store, dedupPath, profile.id, p.statement, sessionId, t, p.kind);
|
|
58809
|
+
if (fold.noFile) foldNoFile++;
|
|
58810
|
+
else if (fold.candidates === 0) foldNoCandidates++;
|
|
58811
|
+
foldKindVeto += fold.kindVetoed;
|
|
58812
|
+
if (!fold.foldedId) {
|
|
58813
|
+
if (fold.bestJaccard >= 0.45) foldNearMiss45++;
|
|
58814
|
+
else if (fold.bestJaccard >= 0.3) foldNearMiss30++;
|
|
58815
|
+
if (fold.nominate) {
|
|
58816
|
+
pendingNomination = { ...fold.nominate, source: "file-scout" };
|
|
58817
|
+
}
|
|
58818
|
+
}
|
|
58819
|
+
if (fold.foldedId) {
|
|
58820
|
+
if (fold.foldedBy === "scout") foldScoutHit++;
|
|
58821
|
+
else foldJaccardHit++;
|
|
57301
58822
|
minted++;
|
|
57302
58823
|
if (p.kind !== "constraint") {
|
|
57303
|
-
sessionLastProblem.set(sessionId,
|
|
57304
|
-
inScopeProblemId =
|
|
58824
|
+
sessionLastProblem.set(sessionId, fold.foldedId);
|
|
58825
|
+
inScopeProblemId = fold.foldedId;
|
|
57305
58826
|
}
|
|
57306
|
-
if (p.threadId) threads.set(p.threadId,
|
|
58827
|
+
if (p.threadId) threads.set(p.threadId, fold.foldedId);
|
|
57307
58828
|
continue;
|
|
57308
58829
|
}
|
|
57309
58830
|
} catch {
|
|
57310
58831
|
}
|
|
57311
58832
|
}
|
|
58833
|
+
if (!pendingNomination) {
|
|
58834
|
+
try {
|
|
58835
|
+
const ws = findWorkspaceFoldCandidate(store, profile.id, p.statement, p.kind);
|
|
58836
|
+
if (ws) pendingNomination = { ...ws, source: "workspace-scout" };
|
|
58837
|
+
} catch (err2) {
|
|
58838
|
+
console.warn("[errata] workspace fold scout failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
|
|
58839
|
+
}
|
|
58840
|
+
}
|
|
57312
58841
|
const r = ingestDesignProblem(store, { problem: p.statement, kind: p.kind }, {
|
|
57313
58842
|
workspaceId: profile.id,
|
|
57314
58843
|
source: sessionId,
|
|
57315
|
-
ts: t
|
|
58844
|
+
ts: t,
|
|
58845
|
+
hostHarness: turn.hostHarness
|
|
57316
58846
|
});
|
|
58847
|
+
if (r.created && dedupPath) {
|
|
58848
|
+
try {
|
|
58849
|
+
const closedId = findResolvedAnchorMatch(store, dedupPath, profile.id, p.statement, p.kind);
|
|
58850
|
+
const touch = closedId ? markResolvedRecurrence(store, closedId, t, sessionId) : { marked: false, reopened: false };
|
|
58851
|
+
if (closedId && touch.marked) {
|
|
58852
|
+
store.mergeEdge({
|
|
58853
|
+
id: `edge_${digest({ from: r.problemId, type: "REGRESSION_OF", to: closedId })}`.slice(0, 24),
|
|
58854
|
+
from: r.problemId,
|
|
58855
|
+
to: closedId,
|
|
58856
|
+
type: "REGRESSION_OF",
|
|
58857
|
+
confidence: 0.5,
|
|
58858
|
+
extractionSource: "daemon-extracted",
|
|
58859
|
+
createdAt: t,
|
|
58860
|
+
lastSeenAt: t,
|
|
58861
|
+
navSuccesses: 0,
|
|
58862
|
+
navFailures: 0,
|
|
58863
|
+
attrs: { provisional: true, machineProposed: true, session: sessionId }
|
|
58864
|
+
});
|
|
58865
|
+
recurrencesMarked++;
|
|
58866
|
+
if (touch.reopened) reopenedFromRegression++;
|
|
58867
|
+
console.warn(
|
|
58868
|
+
`[errata] REGRESSION candidate: new problem ${r.problemId} paraphrases resolved ${closedId} on ${dedupPath} \u2014 bound REGRESSION_OF, marked resolutionSuspect` + (touch.reopened ? "; second independent witness \u2014 REOPENED" : "")
|
|
58869
|
+
);
|
|
58870
|
+
}
|
|
58871
|
+
} catch (err2) {
|
|
58872
|
+
console.warn(
|
|
58873
|
+
"[errata] regression recognition failed (mint unaffected):",
|
|
58874
|
+
err2 instanceof Error ? err2.message : err2
|
|
58875
|
+
);
|
|
58876
|
+
}
|
|
58877
|
+
}
|
|
58878
|
+
if (r.created && pendingNomination) {
|
|
58879
|
+
try {
|
|
58880
|
+
const marked = markFoldCandidate(store, r.problemId, pendingNomination.targetId, {
|
|
58881
|
+
...pendingNomination.hashCos !== void 0 ? { hashCos: pendingNomination.hashCos } : {},
|
|
58882
|
+
...pendingNomination.jaccard !== void 0 ? { jaccard: pendingNomination.jaccard } : {},
|
|
58883
|
+
source: pendingNomination.source,
|
|
58884
|
+
ts: t
|
|
58885
|
+
});
|
|
58886
|
+
if (marked) foldCandidatesMarked++;
|
|
58887
|
+
} catch (err2) {
|
|
58888
|
+
console.warn("[errata] fold nomination failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
|
|
58889
|
+
}
|
|
58890
|
+
}
|
|
57317
58891
|
if (r.created || r.corroborated) {
|
|
57318
58892
|
minted++;
|
|
57319
58893
|
if (p.kind !== "constraint") {
|
|
@@ -57649,7 +59223,7 @@ function createWorkspaceEngine(opts) {
|
|
|
57649
59223
|
if (typeof cloud.reportContradictions === "function") {
|
|
57650
59224
|
await sendWitnesses(
|
|
57651
59225
|
"contradict",
|
|
57652
|
-
plan.refutes,
|
|
59226
|
+
wireWitnessItems(plan.refutes),
|
|
57653
59227
|
(items2, session) => cloud.reportContradictions({
|
|
57654
59228
|
daemonVersion: DAEMON_VERSION,
|
|
57655
59229
|
projectId: profile.id,
|
|
@@ -57717,6 +59291,15 @@ function createWorkspaceEngine(opts) {
|
|
|
57717
59291
|
triaged,
|
|
57718
59292
|
priorEdges,
|
|
57719
59293
|
corroboratedEdges,
|
|
59294
|
+
// RG-mark: refute witnesses stamped onto local nodes (the local half of
|
|
59295
|
+
// the contradict channel; the wire half rides the witness ledger).
|
|
59296
|
+
refutesStamped,
|
|
59297
|
+
// RG-recognize tier A: resolved problems the door saw recur this pass.
|
|
59298
|
+
recurrencesMarked,
|
|
59299
|
+
// RG-reopen: closes flipped open on the two-touch evidence bar.
|
|
59300
|
+
reopenedFromRegression,
|
|
59301
|
+
// RG-prime: regression questions acknowledged by a fix-held confirm.
|
|
59302
|
+
fixHeldConfirmed,
|
|
57720
59303
|
// WM-labels calibration cells (see prior-tags exposure split).
|
|
57721
59304
|
exposureShown,
|
|
57722
59305
|
exposureEvicted,
|
|
@@ -57726,6 +59309,20 @@ function createWorkspaceEngine(opts) {
|
|
|
57726
59309
|
exposureOutcomes: exposureShown + exposureEvicted + exposureUnshown,
|
|
57727
59310
|
// AC-hint-upgrade (harvest seam): edited-this-turn hint promotions.
|
|
57728
59311
|
anchorHintsUpgraded,
|
|
59312
|
+
// WM-fold funnel (phase 0) — which gate of the same-anchor fold
|
|
59313
|
+
// conjunction eats the mass, and the near-miss bands under the bars.
|
|
59314
|
+
foldAttempts,
|
|
59315
|
+
foldNoFile,
|
|
59316
|
+
foldNoCandidates,
|
|
59317
|
+
foldKindVeto,
|
|
59318
|
+
foldJaccardHit,
|
|
59319
|
+
foldScoutHit,
|
|
59320
|
+
foldNearMiss30,
|
|
59321
|
+
foldNearMiss45,
|
|
59322
|
+
foldCandidatesMarked,
|
|
59323
|
+
// WM-fold phase 3 — cite-time anchor accrual on cited priors.
|
|
59324
|
+
citeAnchorsAccrued,
|
|
59325
|
+
citeAnchorsPromoted,
|
|
57729
59326
|
touchedFileTurns,
|
|
57730
59327
|
touchedToolTurns,
|
|
57731
59328
|
// Seam diagnostic: sessions holding tool-run records at harvest time.
|
|
@@ -57850,11 +59447,70 @@ function createWorkspaceEngine(opts) {
|
|
|
57850
59447
|
await yieldToLoop();
|
|
57851
59448
|
await harvestSession(ref.sessionId, ref.path);
|
|
57852
59449
|
}
|
|
59450
|
+
const codexRefs = codexRolloutsForCwd(opts.workspaceRoot, {
|
|
59451
|
+
sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
|
|
59452
|
+
limit: 25
|
|
59453
|
+
});
|
|
59454
|
+
for (const ref of codexRefs) {
|
|
59455
|
+
await yieldToLoop();
|
|
59456
|
+
await harvestSession(ref.sessionId, ref.path);
|
|
59457
|
+
}
|
|
59458
|
+
const geminiRefs = geminiTranscriptsForCwd(opts.workspaceRoot, {
|
|
59459
|
+
sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
|
|
59460
|
+
limit: 25
|
|
59461
|
+
});
|
|
59462
|
+
for (const ref of geminiRefs) {
|
|
59463
|
+
await yieldToLoop();
|
|
59464
|
+
await harvestSession(ref.sessionId, ref.path);
|
|
59465
|
+
}
|
|
59466
|
+
const opencodeRefs = opencodeTranscriptsForCwd(opts.workspaceRoot, {
|
|
59467
|
+
sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
|
|
59468
|
+
limit: 25
|
|
59469
|
+
});
|
|
59470
|
+
for (const ref of opencodeRefs) {
|
|
59471
|
+
await yieldToLoop();
|
|
59472
|
+
await harvestSession(ref.sessionId, ref.path);
|
|
59473
|
+
}
|
|
57853
59474
|
} catch {
|
|
57854
59475
|
}
|
|
57855
59476
|
})();
|
|
57856
59477
|
});
|
|
59478
|
+
const sweepCodexRollouts = () => {
|
|
59479
|
+
setImmediate(() => {
|
|
59480
|
+
void (async () => {
|
|
59481
|
+
try {
|
|
59482
|
+
const since = Date.now() - CODEX_SWEEP_LOOKBACK_MS;
|
|
59483
|
+
const refs = [
|
|
59484
|
+
...codexRolloutsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 }),
|
|
59485
|
+
...geminiTranscriptsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 }),
|
|
59486
|
+
...opencodeTranscriptsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 })
|
|
59487
|
+
];
|
|
59488
|
+
for (const ref of refs) {
|
|
59489
|
+
await yieldToLoop();
|
|
59490
|
+
await harvestSession(ref.sessionId, ref.path);
|
|
59491
|
+
}
|
|
59492
|
+
} catch {
|
|
59493
|
+
}
|
|
59494
|
+
})();
|
|
59495
|
+
});
|
|
59496
|
+
};
|
|
57857
59497
|
const designRollup = opts.designRollup ?? (process.env["ERRATA_ROLLUP"] === "1" ? haikuDesignRollup(process.env["ANTHROPIC_API_KEY"] ?? null) : void 0);
|
|
59498
|
+
const foldJudgeModel = process.env["ERRATA_FOLD_JUDGE_MODEL"] ?? "gpt-5-mini";
|
|
59499
|
+
const foldJudge = process.env["ERRATA_FOLD_JUDGE"] === "1" && isChatConfigured(foldJudgeModel) ? async (a, b) => {
|
|
59500
|
+
const out2 = await chat({
|
|
59501
|
+
model: foldJudgeModel,
|
|
59502
|
+
system: "You judge whether two independently-written one-line problem reports from the same codebase describe the SAME underlying problem. Reworded restatements of one problem are SAME; related-but-different defects, or a defect vs a design constraint, are DISTINCT. Answer with exactly one word: SAME or DISTINCT.",
|
|
59503
|
+
user: `A: ${a}
|
|
59504
|
+
B: ${b}`,
|
|
59505
|
+
maxTokens: 8
|
|
59506
|
+
});
|
|
59507
|
+
if (!out2) return null;
|
|
59508
|
+
const same = /\bSAME\b/i.test(out2);
|
|
59509
|
+
const distinct = /\bDISTINCT\b/i.test(out2);
|
|
59510
|
+
if (same && !distinct) return "same";
|
|
59511
|
+
if (distinct && !same) return "distinct";
|
|
59512
|
+
return null;
|
|
59513
|
+
} : void 0;
|
|
57858
59514
|
const onSessionEnd = (e) => {
|
|
57859
59515
|
if (!designRollup) return;
|
|
57860
59516
|
setImmediate(() => {
|
|
@@ -57992,6 +59648,7 @@ function createWorkspaceEngine(opts) {
|
|
|
57992
59648
|
refreshContextNow();
|
|
57993
59649
|
},
|
|
57994
59650
|
async tick() {
|
|
59651
|
+
sweepCodexRollouts();
|
|
57995
59652
|
maybeRefreshRemotePriors();
|
|
57996
59653
|
const report = {
|
|
57997
59654
|
generalizerEventsProcessed: 0,
|
|
@@ -58130,16 +59787,49 @@ function createWorkspaceEngine(opts) {
|
|
|
58130
59787
|
const sem = await embedSemanticNodes(store);
|
|
58131
59788
|
if (sem.embedded > 0) {
|
|
58132
59789
|
console.log(`[errata] embedded ${sem.embedded} semantic node(s)`);
|
|
58133
|
-
|
|
58134
|
-
|
|
58135
|
-
|
|
58136
|
-
|
|
59790
|
+
}
|
|
59791
|
+
const parkWmRaw = store.getMeta?.("fold_park_sweep_at") ?? null;
|
|
59792
|
+
const e = mergeProblemsByEmbedding(store, {
|
|
59793
|
+
ts: Date.now(),
|
|
59794
|
+
...parkWmRaw !== null ? { freshSince: Number(parkWmRaw) || 0 } : {}
|
|
59795
|
+
});
|
|
59796
|
+
store.setMeta?.("fold_park_sweep_at", String(Date.now()));
|
|
59797
|
+
if (e.merged > 0 || (e.nearMissMarked ?? 0) > 0) {
|
|
59798
|
+
console.log(`[errata] dedup: merged ${e.merged} paraphrased problem(s) across ${e.clusters} cluster(s), parked ${e.nearMissMarked ?? 0} near-miss nomination(s)${parkWmRaw === null ? " (first full sweep)" : ""}`);
|
|
59799
|
+
}
|
|
59800
|
+
if (sem.embedded > 0) {
|
|
58137
59801
|
const cb = bindCanonicalNeighbors(store, { ts: Date.now() });
|
|
58138
59802
|
if (cb.bound > 0) {
|
|
58139
59803
|
console.log(`[errata] canonical-bind: +${cb.bound} machine-proposed link(s) across ${cb.scanned} problem(s)`);
|
|
58140
59804
|
}
|
|
58141
59805
|
refreshContextNow();
|
|
58142
59806
|
}
|
|
59807
|
+
const rg = bindResolvedRegressions(store, { ts: Date.now() });
|
|
59808
|
+
if (rg.bound > 0) {
|
|
59809
|
+
console.warn(`[errata] REGRESSION: bound ${rg.bound} open problem(s) to resolved predecessors${rg.full ? " (first full sweep)" : ""}`);
|
|
59810
|
+
refreshContextNow();
|
|
59811
|
+
}
|
|
59812
|
+
const fjStart = Date.now();
|
|
59813
|
+
const fj = await resolveFoldCandidates(store, {
|
|
59814
|
+
ts: fjStart,
|
|
59815
|
+
...foldJudge ? { judge: foldJudge } : {}
|
|
59816
|
+
});
|
|
59817
|
+
if (fj.examined > 0) {
|
|
59818
|
+
appendPassLedger(paths.configDir, "fold-judge", Date.now() - fjStart, {
|
|
59819
|
+
examined: fj.examined,
|
|
59820
|
+
folded: fj.folded,
|
|
59821
|
+
foldedEmbedding: fj.foldedEmbedding,
|
|
59822
|
+
foldedJudge: fj.foldedJudge,
|
|
59823
|
+
rejected: fj.rejected,
|
|
59824
|
+
standing: fj.standing,
|
|
59825
|
+
judgeErrors: fj.judgeErrors,
|
|
59826
|
+
retargeted: fj.retargeted
|
|
59827
|
+
});
|
|
59828
|
+
if (fj.folded > 0) {
|
|
59829
|
+
console.log(`[errata] fold-judge: folded ${fj.folded} paraphrase pair(s) (${fj.foldedEmbedding} by embedding, ${fj.foldedJudge} by judge), ${fj.standing} standing`);
|
|
59830
|
+
refreshContextNow();
|
|
59831
|
+
}
|
|
59832
|
+
}
|
|
58143
59833
|
return { embedded: sem.embedded };
|
|
58144
59834
|
} catch (err2) {
|
|
58145
59835
|
console.warn("[errata] settled embed failed:", err2);
|
|
@@ -58174,6 +59864,10 @@ function createWorkspaceEngine(opts) {
|
|
|
58174
59864
|
console.log(`[errata] dedup: merged ${e.merged} paraphrased problem(s) by embedding across ${e.clusters} cluster(s)`);
|
|
58175
59865
|
refreshContextNow();
|
|
58176
59866
|
}
|
|
59867
|
+
const rg = bindResolvedRegressions(store, { ts: Date.now() });
|
|
59868
|
+
if (rg.bound > 0) {
|
|
59869
|
+
console.warn(`[errata] REGRESSION: bound ${rg.bound} open problem(s) to resolved predecessors${rg.full ? " (first full sweep)" : ""}`);
|
|
59870
|
+
}
|
|
58177
59871
|
const cb = bindCanonicalNeighbors(store, { ts: Date.now() });
|
|
58178
59872
|
if (cb.bound > 0) {
|
|
58179
59873
|
console.log(`[errata] canonical-bind: +${cb.bound} machine-proposed link(s) across ${cb.scanned} problem(s)`);
|
|
@@ -58264,6 +59958,7 @@ function createWorkspaceEngine(opts) {
|
|
|
58264
59958
|
async stop() {
|
|
58265
59959
|
if (stopGit) stopGit();
|
|
58266
59960
|
if (passWorker) await passWorker.stop();
|
|
59961
|
+
if (watchBreaker) watchBreaker.stop();
|
|
58267
59962
|
if (watcher) await watcher.close();
|
|
58268
59963
|
if (flushTimer) clearTimeout(flushTimer);
|
|
58269
59964
|
for (const tmr of diagTimers.values()) clearTimeout(tmr);
|
|
@@ -59633,6 +61328,157 @@ function createRootAdopter(deps) {
|
|
|
59633
61328
|
};
|
|
59634
61329
|
}
|
|
59635
61330
|
|
|
61331
|
+
// src/health-sentinel.ts
|
|
61332
|
+
function numEnv4(key, fallback) {
|
|
61333
|
+
const n = Number(process.env[key]);
|
|
61334
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
61335
|
+
}
|
|
61336
|
+
var SENTINEL_TICK_MS = numEnv4("ERRATA_SENTINEL_TICK_MS", 3e4);
|
|
61337
|
+
var SENTINEL_WINDOW_TICKS = numEnv4("ERRATA_SENTINEL_WINDOW_TICKS", 6);
|
|
61338
|
+
var SENTINEL_BURN_CORES = numEnv4("ERRATA_SENTINEL_BURN_CORES", 1.2);
|
|
61339
|
+
var SENTINEL_STORM_RATE = numEnv4("ERRATA_SENTINEL_STORM_RATE", 1e3);
|
|
61340
|
+
var SENTINEL_STORM_TICKS = numEnv4("ERRATA_SENTINEL_STORM_TICKS", 4);
|
|
61341
|
+
var SENTINEL_RENOTIFY_MS = numEnv4("ERRATA_SENTINEL_RENOTIFY_MS", 18e5);
|
|
61342
|
+
var SENTINEL_CALM_TICKS = numEnv4("ERRATA_SENTINEL_CALM_TICKS", 2);
|
|
61343
|
+
var SENTINEL_TOAST_COOLDOWN_MS = numEnv4("ERRATA_SENTINEL_TOAST_COOLDOWN_MS", 6e5);
|
|
61344
|
+
function createHealthSentinel(deps = {}) {
|
|
61345
|
+
const now = deps.now ?? Date.now;
|
|
61346
|
+
const cpuUsage = deps.cpuUsage ?? (() => process.cpuUsage());
|
|
61347
|
+
const readCensus = deps.readCensus ?? (() => {
|
|
61348
|
+
const c = watchCensus();
|
|
61349
|
+
return {
|
|
61350
|
+
dispatched: c.dispatched,
|
|
61351
|
+
logged: c.logged,
|
|
61352
|
+
topOffender: c.topOffender ? { path: c.topOffender.path, count: c.topOffender.count } : null
|
|
61353
|
+
};
|
|
61354
|
+
});
|
|
61355
|
+
const activePasses = deps.activePasses ?? (() => passState().active.map((p) => p.name));
|
|
61356
|
+
const rssMb = deps.rssMb ?? (() => Math.round(process.memoryUsage.rss() / 1048576));
|
|
61357
|
+
const toast = deps.notify ?? ((body2, key) => notifyOpsEvent("daemon-distress", body2, { key }));
|
|
61358
|
+
let prevCpu = cpuUsage();
|
|
61359
|
+
let prevTs = now();
|
|
61360
|
+
let prevDispatched = null;
|
|
61361
|
+
let prevLogged = 0;
|
|
61362
|
+
const coreSamples = [];
|
|
61363
|
+
let stormTicks = 0;
|
|
61364
|
+
let calmTicks = 0;
|
|
61365
|
+
let distress = null;
|
|
61366
|
+
let lastNotifiedAt = 0;
|
|
61367
|
+
const lastToastByKind = /* @__PURE__ */ new Map();
|
|
61368
|
+
const attribution = () => {
|
|
61369
|
+
const parts2 = [];
|
|
61370
|
+
const passes = activePasses();
|
|
61371
|
+
if (passes.length > 0) parts2.push(`active pass: ${passes.join(", ")}`);
|
|
61372
|
+
const c = readCensus();
|
|
61373
|
+
if (c.topOffender) parts2.push(`hottest watch path: ${c.topOffender.path}`);
|
|
61374
|
+
parts2.push(`rss ${rssMb()}MB`);
|
|
61375
|
+
return parts2.join(" \xB7 ");
|
|
61376
|
+
};
|
|
61377
|
+
const maybeToast = (d, t) => {
|
|
61378
|
+
if (t - (lastToastByKind.get(d.kind) ?? 0) < SENTINEL_TOAST_COOLDOWN_MS) return;
|
|
61379
|
+
lastToastByKind.set(d.kind, t);
|
|
61380
|
+
toast(d.headline, d.kind);
|
|
61381
|
+
lastNotifiedAt = t;
|
|
61382
|
+
};
|
|
61383
|
+
const transition = (next) => {
|
|
61384
|
+
const t = now();
|
|
61385
|
+
if (next && !distress) {
|
|
61386
|
+
distress = next;
|
|
61387
|
+
deps.ledger?.("enter", next);
|
|
61388
|
+
maybeToast(next, t);
|
|
61389
|
+
deps.onChange?.(next);
|
|
61390
|
+
} else if (next && distress) {
|
|
61391
|
+
distress.body = next.body;
|
|
61392
|
+
distress.headline = next.headline;
|
|
61393
|
+
if (t - lastNotifiedAt >= SENTINEL_RENOTIFY_MS) maybeToast(distress, t);
|
|
61394
|
+
} else if (!next && distress) {
|
|
61395
|
+
deps.ledger?.("clear", distress);
|
|
61396
|
+
distress = null;
|
|
61397
|
+
deps.onChange?.(null);
|
|
61398
|
+
}
|
|
61399
|
+
};
|
|
61400
|
+
const evaluate = () => {
|
|
61401
|
+
const t = now();
|
|
61402
|
+
const wallMs = Math.max(1, t - prevTs);
|
|
61403
|
+
const cpu = cpuUsage();
|
|
61404
|
+
const cores = (cpu.user + cpu.system - prevCpu.user - prevCpu.system) / 1e3 / wallMs;
|
|
61405
|
+
prevCpu = cpu;
|
|
61406
|
+
prevTs = t;
|
|
61407
|
+
coreSamples.push(cores);
|
|
61408
|
+
if (coreSamples.length > SENTINEL_WINDOW_TICKS) coreSamples.shift();
|
|
61409
|
+
const c = readCensus();
|
|
61410
|
+
const dispatchRate = prevDispatched === null ? 0 : (c.dispatched - prevDispatched) / wallMs * 1e3;
|
|
61411
|
+
const survived = c.logged - prevLogged;
|
|
61412
|
+
prevDispatched = c.dispatched;
|
|
61413
|
+
prevLogged = c.logged;
|
|
61414
|
+
stormTicks = dispatchRate > SENTINEL_STORM_RATE && survived === 0 ? stormTicks + 1 : 0;
|
|
61415
|
+
let wanted = null;
|
|
61416
|
+
if (stormTicks >= SENTINEL_STORM_TICKS) {
|
|
61417
|
+
wanted = {
|
|
61418
|
+
kind: "watch-storm",
|
|
61419
|
+
headline: `errata's file watcher is stuck busy (~${Math.round(dispatchRate)} events/sec with nothing changing) \u2014 run \`errata status\` for detail`,
|
|
61420
|
+
body: `filesystem watcher is processing ~${Math.round(dispatchRate)} events/sec with NOTHING surviving \u2014 a wedged or ignored-tree watch is burning CPU for no signal \xB7 ${attribution()}`,
|
|
61421
|
+
sinceTs: distress?.kind === "watch-storm" ? distress.sinceTs : t
|
|
61422
|
+
};
|
|
61423
|
+
} else {
|
|
61424
|
+
const windowFull = coreSamples.length >= SENTINEL_WINDOW_TICKS;
|
|
61425
|
+
const avgCores = coreSamples.reduce((a, b) => a + b, 0) / Math.max(1, coreSamples.length);
|
|
61426
|
+
if (windowFull && avgCores >= SENTINEL_BURN_CORES && cores >= SENTINEL_BURN_CORES && activePasses().length === 0) {
|
|
61427
|
+
wanted = {
|
|
61428
|
+
kind: "cpu-burn",
|
|
61429
|
+
headline: `errata is using more CPU than expected (${avgCores.toFixed(1)} cores with no work running) \u2014 run \`errata status\` for detail`,
|
|
61430
|
+
body: `sustained CPU with no pass running \u2014 avg ${avgCores.toFixed(1)} cores \xB7 ${attribution()}`,
|
|
61431
|
+
sinceTs: distress?.kind === "cpu-burn" ? distress.sinceTs : t
|
|
61432
|
+
};
|
|
61433
|
+
}
|
|
61434
|
+
}
|
|
61435
|
+
if (wanted) {
|
|
61436
|
+
calmTicks = 0;
|
|
61437
|
+
transition(wanted);
|
|
61438
|
+
} else if (distress) {
|
|
61439
|
+
calmTicks++;
|
|
61440
|
+
if (calmTicks >= SENTINEL_CALM_TICKS) {
|
|
61441
|
+
calmTicks = 0;
|
|
61442
|
+
transition(null);
|
|
61443
|
+
}
|
|
61444
|
+
}
|
|
61445
|
+
};
|
|
61446
|
+
const interval = deps.scheduleTicks === false ? null : setInterval(() => {
|
|
61447
|
+
try {
|
|
61448
|
+
evaluate();
|
|
61449
|
+
} catch {
|
|
61450
|
+
}
|
|
61451
|
+
}, SENTINEL_TICK_MS);
|
|
61452
|
+
interval?.unref?.();
|
|
61453
|
+
return {
|
|
61454
|
+
current: () => distress ? { ...distress } : null,
|
|
61455
|
+
tick: evaluate,
|
|
61456
|
+
stop: () => {
|
|
61457
|
+
if (interval) clearInterval(interval);
|
|
61458
|
+
}
|
|
61459
|
+
};
|
|
61460
|
+
}
|
|
61461
|
+
|
|
61462
|
+
// src/codex-wake.ts
|
|
61463
|
+
var CODEX_WAKE_LOOKBACK_MS = 10 * 6e4;
|
|
61464
|
+
var CODEX_WAKE_BOOT_LOOKBACK_MS = 24 * 60 * 6e4;
|
|
61465
|
+
function codexWakeCandidates(input) {
|
|
61466
|
+
if (input.disabled) return [];
|
|
61467
|
+
const find = input.find ?? ((cwd, since) => codexRolloutsForCwd(cwd, { sinceMs: since, limit: 1 }).length + geminiTranscriptsForCwd(cwd, { sinceMs: since, limit: 1 }).length + opencodeTranscriptsForCwd(cwd, { sinceMs: since, limit: 1 }).length);
|
|
61468
|
+
const out2 = [];
|
|
61469
|
+
for (const entry of input.entries) {
|
|
61470
|
+
if (input.liveRoots.has(input.normPath(entry.path))) continue;
|
|
61471
|
+
let n = 0;
|
|
61472
|
+
try {
|
|
61473
|
+
n = find(entry.path, input.sinceMs);
|
|
61474
|
+
} catch {
|
|
61475
|
+
continue;
|
|
61476
|
+
}
|
|
61477
|
+
if (n > 0) out2.push({ name: entry.name, path: entry.path });
|
|
61478
|
+
}
|
|
61479
|
+
return out2;
|
|
61480
|
+
}
|
|
61481
|
+
|
|
59636
61482
|
// src/multi.ts
|
|
59637
61483
|
var projectSymbolSalts = /* @__PURE__ */ new Map();
|
|
59638
61484
|
async function resolveProjectSymbolSalt(client, projectId) {
|
|
@@ -59777,9 +61623,24 @@ async function startMultiDaemon(opts = {}) {
|
|
|
59777
61623
|
for (const r of records) r.engine.markContextDirty();
|
|
59778
61624
|
}
|
|
59779
61625
|
}) : null;
|
|
61626
|
+
const healthSentinel = createHealthSentinel({
|
|
61627
|
+
onChange: () => {
|
|
61628
|
+
for (const r of records) r.engine.markContextDirty();
|
|
61629
|
+
},
|
|
61630
|
+
ledger: (event, d) => {
|
|
61631
|
+
const dir = records[0]?.engine.paths.configDir;
|
|
61632
|
+
if (dir) {
|
|
61633
|
+
appendPassLedger(dir, "health-sentinel", 0, {
|
|
61634
|
+
[`distress:${event}:${d.kind}`]: 1,
|
|
61635
|
+
distressSinceTs: d.sinceTs
|
|
61636
|
+
});
|
|
61637
|
+
}
|
|
61638
|
+
}
|
|
61639
|
+
});
|
|
59780
61640
|
let serverPending = null;
|
|
59781
61641
|
const boundaryListeners = [];
|
|
59782
61642
|
let boundaryFlushing = false;
|
|
61643
|
+
let settleRunning = false;
|
|
59783
61644
|
let boundaryFlushStartedAt = 0;
|
|
59784
61645
|
let boundaryFlushStage = "";
|
|
59785
61646
|
let boundaryFlushUnits = 0;
|
|
@@ -59871,6 +61732,10 @@ async function startMultiDaemon(opts = {}) {
|
|
|
59871
61732
|
reviewUrl: () => `${baseUrl}/ws/${id}/review`,
|
|
59872
61733
|
sharedStore,
|
|
59873
61734
|
pendingUpdate: () => serverPending ?? updatePoller?.current() ?? null,
|
|
61735
|
+
// Ops-gated (default OFF): the 🚨 notice reads as "errata broke my
|
|
61736
|
+
// machine" to a non-operator. Detection + ledger run regardless — only
|
|
61737
|
+
// this push surface (and the toast, gated in notifyOpsEvent) is scoped.
|
|
61738
|
+
daemonDistress: () => loadConfig().opsNotices ? healthSentinel.current() : null,
|
|
59874
61739
|
...opts.skipWatchers ? { skipWatchers: true } : {}
|
|
59875
61740
|
});
|
|
59876
61741
|
const webApp = buildWebUi({
|
|
@@ -59959,7 +61824,7 @@ async function startMultiDaemon(opts = {}) {
|
|
|
59959
61824
|
registerWorkspace(rec.engine.profile, rec.root);
|
|
59960
61825
|
} catch {
|
|
59961
61826
|
}
|
|
59962
|
-
void rec.engine.reindex({ skipEmbed: true }).catch(() => {
|
|
61827
|
+
void rec.engine.reindex({ skipEmbed: true, quiet: rec.engine.store.nodeCount() > 0 }).catch(() => {
|
|
59963
61828
|
});
|
|
59964
61829
|
console.log(`[errata] hot-registered ${rec.entry.name} (${rec.id}) \u2014 now watching live`);
|
|
59965
61830
|
return { attached: true, id: rec.id };
|
|
@@ -60155,6 +62020,33 @@ async function startMultiDaemon(opts = {}) {
|
|
|
60155
62020
|
};
|
|
60156
62021
|
const idleSweep = setInterval(sweepIdleWorkspaces, WORKSPACE_SWEEP_INTERVAL_MS);
|
|
60157
62022
|
idleSweep.unref?.();
|
|
62023
|
+
let codexWakeFirstPass = true;
|
|
62024
|
+
const sweepCodexWake = () => {
|
|
62025
|
+
const lookback = codexWakeFirstPass ? CODEX_WAKE_BOOT_LOOKBACK_MS : CODEX_WAKE_LOOKBACK_MS;
|
|
62026
|
+
codexWakeFirstPass = false;
|
|
62027
|
+
let entries2;
|
|
62028
|
+
try {
|
|
62029
|
+
entries2 = listWorkspaceEntries();
|
|
62030
|
+
} catch {
|
|
62031
|
+
return;
|
|
62032
|
+
}
|
|
62033
|
+
const candidates = codexWakeCandidates({
|
|
62034
|
+
entries: entries2,
|
|
62035
|
+
liveRoots: new Set(records.map((r) => normPath(r.root))),
|
|
62036
|
+
normPath,
|
|
62037
|
+
sinceMs: Date.now() - lookback,
|
|
62038
|
+
disabled: process.env["ERRATA_CODEX_WAKE_DISABLE"] === "1"
|
|
62039
|
+
});
|
|
62040
|
+
for (const c of candidates) {
|
|
62041
|
+
if (attachWorkspace(c.path).attached) {
|
|
62042
|
+
console.log(`[errata] woke workspace ${c.name} \u2014 recent Codex/Gemini/OpenCode transcript (headless capture)`);
|
|
62043
|
+
}
|
|
62044
|
+
}
|
|
62045
|
+
};
|
|
62046
|
+
const codexWake = setInterval(sweepCodexWake, WORKSPACE_SWEEP_INTERVAL_MS);
|
|
62047
|
+
codexWake.unref?.();
|
|
62048
|
+
const codexWakeBoot = setTimeout(sweepCodexWake, 5e3);
|
|
62049
|
+
codexWakeBoot.unref?.();
|
|
60158
62050
|
const reindexAll = async (rOpts) => {
|
|
60159
62051
|
const out2 = /* @__PURE__ */ new Map();
|
|
60160
62052
|
const toIndex = records.filter((r) => rOpts?.force || r.engine.store.nodeCount() === 0);
|
|
@@ -60843,12 +62735,19 @@ async function startMultiDaemon(opts = {}) {
|
|
|
60843
62735
|
return { written, pruned: pruned2 };
|
|
60844
62736
|
},
|
|
60845
62737
|
async sessionBoundaryFlush() {
|
|
62738
|
+
if (!settleRunning) {
|
|
62739
|
+
settleRunning = true;
|
|
62740
|
+
try {
|
|
62741
|
+
for (const r of records) {
|
|
62742
|
+
await r.engine.embedSettled();
|
|
62743
|
+
}
|
|
62744
|
+
} finally {
|
|
62745
|
+
settleRunning = false;
|
|
62746
|
+
}
|
|
62747
|
+
}
|
|
60846
62748
|
if (boundaryFlushing) return { uploaded: 0, written: 0, pruned: 0, skipped: "in-flight" };
|
|
60847
|
-
beginFlush("
|
|
62749
|
+
beginFlush("instances");
|
|
60848
62750
|
try {
|
|
60849
|
-
for (const r of records) {
|
|
60850
|
-
await r.engine.embedSettled();
|
|
60851
|
-
}
|
|
60852
62751
|
flushStage("instances");
|
|
60853
62752
|
const inst = await daemon.syncInstancesPublic();
|
|
60854
62753
|
flushStage("skills");
|
|
@@ -60868,6 +62767,8 @@ async function startMultiDaemon(opts = {}) {
|
|
|
60868
62767
|
},
|
|
60869
62768
|
async stop() {
|
|
60870
62769
|
clearInterval(idleSweep);
|
|
62770
|
+
clearInterval(codexWake);
|
|
62771
|
+
clearTimeout(codexWakeBoot);
|
|
60871
62772
|
try {
|
|
60872
62773
|
const cur = readFileSync27(lockPath, "utf8");
|
|
60873
62774
|
if (JSON.parse(cur).pid === process.pid) unlinkSync3(lockPath);
|
|
@@ -60877,6 +62778,7 @@ async function startMultiDaemon(opts = {}) {
|
|
|
60877
62778
|
(res) => server.close(res)
|
|
60878
62779
|
);
|
|
60879
62780
|
updatePoller?.stop();
|
|
62781
|
+
healthSentinel.stop();
|
|
60880
62782
|
for (const r of records) await r.engine.stop();
|
|
60881
62783
|
if (consolidateWorker) await consolidateWorker.stop();
|
|
60882
62784
|
try {
|
|
@@ -61501,6 +63403,17 @@ function shouldUseOAuthLogin(flags2, env2 = process.env) {
|
|
|
61501
63403
|
}
|
|
61502
63404
|
|
|
61503
63405
|
// src/doctor.ts
|
|
63406
|
+
import { spawnSync } from "node:child_process";
|
|
63407
|
+
function installedHarnessVersion(bin) {
|
|
63408
|
+
try {
|
|
63409
|
+
const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 3e3 });
|
|
63410
|
+
if (r.status !== 0 && !r.stdout) return null;
|
|
63411
|
+
const m = /(\d+\.\d+\.\d+)/.exec(`${r.stdout ?? ""}${r.stderr ?? ""}`);
|
|
63412
|
+
return m ? m[1] : null;
|
|
63413
|
+
} catch {
|
|
63414
|
+
return null;
|
|
63415
|
+
}
|
|
63416
|
+
}
|
|
61504
63417
|
async function diagnoseInstallation(cfg, dependencies) {
|
|
61505
63418
|
const checks = [];
|
|
61506
63419
|
const profileName = cfg.activeInstallationProfile;
|
|
@@ -61573,6 +63486,20 @@ async function diagnoseInstallation(cfg, dependencies) {
|
|
|
61573
63486
|
});
|
|
61574
63487
|
}
|
|
61575
63488
|
}
|
|
63489
|
+
const versionOf = dependencies.harnessVersion ?? installedHarnessVersion;
|
|
63490
|
+
for (const { bin, pinned } of [
|
|
63491
|
+
{ bin: "codex", pinned: CODEX_PARSER_PINNED_VERSION },
|
|
63492
|
+
{ bin: "opencode", pinned: OPENCODE_PARSER_PINNED_VERSION }
|
|
63493
|
+
]) {
|
|
63494
|
+
const installed = versionOf(bin);
|
|
63495
|
+
checks.push(
|
|
63496
|
+
installed === null ? { id: `${bin}_parser_pin`, status: "pass", detail: `${bin} not installed \u2014 parser-pin check skipped` } : installed === pinned ? { id: `${bin}_parser_pin`, status: "pass", detail: `${bin} ${installed} matches the transcript-parser pin` } : {
|
|
63497
|
+
id: `${bin}_parser_pin`,
|
|
63498
|
+
status: "warn",
|
|
63499
|
+
detail: `${bin} ${installed} \u2260 parser pinned ${pinned} \u2014 transcripts verified against ${pinned}; re-pin fixtures if capture drifts`
|
|
63500
|
+
}
|
|
63501
|
+
);
|
|
63502
|
+
}
|
|
61576
63503
|
return {
|
|
61577
63504
|
ok: checks.every((check2) => check2.status !== "fail"),
|
|
61578
63505
|
profile: profileName,
|
|
@@ -61632,7 +63559,7 @@ var DEFAULT_CONSOLIDATION_POLICY = {
|
|
|
61632
63559
|
quiescenceMs: 6e4
|
|
61633
63560
|
// sim preferred 60s over 90s (fresher, esp. heavy-code)
|
|
61634
63561
|
};
|
|
61635
|
-
function
|
|
63562
|
+
function numEnv5(env2, key, fallback) {
|
|
61636
63563
|
const raw2 = env2[key];
|
|
61637
63564
|
if (raw2 == null || raw2.trim() === "") return fallback;
|
|
61638
63565
|
const n = Number(raw2);
|
|
@@ -61640,18 +63567,196 @@ function numEnv3(env2, key, fallback) {
|
|
|
61640
63567
|
}
|
|
61641
63568
|
function consolidationPolicyFromEnv(env2 = process.env) {
|
|
61642
63569
|
return {
|
|
61643
|
-
baseFloorMs:
|
|
61644
|
-
dutyFactor:
|
|
61645
|
-
momentumThreshold:
|
|
61646
|
-
momentumRatio:
|
|
61647
|
-
momentumRatioCap:
|
|
61648
|
-
quiescenceMs:
|
|
63570
|
+
baseFloorMs: numEnv5(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
|
|
63571
|
+
dutyFactor: numEnv5(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
|
|
63572
|
+
momentumThreshold: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
|
|
63573
|
+
momentumRatio: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
|
|
63574
|
+
momentumRatioCap: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
|
|
63575
|
+
quiescenceMs: numEnv5(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
|
|
61649
63576
|
};
|
|
61650
63577
|
}
|
|
61651
63578
|
function consolidationGapMs(lastPassMs, policy) {
|
|
61652
63579
|
return Math.max(policy.baseFloorMs, lastPassMs * policy.dutyFactor);
|
|
61653
63580
|
}
|
|
61654
63581
|
|
|
63582
|
+
// src/hook-commands.ts
|
|
63583
|
+
function hookCurlCommand(port, path2 = "/api/hook") {
|
|
63584
|
+
const url2 = `http://127.0.0.1:${port}${path2}`;
|
|
63585
|
+
return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
|
|
63586
|
+
}
|
|
63587
|
+
function hookRelayCommand(port, path2) {
|
|
63588
|
+
const url2 = `http://127.0.0.1:${port}${path2}`;
|
|
63589
|
+
return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
|
|
63590
|
+
}
|
|
63591
|
+
var CODEX_HOOKS_BEGIN = `# >>> errata hooks (errata-managed)`;
|
|
63592
|
+
var CODEX_HOOKS_END = `# <<< errata hooks`;
|
|
63593
|
+
function buildCodexHooksToml(port) {
|
|
63594
|
+
const cmd2 = hookCurlCommand(port).replace(/"/g, '\\"');
|
|
63595
|
+
const turnCmd = hookCurlCommand(port, "/api/turn").replace(/"/g, '\\"');
|
|
63596
|
+
return `${CODEX_HOOKS_BEGIN}
|
|
63597
|
+
[[hooks.PreToolUse]]
|
|
63598
|
+
matcher = ".*"
|
|
63599
|
+
|
|
63600
|
+
[[hooks.PreToolUse.hooks]]
|
|
63601
|
+
type = "command"
|
|
63602
|
+
command = "${cmd2}"
|
|
63603
|
+
timeout = 10
|
|
63604
|
+
|
|
63605
|
+
[[hooks.PostToolUse]]
|
|
63606
|
+
matcher = ".*"
|
|
63607
|
+
|
|
63608
|
+
[[hooks.PostToolUse.hooks]]
|
|
63609
|
+
type = "command"
|
|
63610
|
+
command = "${cmd2}"
|
|
63611
|
+
timeout = 10
|
|
63612
|
+
|
|
63613
|
+
[[hooks.PostToolUse.hooks]]
|
|
63614
|
+
type = "command"
|
|
63615
|
+
command = "${turnCmd}"
|
|
63616
|
+
timeout = 10
|
|
63617
|
+
${CODEX_HOOKS_END}
|
|
63618
|
+
`;
|
|
63619
|
+
}
|
|
63620
|
+
|
|
63621
|
+
// src/opencode-plugin.ts
|
|
63622
|
+
var OPENCODE_PLUGIN_MARKER = "// @inerrata opencode plugin \u2014 errata-managed (errata install-hooks opencode)";
|
|
63623
|
+
var OPENCODE_D1_CONTRACT = "Behavioral contract: (1) On ANY error, call search() BEFORE debugging \u2014 costs ~400 tokens vs 5,000\u201350,000 cold. (2) After solving non-trivial problems, contribute back: if your harness emits typed nodes (canonicalId + label + attrs), use ingest \u2014 the typed door; otherwise call contribute(), the prose fallback. (3) Before web search, try search() first \u2014 graph is 2\u201315\xD7 cheaper. (4) At session end, review what you solved and contribute anything novel.";
|
|
63624
|
+
var OPENCODE_TOOL_FAILURE_NUDGE = "[inerrata] This tool call failed \u2014 call search() with the error message BEFORE debugging from scratch (~400 tokens vs 5,000\u201350,000 cold).";
|
|
63625
|
+
function buildOpencodePluginSource(port, opts = {}) {
|
|
63626
|
+
const version2 = opts.version ?? "dev";
|
|
63627
|
+
return `${OPENCODE_PLUGIN_MARKER}
|
|
63628
|
+
// Generated by errata ${version2}. Re-run \`errata install-hooks opencode\` to update; edits here are overwritten.
|
|
63629
|
+
// What it does: mirrors each OpenCode session's OWN message record to a JSONL
|
|
63630
|
+
// file the errata daemon can parse, and tells the daemon on every turn boundary.
|
|
63631
|
+
// It has no dependencies and never throws into OpenCode.
|
|
63632
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
63633
|
+
import { join } from "node:path";
|
|
63634
|
+
import { homedir } from "node:os";
|
|
63635
|
+
|
|
63636
|
+
const DAEMON = "http://127.0.0.1:${port}";
|
|
63637
|
+
const HEADER_TYPE = "errata-opencode-session";
|
|
63638
|
+
const D1_CONTRACT = ${JSON.stringify(OPENCODE_D1_CONTRACT)};
|
|
63639
|
+
const TOOL_FAILURE_NUDGE = ${JSON.stringify(OPENCODE_TOOL_FAILURE_NUDGE)};
|
|
63640
|
+
|
|
63641
|
+
function mirrorRoot() {
|
|
63642
|
+
const explicit = process.env.ERRATA_OPENCODE_SESSIONS_DIRS;
|
|
63643
|
+
if (explicit) { const first = explicit.split(":").map((s) => s.trim()).find(Boolean); if (first) return first; }
|
|
63644
|
+
if (process.env.XDG_DATA_HOME) return join(process.env.XDG_DATA_HOME, "opencode", "errata");
|
|
63645
|
+
return join(homedir(), ".local", "share", "opencode", "errata");
|
|
63646
|
+
}
|
|
63647
|
+
|
|
63648
|
+
async function post(path, body) {
|
|
63649
|
+
try {
|
|
63650
|
+
const res = await fetch(DAEMON + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(15000) });
|
|
63651
|
+
return res.status;
|
|
63652
|
+
} catch { return 0; }
|
|
63653
|
+
}
|
|
63654
|
+
|
|
63655
|
+
export const InerrataPlugin = async (input) => {
|
|
63656
|
+
const directory = input.directory;
|
|
63657
|
+
const projectID = input.project && input.project.id ? input.project.id : null;
|
|
63658
|
+
const root = mirrorRoot();
|
|
63659
|
+
// per-session state: which message ids are already in the mirror file
|
|
63660
|
+
const written = new Map(); // sessionID -> Set<messageId>
|
|
63661
|
+
const files = new Map(); // sessionID -> path
|
|
63662
|
+
|
|
63663
|
+
function fileFor(sessionID) {
|
|
63664
|
+
let p = files.get(sessionID);
|
|
63665
|
+
if (p) return p;
|
|
63666
|
+
try { mkdirSync(root, { recursive: true }); } catch {}
|
|
63667
|
+
p = join(root, sessionID + ".jsonl");
|
|
63668
|
+
files.set(sessionID, p);
|
|
63669
|
+
const seen = new Set();
|
|
63670
|
+
if (existsSync(p)) {
|
|
63671
|
+
// resumed session / plugin re-instantiated: learn what is already mirrored
|
|
63672
|
+
try {
|
|
63673
|
+
for (const line of readFileSync(p, "utf8").split("\\n")) {
|
|
63674
|
+
if (!line) continue;
|
|
63675
|
+
try { const o = JSON.parse(line); if (o && o.info && typeof o.info.id === "string") seen.add(o.info.id); } catch {}
|
|
63676
|
+
}
|
|
63677
|
+
} catch {}
|
|
63678
|
+
} else {
|
|
63679
|
+
appendFileSync(p, JSON.stringify({ type: HEADER_TYPE, version: 1, sessionID, directory, projectID, worktree: input.worktree || null, writtenAt: Date.now() }) + "\\n");
|
|
63680
|
+
}
|
|
63681
|
+
written.set(sessionID, seen);
|
|
63682
|
+
return p;
|
|
63683
|
+
}
|
|
63684
|
+
|
|
63685
|
+
async function mirror(sessionID) {
|
|
63686
|
+
// OpenCode's own record: session.messages() items are {info: Message, parts: Part[]}
|
|
63687
|
+
// (the same objects \`opencode export\` prints). Append only completed assistant
|
|
63688
|
+
// messages + every user message not yet mirrored; ids never repeat.
|
|
63689
|
+
const p = fileFor(sessionID);
|
|
63690
|
+
const seen = written.get(sessionID);
|
|
63691
|
+
let items = [];
|
|
63692
|
+
try {
|
|
63693
|
+
const r = await input.client.session.messages({ path: { id: sessionID } });
|
|
63694
|
+
items = (r && r.data) ? r.data : (Array.isArray(r) ? r : []);
|
|
63695
|
+
} catch { return { path: p, appended: 0 }; }
|
|
63696
|
+
let appended = 0;
|
|
63697
|
+
for (const item of items) {
|
|
63698
|
+
const info = item && item.info;
|
|
63699
|
+
if (!info || typeof info.id !== "string" || seen.has(info.id)) continue;
|
|
63700
|
+
if (info.role === "assistant" && !(info.time && info.time.completed)) continue; // still streaming
|
|
63701
|
+
try {
|
|
63702
|
+
appendFileSync(p, JSON.stringify({ info, parts: Array.isArray(item.parts) ? item.parts : [] }) + "\\n");
|
|
63703
|
+
seen.add(info.id); appended++;
|
|
63704
|
+
} catch {}
|
|
63705
|
+
}
|
|
63706
|
+
return { path: p, appended };
|
|
63707
|
+
}
|
|
63708
|
+
|
|
63709
|
+
const touched = new Set();
|
|
63710
|
+
return {
|
|
63711
|
+
// D1's text reaches OpenCode (\xA711.5 item 1): appended to the SYSTEM prompt
|
|
63712
|
+
// of every request \u2014 the same channel the Claude session-start hook uses
|
|
63713
|
+
// (system-level fires reliably; per-turn nudges measurably do not).
|
|
63714
|
+
// \`experimental.chat.system.transform\` is the hook the 1.18.18 plugin
|
|
63715
|
+
// API exposes for this ({sessionID?, model} \u2192 {system: string[]}).
|
|
63716
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
63717
|
+
try {
|
|
63718
|
+
if (output && Array.isArray(output.system) && !output.system.includes(D1_CONTRACT)) output.system.push(D1_CONTRACT);
|
|
63719
|
+
} catch {}
|
|
63720
|
+
},
|
|
63721
|
+
// Tool-failure nudge (\xA711.5 item 2): when a tool result looks failed, one
|
|
63722
|
+
// advisory line is appended to the OUTPUT the model reads. Failure is a
|
|
63723
|
+
// heuristic \u2014 \`metadata\` is untyped in the plugin API \u2014 pinned to the
|
|
63724
|
+
// shapes observed: metadata.error truthy, or a non-zero exit/exitCode.
|
|
63725
|
+
"tool.execute.after": async (_input, output) => {
|
|
63726
|
+
try {
|
|
63727
|
+
if (!output || typeof output.output !== "string") return;
|
|
63728
|
+
const meta = output.metadata;
|
|
63729
|
+
const failed = !!(meta && (meta.error || (typeof meta.exit === "number" && meta.exit !== 0) || (typeof meta.exitCode === "number" && meta.exitCode !== 0)));
|
|
63730
|
+
if (failed && !output.output.includes(TOOL_FAILURE_NUDGE)) output.output = output.output + "\\n\\n" + TOOL_FAILURE_NUDGE;
|
|
63731
|
+
} catch {}
|
|
63732
|
+
},
|
|
63733
|
+
event: async ({ event }) => {
|
|
63734
|
+
try {
|
|
63735
|
+
if (!event || typeof event.type !== "string") return;
|
|
63736
|
+
const sid = event.properties && event.properties.sessionID;
|
|
63737
|
+
if (event.type === "session.created" && sid) { fileFor(sid); touched.add(sid); return; }
|
|
63738
|
+
if (event.type === "session.idle" && sid) {
|
|
63739
|
+
const { path, appended } = await mirror(sid);
|
|
63740
|
+
touched.add(sid);
|
|
63741
|
+
// turn boundary \u2192 the daemon harvests the mirror (idempotent per turn)
|
|
63742
|
+
await post("/api/turn", { session_id: "opencode:" + sid, transcript_path: path, cwd: directory, hostHarness: "opencode", appended });
|
|
63743
|
+
}
|
|
63744
|
+
} catch {}
|
|
63745
|
+
},
|
|
63746
|
+
dispose: async () => {
|
|
63747
|
+
// OpenCode has no session-end event; process disposal is the closest.
|
|
63748
|
+
for (const sid of touched) {
|
|
63749
|
+
try {
|
|
63750
|
+
const { path } = await mirror(sid);
|
|
63751
|
+
await post("/api/session-end", { session_id: "opencode:" + sid, transcript_path: path, cwd: directory, reason: "opencode-dispose" });
|
|
63752
|
+
} catch {}
|
|
63753
|
+
}
|
|
63754
|
+
},
|
|
63755
|
+
};
|
|
63756
|
+
};
|
|
63757
|
+
`;
|
|
63758
|
+
}
|
|
63759
|
+
|
|
61655
63760
|
// src/cli.ts
|
|
61656
63761
|
var exitCleanOnEpipe = (err2) => {
|
|
61657
63762
|
if (err2.code === "EPIPE") process.exit(0);
|
|
@@ -61733,7 +63838,7 @@ async function main() {
|
|
|
61733
63838
|
case "install-hooks":
|
|
61734
63839
|
return cmdInstallHooks(rest);
|
|
61735
63840
|
case "mcp":
|
|
61736
|
-
return cmdMcp();
|
|
63841
|
+
return cmdMcp(rest);
|
|
61737
63842
|
case "sync":
|
|
61738
63843
|
return cmdSync(rest[0] ?? "now");
|
|
61739
63844
|
case "feedback":
|
|
@@ -61744,6 +63849,8 @@ async function main() {
|
|
|
61744
63849
|
return cmdConsent(rest);
|
|
61745
63850
|
case "notifications":
|
|
61746
63851
|
return cmdNotifications(rest[0] ?? "");
|
|
63852
|
+
case "ops-notices":
|
|
63853
|
+
return cmdOpsNotices(rest[0] ?? "");
|
|
61747
63854
|
case "update":
|
|
61748
63855
|
return cmdUpdate(rest);
|
|
61749
63856
|
case "projects":
|
|
@@ -61832,10 +63939,11 @@ Commands:
|
|
|
61832
63939
|
Written to .errata/report.html. Flag: --future-verbs
|
|
61833
63940
|
install-hooks <harness>
|
|
61834
63941
|
Wire harness hooks \u2192 daemon /api/hook (or MCP).
|
|
61835
|
-
<harness>: claude (default) | cursor | codex | aider
|
|
63942
|
+
<harness>: claude (default) | cursor | codex | opencode | aider
|
|
61836
63943
|
Flags: --port N (default 7891)
|
|
61837
|
-
mcp
|
|
61838
|
-
surface (navigation, problems, claims, burst, health)
|
|
63944
|
+
mcp [--grok] Run the MCP stdio server \u2014 the agent's full errata tool
|
|
63945
|
+
surface (navigation, problems, claims, burst, health).
|
|
63946
|
+
--grok emits bare tool names accepted by Grok Code.
|
|
61839
63947
|
login Sign in with the cloud. Default: short verification URL +
|
|
61840
63948
|
typeable code (device-bridged OAuth). Flags: --browser
|
|
61841
63949
|
(loopback code flow) \xB7 --token <key> \xB7
|
|
@@ -61867,6 +63975,9 @@ Commands:
|
|
|
61867
63975
|
Send scrubbed feedback to the maintainers (needs login)
|
|
61868
63976
|
notifications <on|off>
|
|
61869
63977
|
Desktop toasts on problem open/resolve + review ready
|
|
63978
|
+
ops-notices <on|off>
|
|
63979
|
+
Ops-grade health surfaces (daemon-distress toast + agent
|
|
63980
|
+
notice). Default off; for people operating errata itself
|
|
61870
63981
|
|
|
61871
63982
|
Environment:
|
|
61872
63983
|
ERRATA_CLOUD_URL Cloud base URL (default ${DEFAULT_CLOUD_URL})
|
|
@@ -63660,19 +65771,22 @@ async function cmdStop() {
|
|
|
63660
65771
|
}
|
|
63661
65772
|
}
|
|
63662
65773
|
}
|
|
63663
|
-
async function cmdMcp() {
|
|
65774
|
+
async function cmdMcp(args2) {
|
|
63664
65775
|
await ensureProfile();
|
|
63665
|
-
|
|
65776
|
+
const grokCompatibility = args2.includes("--grok");
|
|
65777
|
+
ensureSingletonRunning({ withoutPinnedConfig: grokCompatibility });
|
|
63666
65778
|
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
63667
|
-
await runMcpServer2(ROOT);
|
|
65779
|
+
await runMcpServer2(ROOT, { toolNameStyle: grokCompatibility ? "bare" : "prefixed" });
|
|
63668
65780
|
}
|
|
63669
65781
|
function selfArgv(sub) {
|
|
63670
65782
|
const self = process.execPath;
|
|
63671
65783
|
if (/errata(\.exe)?$/i.test(self)) return { cmd: self, args: [sub] };
|
|
63672
65784
|
return { cmd: self, args: [...process.execArgv, process.argv[1] ?? "", sub] };
|
|
63673
65785
|
}
|
|
63674
|
-
function spawnDaemonDetached() {
|
|
65786
|
+
function spawnDaemonDetached(options = {}) {
|
|
63675
65787
|
const { cmd: cmd2, args: args2 } = selfArgv("dash");
|
|
65788
|
+
const daemonEnv = { ...process.env };
|
|
65789
|
+
if (options.withoutPinnedConfig) delete daemonEnv["ERRATA_CONFIG_PATH"];
|
|
63676
65790
|
let out2 = "ignore";
|
|
63677
65791
|
try {
|
|
63678
65792
|
const logPath = daemonLogPath();
|
|
@@ -63694,20 +65808,20 @@ function spawnDaemonDetached() {
|
|
|
63694
65808
|
// a backstop, not a working limit. NODE_OPTIONS so it applies however the
|
|
63695
65809
|
// bundle re-executes.
|
|
63696
65810
|
env: {
|
|
63697
|
-
...
|
|
63698
|
-
NODE_OPTIONS: `${
|
|
65811
|
+
...daemonEnv,
|
|
65812
|
+
NODE_OPTIONS: `${daemonEnv["NODE_OPTIONS"] ?? ""} --max-old-space-size=${DAEMON_MAX_HEAP_MB}`.trim()
|
|
63699
65813
|
}
|
|
63700
65814
|
}).unref();
|
|
63701
65815
|
if (out2 !== "ignore") closeSync2(out2);
|
|
63702
65816
|
}
|
|
63703
|
-
function ensureSingletonRunning() {
|
|
65817
|
+
function ensureSingletonRunning(options = {}) {
|
|
63704
65818
|
try {
|
|
63705
65819
|
if (isDaemonAlive(globalDaemonLock())) return true;
|
|
63706
|
-
spawnDaemonDetached();
|
|
65820
|
+
spawnDaemonDetached(options);
|
|
63707
65821
|
void (async () => {
|
|
63708
65822
|
if (await waitForDaemon(globalDaemonLock(), 8e3)) return;
|
|
63709
65823
|
if (isDaemonAlive(globalDaemonLock())) return;
|
|
63710
|
-
spawnDaemonDetached();
|
|
65824
|
+
spawnDaemonDetached(options);
|
|
63711
65825
|
await waitForDaemon(globalDaemonLock(), 8e3);
|
|
63712
65826
|
})();
|
|
63713
65827
|
return true;
|
|
@@ -63729,28 +65843,66 @@ async function cmdInstallHooks(args2) {
|
|
|
63729
65843
|
case "codex":
|
|
63730
65844
|
await installCodexHooks(port);
|
|
63731
65845
|
return;
|
|
65846
|
+
case "opencode":
|
|
65847
|
+
await installOpencodePlugin(port);
|
|
65848
|
+
return;
|
|
63732
65849
|
case "aider":
|
|
63733
65850
|
printAiderInstructions();
|
|
63734
65851
|
return;
|
|
63735
65852
|
default:
|
|
63736
65853
|
console.error(`unknown harness: ${harness}`);
|
|
63737
|
-
console.error(` supported: claude, cursor, codex, aider`);
|
|
65854
|
+
console.error(` supported: claude, cursor, codex, opencode, aider`);
|
|
63738
65855
|
process.exit(2);
|
|
63739
65856
|
}
|
|
63740
65857
|
}
|
|
65858
|
+
async function installOpencodePlugin(port) {
|
|
65859
|
+
const { mkdirSync: mkdirSync9, existsSync: existsSync31, readFileSync: readFileSync29, writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
65860
|
+
const { join: join33 } = await import("node:path");
|
|
65861
|
+
const dir = join33(ROOT, ".opencode", "plugin");
|
|
65862
|
+
if (!existsSync31(dir)) mkdirSync9(dir, { recursive: true });
|
|
65863
|
+
const file2 = join33(dir, "inerrata.js");
|
|
65864
|
+
if (existsSync31(file2)) {
|
|
65865
|
+
const head2 = readFileSync29(file2, "utf8").split("\n")[0] ?? "";
|
|
65866
|
+
if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
|
|
65867
|
+
console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
|
|
65868
|
+
process.exit(2);
|
|
65869
|
+
}
|
|
65870
|
+
}
|
|
65871
|
+
writeFileSync23(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
|
|
65872
|
+
const cfgFile = join33(ROOT, "opencode.json");
|
|
65873
|
+
let cfg = { $schema: "https://opencode.ai/config.json" };
|
|
65874
|
+
if (existsSync31(cfgFile)) {
|
|
65875
|
+
try {
|
|
65876
|
+
cfg = JSON.parse(readFileSync29(cfgFile, "utf8"));
|
|
65877
|
+
} catch {
|
|
65878
|
+
console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
|
|
65879
|
+
process.exit(2);
|
|
65880
|
+
}
|
|
65881
|
+
}
|
|
65882
|
+
cfg.mcp ??= {};
|
|
65883
|
+
const hadErrata = "errata" in cfg.mcp;
|
|
65884
|
+
if (!hadErrata) {
|
|
65885
|
+
const inv = errataMcpInvocation();
|
|
65886
|
+
cfg.mcp["errata"] = { type: "local", command: [inv.command, ...inv.args] };
|
|
65887
|
+
writeFileSync23(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
65888
|
+
}
|
|
65889
|
+
console.log(`installed OpenCode plugin \u2192 ${file2}`);
|
|
65890
|
+
if (hadErrata) console.log(` mcp.errata already present in ${cfgFile} \u2014 left untouched`);
|
|
65891
|
+
else console.log(`installed OpenCode MCP server config \u2192 ${cfgFile} (mcp.errata: local \`errata mcp\`)`);
|
|
65892
|
+
console.log(` endpoint: http://127.0.0.1:${port}/api/turn on every session.idle (+ /api/session-end on dispose)`);
|
|
65893
|
+
console.log("");
|
|
65894
|
+
console.log(` OpenCode keeps sessions in SQLite, so the plugin mirrors each session's`);
|
|
65895
|
+
console.log(` own message record to $XDG_DATA_HOME/opencode/errata/<sessionID>.jsonl`);
|
|
65896
|
+
console.log(` (override: ERRATA_OPENCODE_SESSIONS_DIRS) and the daemon parses THAT \u2014`);
|
|
65897
|
+
console.log(` role-typed, attested. Works for headless \`opencode run\` too.`);
|
|
65898
|
+
console.log(` Cloud tools (search/contribute) are the MCP entry in opencode.json:`);
|
|
65899
|
+
console.log(` { "mcp": { "inerrata": { "type": "remote", "url": "https://mcp.inerrata.dev/mcp", "headers": { "Authorization": "Bearer <errk_\u2026>" } } } }`);
|
|
65900
|
+
}
|
|
63741
65901
|
var ERRATA_TAG = "# errata-managed";
|
|
63742
65902
|
function errataMcpInvocation() {
|
|
63743
65903
|
const { cmd: cmd2, args: args2 } = selfArgv("mcp");
|
|
63744
65904
|
return { command: cmd2, args: args2 };
|
|
63745
65905
|
}
|
|
63746
|
-
function hookCurlCommand(port, path2 = "/api/hook") {
|
|
63747
|
-
const url2 = `http://127.0.0.1:${port}${path2}`;
|
|
63748
|
-
return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
|
|
63749
|
-
}
|
|
63750
|
-
function hookRelayCommand(port, path2) {
|
|
63751
|
-
const url2 = `http://127.0.0.1:${port}${path2}`;
|
|
63752
|
-
return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
|
|
63753
|
-
}
|
|
63754
65906
|
async function installClaudeHooks(port) {
|
|
63755
65907
|
const { mkdirSync: mkdirSync9, existsSync: existsSync31, readFileSync: readFileSync29, writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
63756
65908
|
const { join: join33 } = await import("node:path");
|
|
@@ -63872,45 +66024,32 @@ async function installCodexHooks(port) {
|
|
|
63872
66024
|
const dir = join33(ROOT, ".codex");
|
|
63873
66025
|
if (!existsSync31(dir)) mkdirSync9(dir, { recursive: true });
|
|
63874
66026
|
const file2 = join33(dir, "config.toml");
|
|
63875
|
-
const BEGIN = `# >>> errata hooks (errata-managed)`;
|
|
63876
|
-
const END = `# <<< errata hooks`;
|
|
63877
66027
|
let existing = "";
|
|
63878
66028
|
if (existsSync31(file2)) {
|
|
63879
66029
|
existing = readFileSync29(file2, "utf8");
|
|
63880
|
-
const beginIdx = existing.indexOf(
|
|
63881
|
-
const endIdx = existing.indexOf(
|
|
66030
|
+
const beginIdx = existing.indexOf(CODEX_HOOKS_BEGIN);
|
|
66031
|
+
const endIdx = existing.indexOf(CODEX_HOOKS_END);
|
|
63882
66032
|
if (beginIdx >= 0 && endIdx > beginIdx) {
|
|
63883
|
-
existing = existing.slice(0, beginIdx).trimEnd() + existing.slice(endIdx +
|
|
66033
|
+
existing = existing.slice(0, beginIdx).trimEnd() + existing.slice(endIdx + CODEX_HOOKS_END.length).trimStart();
|
|
63884
66034
|
}
|
|
63885
66035
|
}
|
|
63886
|
-
const
|
|
63887
|
-
const block = `${BEGIN}
|
|
63888
|
-
[[hooks.PreToolUse]]
|
|
63889
|
-
matcher = ".*"
|
|
63890
|
-
|
|
63891
|
-
[[hooks.PreToolUse.hooks]]
|
|
63892
|
-
type = "command"
|
|
63893
|
-
command = "${cmd2}"
|
|
63894
|
-
timeout = 10
|
|
63895
|
-
|
|
63896
|
-
[[hooks.PostToolUse]]
|
|
63897
|
-
matcher = ".*"
|
|
63898
|
-
|
|
63899
|
-
[[hooks.PostToolUse.hooks]]
|
|
63900
|
-
type = "command"
|
|
63901
|
-
command = "${cmd2}"
|
|
63902
|
-
timeout = 10
|
|
63903
|
-
${END}
|
|
63904
|
-
`;
|
|
66036
|
+
const block = buildCodexHooksToml(port);
|
|
63905
66037
|
const final = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
|
|
63906
66038
|
|
|
63907
66039
|
${block}` : existing + (existing.endsWith("\n\n") ? "" : "\n") + block;
|
|
63908
66040
|
writeFileSync23(file2, final, "utf8");
|
|
63909
66041
|
console.log(`installed Codex hooks \u2192 ${file2}`);
|
|
63910
|
-
console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
|
|
66042
|
+
console.log(` endpoint: http://127.0.0.1:${port}/api/hook (+ /api/turn on PostToolUse)`);
|
|
63911
66043
|
console.log("");
|
|
63912
66044
|
console.log(` Codex reads files via the shell, so the virtual graph surface`);
|
|
63913
66045
|
console.log(` works by \`cat .errata/g/<verb>/<seed>\` (e.g. cat .errata/g/burst/foo).`);
|
|
66046
|
+
console.log("");
|
|
66047
|
+
console.log(` Codex has no Stop/SessionEnd hook at this installed version, so`);
|
|
66048
|
+
console.log(` [!\u2026]/(fix:[\u2026]) tags are harvested via the PostToolUse-triggered`);
|
|
66049
|
+
console.log(` /api/turn call above instead \u2014 every turn with a tool call is`);
|
|
66050
|
+
console.log(` covered; a tool-less final turn is picked up on this workspace's`);
|
|
66051
|
+
console.log(` next daemon boot replay. Re-run this after a Codex upgrade in`);
|
|
66052
|
+
console.log(` case a newer version adds a real turn-boundary hook.`);
|
|
63914
66053
|
console.log(` If [features] hooks=false in your config.toml, set it true. Hook`);
|
|
63915
66054
|
console.log(` schemas evolve \u2014 if hooks stop firing after an update, re-run this.`);
|
|
63916
66055
|
}
|
|
@@ -64413,6 +66552,19 @@ async function cmdNotifications(state) {
|
|
|
64413
66552
|
saveConfig(cfg);
|
|
64414
66553
|
console.log(`desktop notifications ${cfg.notifications ? "enabled" : "disabled"}`);
|
|
64415
66554
|
}
|
|
66555
|
+
async function cmdOpsNotices(state) {
|
|
66556
|
+
const s = state.toLowerCase();
|
|
66557
|
+
if (s !== "on" && s !== "off") {
|
|
66558
|
+
console.error("usage: errata ops-notices <on|off>");
|
|
66559
|
+
process.exit(2);
|
|
66560
|
+
}
|
|
66561
|
+
const cfg = loadConfig();
|
|
66562
|
+
cfg.opsNotices = s === "on";
|
|
66563
|
+
saveConfig(cfg);
|
|
66564
|
+
console.log(
|
|
66565
|
+
`ops health notices ${cfg.opsNotices ? "enabled \u2014 distress conditions will toast and reach the agent context" : "disabled \u2014 detection and `errata status` still run"}`
|
|
66566
|
+
);
|
|
66567
|
+
}
|
|
64416
66568
|
async function cmdFeedback(args2) {
|
|
64417
66569
|
const cfg = loadConfig();
|
|
64418
66570
|
if (!hasCloudCredential(cfg)) {
|