opencode-swarm 7.67.0 → 7.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +997 -482
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/learning.d.ts +1 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/config/evidence-schema.d.ts +214 -0
- package/dist/hooks/curator.d.ts +4 -1
- package/dist/hooks/skill-usage-log.d.ts +10 -0
- package/dist/index.js +3243 -2323
- package/dist/mutation/engine.d.ts +12 -1
- package/dist/services/learning-metrics.d.ts +63 -0
- package/dist/services/skill-changelog.d.ts +21 -0
- package/dist/services/skill-generator.d.ts +7 -1
- package/dist/services/skill-reviser.d.ts +59 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "opencode-swarm",
|
|
55
|
-
version: "7.
|
|
55
|
+
version: "7.68.0",
|
|
56
56
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
57
57
|
main: "dist/index.js",
|
|
58
58
|
types: "dist/index.d.ts",
|
|
@@ -19221,7 +19221,7 @@ var init_loader = __esm(() => {
|
|
|
19221
19221
|
});
|
|
19222
19222
|
|
|
19223
19223
|
// src/config/evidence-schema.ts
|
|
19224
|
-
var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
|
|
19224
|
+
var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, GateEvidenceBaseSchema, MutationGateEvidenceSchema, DriftVerificationEvidenceSchema, HallucinationVerificationEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
|
|
19225
19225
|
var init_evidence_schema = __esm(() => {
|
|
19226
19226
|
init_zod();
|
|
19227
19227
|
EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
|
|
@@ -19240,11 +19240,16 @@ var init_evidence_schema = __esm(() => {
|
|
|
19240
19240
|
"sbom",
|
|
19241
19241
|
"build",
|
|
19242
19242
|
"quality_budget",
|
|
19243
|
-
"secretscan"
|
|
19243
|
+
"secretscan",
|
|
19244
|
+
"mutation-gate",
|
|
19245
|
+
"drift-verification",
|
|
19246
|
+
"hallucination-verification"
|
|
19244
19247
|
]);
|
|
19245
19248
|
EvidenceVerdictSchema = exports_external.enum([
|
|
19246
19249
|
"pass",
|
|
19247
19250
|
"fail",
|
|
19251
|
+
"warn",
|
|
19252
|
+
"skip",
|
|
19248
19253
|
"approved",
|
|
19249
19254
|
"rejected",
|
|
19250
19255
|
"info"
|
|
@@ -19450,6 +19455,30 @@ var init_evidence_schema = __esm(() => {
|
|
|
19450
19455
|
files_scanned: exports_external.number().int().min(0).default(0),
|
|
19451
19456
|
skipped_files: exports_external.number().int().min(0).default(0)
|
|
19452
19457
|
});
|
|
19458
|
+
GateEvidenceBaseSchema = exports_external.object({
|
|
19459
|
+
task_id: exports_external.string().optional(),
|
|
19460
|
+
agent: exports_external.string().optional(),
|
|
19461
|
+
timestamp: exports_external.string().datetime(),
|
|
19462
|
+
summary: exports_external.string().min(1),
|
|
19463
|
+
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
19464
|
+
});
|
|
19465
|
+
MutationGateEvidenceSchema = GateEvidenceBaseSchema.extend({
|
|
19466
|
+
type: exports_external.literal("mutation-gate"),
|
|
19467
|
+
verdict: exports_external.enum(["pass", "warn", "fail", "skip"]),
|
|
19468
|
+
killRate: exports_external.number().min(0).max(1).optional(),
|
|
19469
|
+
adjustedKillRate: exports_external.number().min(0).max(1).optional(),
|
|
19470
|
+
survivedMutants: exports_external.string().optional()
|
|
19471
|
+
});
|
|
19472
|
+
DriftVerificationEvidenceSchema = GateEvidenceBaseSchema.extend({
|
|
19473
|
+
type: exports_external.literal("drift-verification"),
|
|
19474
|
+
verdict: exports_external.enum(["approved", "rejected"]),
|
|
19475
|
+
requirementCoverage: exports_external.string().optional()
|
|
19476
|
+
});
|
|
19477
|
+
HallucinationVerificationEvidenceSchema = GateEvidenceBaseSchema.extend({
|
|
19478
|
+
type: exports_external.literal("hallucination-verification"),
|
|
19479
|
+
verdict: exports_external.enum(["approved", "rejected"]),
|
|
19480
|
+
findings: exports_external.string().optional()
|
|
19481
|
+
});
|
|
19453
19482
|
EvidenceSchema = exports_external.discriminatedUnion("type", [
|
|
19454
19483
|
ReviewEvidenceSchema,
|
|
19455
19484
|
TestEvidenceSchema,
|
|
@@ -19463,7 +19492,10 @@ var init_evidence_schema = __esm(() => {
|
|
|
19463
19492
|
SbomEvidenceSchema,
|
|
19464
19493
|
BuildEvidenceSchema,
|
|
19465
19494
|
QualityBudgetEvidenceSchema,
|
|
19466
|
-
SecretscanEvidenceSchema
|
|
19495
|
+
SecretscanEvidenceSchema,
|
|
19496
|
+
MutationGateEvidenceSchema,
|
|
19497
|
+
DriftVerificationEvidenceSchema,
|
|
19498
|
+
HallucinationVerificationEvidenceSchema
|
|
19467
19499
|
]);
|
|
19468
19500
|
EvidenceBundleSchema = exports_external.object({
|
|
19469
19501
|
schema_version: exports_external.literal("1.0.0"),
|
|
@@ -36950,14 +36982,217 @@ var init_event_bus = __esm(() => {
|
|
|
36950
36982
|
init_utils();
|
|
36951
36983
|
});
|
|
36952
36984
|
|
|
36985
|
+
// src/hooks/knowledge-events.ts
|
|
36986
|
+
import { existsSync as existsSync9 } from "fs";
|
|
36987
|
+
import { appendFile as appendFile2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
36988
|
+
import * as path12 from "path";
|
|
36989
|
+
function resolveKnowledgeEventsPath(directory) {
|
|
36990
|
+
return path12.join(directory, ".swarm", "knowledge-events.jsonl");
|
|
36991
|
+
}
|
|
36992
|
+
function resolveLegacyApplicationLogPath(directory) {
|
|
36993
|
+
return path12.join(directory, ".swarm", "knowledge-application.jsonl");
|
|
36994
|
+
}
|
|
36995
|
+
async function readKnowledgeEvents(directory) {
|
|
36996
|
+
const filePath = resolveKnowledgeEventsPath(directory);
|
|
36997
|
+
if (!existsSync9(filePath))
|
|
36998
|
+
return [];
|
|
36999
|
+
const content = await readFile2(filePath, "utf-8");
|
|
37000
|
+
const out = [];
|
|
37001
|
+
for (const line of content.split(`
|
|
37002
|
+
`)) {
|
|
37003
|
+
const trimmed = line.trim();
|
|
37004
|
+
if (!trimmed)
|
|
37005
|
+
continue;
|
|
37006
|
+
try {
|
|
37007
|
+
out.push(JSON.parse(trimmed));
|
|
37008
|
+
} catch {
|
|
37009
|
+
warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
|
|
37010
|
+
}
|
|
37011
|
+
}
|
|
37012
|
+
return out;
|
|
37013
|
+
}
|
|
37014
|
+
async function readLegacyApplicationRecords(directory) {
|
|
37015
|
+
const filePath = resolveLegacyApplicationLogPath(directory);
|
|
37016
|
+
if (!existsSync9(filePath))
|
|
37017
|
+
return [];
|
|
37018
|
+
const content = await readFile2(filePath, "utf-8");
|
|
37019
|
+
const out = [];
|
|
37020
|
+
for (const line of content.split(`
|
|
37021
|
+
`)) {
|
|
37022
|
+
const trimmed = line.trim();
|
|
37023
|
+
if (!trimmed)
|
|
37024
|
+
continue;
|
|
37025
|
+
try {
|
|
37026
|
+
out.push(JSON.parse(trimmed));
|
|
37027
|
+
} catch {
|
|
37028
|
+
warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
|
|
37029
|
+
}
|
|
37030
|
+
}
|
|
37031
|
+
return out;
|
|
37032
|
+
}
|
|
37033
|
+
function emptyRollup() {
|
|
37034
|
+
return {
|
|
37035
|
+
shown_count: 0,
|
|
37036
|
+
acknowledged_count: 0,
|
|
37037
|
+
applied_explicit_count: 0,
|
|
37038
|
+
ignored_count: 0,
|
|
37039
|
+
violated_count: 0,
|
|
37040
|
+
contradicted_count: 0,
|
|
37041
|
+
n_a_count: 0,
|
|
37042
|
+
succeeded_after_shown_count: 0,
|
|
37043
|
+
failed_after_shown_count: 0,
|
|
37044
|
+
partial_after_shown_count: 0,
|
|
37045
|
+
violation_timestamps: []
|
|
37046
|
+
};
|
|
37047
|
+
}
|
|
37048
|
+
function get(map3, id) {
|
|
37049
|
+
let r = map3.get(id);
|
|
37050
|
+
if (!r) {
|
|
37051
|
+
r = emptyRollup();
|
|
37052
|
+
map3.set(id, r);
|
|
37053
|
+
}
|
|
37054
|
+
return r;
|
|
37055
|
+
}
|
|
37056
|
+
function maxIso(current, candidate) {
|
|
37057
|
+
if (!current)
|
|
37058
|
+
return candidate;
|
|
37059
|
+
return candidate > current ? candidate : current;
|
|
37060
|
+
}
|
|
37061
|
+
function recomputeCounters(events, legacyRecords = []) {
|
|
37062
|
+
const map3 = new Map;
|
|
37063
|
+
const retrievedIds = new Set;
|
|
37064
|
+
for (const e of events) {
|
|
37065
|
+
switch (e.type) {
|
|
37066
|
+
case "retrieved": {
|
|
37067
|
+
for (const id of e.result_ids) {
|
|
37068
|
+
retrievedIds.add(id);
|
|
37069
|
+
get(map3, id).shown_count += 1;
|
|
37070
|
+
}
|
|
37071
|
+
break;
|
|
37072
|
+
}
|
|
37073
|
+
case "acknowledged": {
|
|
37074
|
+
const r = get(map3, e.knowledge_id);
|
|
37075
|
+
r.acknowledged_count += 1;
|
|
37076
|
+
r.last_acknowledged_at = maxIso(r.last_acknowledged_at, e.timestamp);
|
|
37077
|
+
break;
|
|
37078
|
+
}
|
|
37079
|
+
case "applied": {
|
|
37080
|
+
const r = get(map3, e.knowledge_id);
|
|
37081
|
+
r.applied_explicit_count += 1;
|
|
37082
|
+
r.last_applied_at = maxIso(r.last_applied_at, e.timestamp);
|
|
37083
|
+
break;
|
|
37084
|
+
}
|
|
37085
|
+
case "ignored":
|
|
37086
|
+
get(map3, e.knowledge_id).ignored_count += 1;
|
|
37087
|
+
break;
|
|
37088
|
+
case "violated": {
|
|
37089
|
+
const r = get(map3, e.knowledge_id);
|
|
37090
|
+
r.violated_count += 1;
|
|
37091
|
+
r.violation_timestamps.push(e.timestamp);
|
|
37092
|
+
break;
|
|
37093
|
+
}
|
|
37094
|
+
case "contradicted":
|
|
37095
|
+
get(map3, e.knowledge_id).contradicted_count += 1;
|
|
37096
|
+
break;
|
|
37097
|
+
case "n_a":
|
|
37098
|
+
get(map3, e.knowledge_id).n_a_count += 1;
|
|
37099
|
+
break;
|
|
37100
|
+
case "outcome": {
|
|
37101
|
+
if (!e.knowledge_id)
|
|
37102
|
+
break;
|
|
37103
|
+
const r = get(map3, e.knowledge_id);
|
|
37104
|
+
if (e.outcome === "success")
|
|
37105
|
+
r.succeeded_after_shown_count += 1;
|
|
37106
|
+
else if (e.outcome === "failure")
|
|
37107
|
+
r.failed_after_shown_count += 1;
|
|
37108
|
+
else if (e.outcome === "partial")
|
|
37109
|
+
r.partial_after_shown_count += 1;
|
|
37110
|
+
break;
|
|
37111
|
+
}
|
|
37112
|
+
}
|
|
37113
|
+
}
|
|
37114
|
+
for (const rec of legacyRecords) {
|
|
37115
|
+
const r = get(map3, rec.knowledgeId);
|
|
37116
|
+
switch (rec.result) {
|
|
37117
|
+
case "shown":
|
|
37118
|
+
if (!retrievedIds.has(rec.knowledgeId))
|
|
37119
|
+
r.shown_count += 1;
|
|
37120
|
+
break;
|
|
37121
|
+
case "acknowledged":
|
|
37122
|
+
r.acknowledged_count += 1;
|
|
37123
|
+
r.last_acknowledged_at = maxIso(r.last_acknowledged_at, rec.timestamp);
|
|
37124
|
+
break;
|
|
37125
|
+
case "applied":
|
|
37126
|
+
r.applied_explicit_count += 1;
|
|
37127
|
+
r.last_applied_at = maxIso(r.last_applied_at, rec.timestamp);
|
|
37128
|
+
break;
|
|
37129
|
+
case "ignored":
|
|
37130
|
+
r.ignored_count += 1;
|
|
37131
|
+
break;
|
|
37132
|
+
case "violated":
|
|
37133
|
+
r.violated_count += 1;
|
|
37134
|
+
r.violation_timestamps.push(rec.timestamp);
|
|
37135
|
+
break;
|
|
37136
|
+
}
|
|
37137
|
+
}
|
|
37138
|
+
for (const r of map3.values()) {
|
|
37139
|
+
if (r.violation_timestamps.length > 1) {
|
|
37140
|
+
r.violation_timestamps.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
|
|
37141
|
+
}
|
|
37142
|
+
if (r.violation_timestamps.length > MAX_VIOLATION_TIMESTAMPS) {
|
|
37143
|
+
r.violation_timestamps = r.violation_timestamps.slice(0, MAX_VIOLATION_TIMESTAMPS);
|
|
37144
|
+
}
|
|
37145
|
+
}
|
|
37146
|
+
return map3;
|
|
37147
|
+
}
|
|
37148
|
+
async function readKnowledgeCounterRollups(directory) {
|
|
37149
|
+
try {
|
|
37150
|
+
const [events, legacyRecords] = await Promise.all([
|
|
37151
|
+
readKnowledgeEvents(directory),
|
|
37152
|
+
readLegacyApplicationRecords(directory)
|
|
37153
|
+
]);
|
|
37154
|
+
return recomputeCounters(events, legacyRecords);
|
|
37155
|
+
} catch (err) {
|
|
37156
|
+
warn(`[knowledge-events] readKnowledgeCounterRollups failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
37157
|
+
return new Map;
|
|
37158
|
+
}
|
|
37159
|
+
}
|
|
37160
|
+
function effectiveRetrievalOutcomes(stored, rollup) {
|
|
37161
|
+
const base = stored ?? {
|
|
37162
|
+
applied_count: 0,
|
|
37163
|
+
succeeded_after_count: 0,
|
|
37164
|
+
failed_after_count: 0
|
|
37165
|
+
};
|
|
37166
|
+
if (!rollup)
|
|
37167
|
+
return base;
|
|
37168
|
+
return {
|
|
37169
|
+
...base,
|
|
37170
|
+
...rollup
|
|
37171
|
+
};
|
|
37172
|
+
}
|
|
37173
|
+
var import_proper_lockfile3, RECEIPT_EVENT_TYPES, MAX_VIOLATION_TIMESTAMPS = 10;
|
|
37174
|
+
var init_knowledge_events = __esm(() => {
|
|
37175
|
+
init_logger();
|
|
37176
|
+
import_proper_lockfile3 = __toESM(require_proper_lockfile(), 1);
|
|
37177
|
+
RECEIPT_EVENT_TYPES = new Set([
|
|
37178
|
+
"acknowledged",
|
|
37179
|
+
"applied",
|
|
37180
|
+
"ignored",
|
|
37181
|
+
"contradicted",
|
|
37182
|
+
"violated",
|
|
37183
|
+
"n_a",
|
|
37184
|
+
"override"
|
|
37185
|
+
]);
|
|
37186
|
+
});
|
|
37187
|
+
|
|
36953
37188
|
// src/evidence/task-file.ts
|
|
36954
37189
|
import { renameSync as renameSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
36955
|
-
import * as
|
|
37190
|
+
import * as path13 from "path";
|
|
36956
37191
|
function taskEvidenceRelPath(taskId) {
|
|
36957
|
-
return
|
|
37192
|
+
return path13.join("evidence", `${taskId}.json`);
|
|
36958
37193
|
}
|
|
36959
37194
|
function taskEvidencePath(directory, taskId) {
|
|
36960
|
-
return
|
|
37195
|
+
return path13.join(directory, ".swarm", taskEvidenceRelPath(taskId));
|
|
36961
37196
|
}
|
|
36962
37197
|
async function atomicWriteFile(targetPath, content) {
|
|
36963
37198
|
const tempPath = `${targetPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
@@ -36981,40 +37216,40 @@ var init_task_file = __esm(() => {
|
|
|
36981
37216
|
});
|
|
36982
37217
|
|
|
36983
37218
|
// src/hooks/knowledge-store.ts
|
|
36984
|
-
import { existsSync as
|
|
36985
|
-
import { appendFile as
|
|
37219
|
+
import { existsSync as existsSync10 } from "fs";
|
|
37220
|
+
import { appendFile as appendFile3, mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
|
|
36986
37221
|
import * as os4 from "os";
|
|
36987
|
-
import * as
|
|
37222
|
+
import * as path14 from "path";
|
|
36988
37223
|
function resolveSwarmKnowledgePath(directory) {
|
|
36989
|
-
return
|
|
37224
|
+
return path14.join(directory, ".swarm", "knowledge.jsonl");
|
|
36990
37225
|
}
|
|
36991
37226
|
function resolveSwarmRejectedPath(directory) {
|
|
36992
|
-
return
|
|
37227
|
+
return path14.join(directory, ".swarm", "knowledge-rejected.jsonl");
|
|
36993
37228
|
}
|
|
36994
37229
|
function resolveSwarmRetractionsPath(directory) {
|
|
36995
|
-
return
|
|
37230
|
+
return path14.join(directory, ".swarm", "knowledge-retractions.jsonl");
|
|
36996
37231
|
}
|
|
36997
37232
|
function resolveHiveKnowledgePath() {
|
|
36998
37233
|
const platform = process.platform;
|
|
36999
37234
|
const home = process.env.HOME || os4.homedir();
|
|
37000
37235
|
let dataDir;
|
|
37001
37236
|
if (platform === "win32") {
|
|
37002
|
-
dataDir =
|
|
37237
|
+
dataDir = path14.join(process.env.LOCALAPPDATA || path14.join(home, "AppData", "Local"), "opencode-swarm", "Data");
|
|
37003
37238
|
} else if (platform === "darwin") {
|
|
37004
|
-
dataDir =
|
|
37239
|
+
dataDir = path14.join(home, "Library", "Application Support", "opencode-swarm");
|
|
37005
37240
|
} else {
|
|
37006
|
-
dataDir =
|
|
37241
|
+
dataDir = path14.join(process.env.XDG_DATA_HOME || path14.join(home, ".local", "share"), "opencode-swarm");
|
|
37007
37242
|
}
|
|
37008
|
-
return
|
|
37243
|
+
return path14.join(dataDir, "shared-learnings.jsonl");
|
|
37009
37244
|
}
|
|
37010
37245
|
function resolveHiveRejectedPath() {
|
|
37011
37246
|
const hivePath = resolveHiveKnowledgePath();
|
|
37012
|
-
return
|
|
37247
|
+
return path14.join(path14.dirname(hivePath), "shared-learnings-rejected.jsonl");
|
|
37013
37248
|
}
|
|
37014
37249
|
async function readKnowledge(filePath) {
|
|
37015
|
-
if (!
|
|
37250
|
+
if (!existsSync10(filePath))
|
|
37016
37251
|
return [];
|
|
37017
|
-
const content = await
|
|
37252
|
+
const content = await readFile3(filePath, "utf-8");
|
|
37018
37253
|
const results = [];
|
|
37019
37254
|
for (const line of content.split(`
|
|
37020
37255
|
`)) {
|
|
@@ -37106,15 +37341,15 @@ async function appendRetractionRecord(directory, record3) {
|
|
|
37106
37341
|
await appendKnowledge(resolveSwarmRetractionsPath(directory), record3);
|
|
37107
37342
|
}
|
|
37108
37343
|
async function appendKnowledge(filePath, entry) {
|
|
37109
|
-
const dir =
|
|
37110
|
-
await
|
|
37344
|
+
const dir = path14.dirname(filePath);
|
|
37345
|
+
await mkdir3(dir, { recursive: true });
|
|
37111
37346
|
let release = null;
|
|
37112
37347
|
try {
|
|
37113
|
-
release = await
|
|
37348
|
+
release = await import_proper_lockfile4.default.lock(dir, {
|
|
37114
37349
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37115
37350
|
stale: 5000
|
|
37116
37351
|
});
|
|
37117
|
-
await
|
|
37352
|
+
await appendFile3(filePath, `${JSON.stringify(entry)}
|
|
37118
37353
|
`, "utf-8");
|
|
37119
37354
|
} finally {
|
|
37120
37355
|
if (release) {
|
|
@@ -37125,11 +37360,11 @@ async function appendKnowledge(filePath, entry) {
|
|
|
37125
37360
|
}
|
|
37126
37361
|
}
|
|
37127
37362
|
async function rewriteKnowledge(filePath, entries) {
|
|
37128
|
-
const dir =
|
|
37129
|
-
await
|
|
37363
|
+
const dir = path14.dirname(filePath);
|
|
37364
|
+
await mkdir3(dir, { recursive: true });
|
|
37130
37365
|
let release = null;
|
|
37131
37366
|
try {
|
|
37132
|
-
release = await
|
|
37367
|
+
release = await import_proper_lockfile4.default.lock(dir, {
|
|
37133
37368
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37134
37369
|
stale: 5000
|
|
37135
37370
|
});
|
|
@@ -37146,15 +37381,15 @@ async function rewriteKnowledge(filePath, entries) {
|
|
|
37146
37381
|
}
|
|
37147
37382
|
}
|
|
37148
37383
|
async function transactFile(filePath, read, write, mutate) {
|
|
37149
|
-
const dir =
|
|
37384
|
+
const dir = path14.dirname(filePath);
|
|
37150
37385
|
try {
|
|
37151
|
-
await
|
|
37386
|
+
await mkdir3(dir, { recursive: true });
|
|
37152
37387
|
} catch {
|
|
37153
37388
|
return false;
|
|
37154
37389
|
}
|
|
37155
37390
|
let release = null;
|
|
37156
37391
|
try {
|
|
37157
|
-
release = await
|
|
37392
|
+
release = await import_proper_lockfile4.default.lock(dir, {
|
|
37158
37393
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37159
37394
|
stale: 5000
|
|
37160
37395
|
});
|
|
@@ -37301,9 +37536,9 @@ async function applyConfidenceDeltas(filePath, deltas) {
|
|
|
37301
37536
|
}
|
|
37302
37537
|
let release = null;
|
|
37303
37538
|
try {
|
|
37304
|
-
const dir =
|
|
37305
|
-
await
|
|
37306
|
-
release = await
|
|
37539
|
+
const dir = path14.dirname(filePath);
|
|
37540
|
+
await mkdir3(dir, { recursive: true });
|
|
37541
|
+
release = await import_proper_lockfile4.default.lock(dir, {
|
|
37307
37542
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37308
37543
|
stale: 5000
|
|
37309
37544
|
});
|
|
@@ -37336,15 +37571,432 @@ async function applyConfidenceDeltas(filePath, deltas) {
|
|
|
37336
37571
|
}
|
|
37337
37572
|
}
|
|
37338
37573
|
}
|
|
37339
|
-
var
|
|
37574
|
+
var import_proper_lockfile4, OUTCOME_SIGNAL_SMOOTHING = 4, CONFIDENCE_FLOOR = 0.1, CONFIDENCE_CEILING = 1;
|
|
37340
37575
|
var init_knowledge_store = __esm(() => {
|
|
37341
37576
|
init_task_file();
|
|
37342
|
-
|
|
37577
|
+
import_proper_lockfile4 = __toESM(require_proper_lockfile(), 1);
|
|
37578
|
+
});
|
|
37579
|
+
|
|
37580
|
+
// src/hooks/knowledge-escalator.ts
|
|
37581
|
+
async function readRecentEscalations(directory, windowDays = ESCALATION_DISPLAY_WINDOW_DAYS, now = new Date) {
|
|
37582
|
+
try {
|
|
37583
|
+
const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;
|
|
37584
|
+
const events = await readKnowledgeEvents(directory);
|
|
37585
|
+
const out = [];
|
|
37586
|
+
for (const e of events) {
|
|
37587
|
+
if (e.type !== "escalation")
|
|
37588
|
+
continue;
|
|
37589
|
+
const t = Date.parse(e.timestamp);
|
|
37590
|
+
if (Number.isNaN(t) || t < cutoff)
|
|
37591
|
+
continue;
|
|
37592
|
+
out.push({
|
|
37593
|
+
entry_id: e.entry_id,
|
|
37594
|
+
from: e.from,
|
|
37595
|
+
to: e.to,
|
|
37596
|
+
reason: e.reason,
|
|
37597
|
+
at: e.timestamp
|
|
37598
|
+
});
|
|
37599
|
+
}
|
|
37600
|
+
out.sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
|
|
37601
|
+
return out;
|
|
37602
|
+
} catch {
|
|
37603
|
+
return [];
|
|
37604
|
+
}
|
|
37605
|
+
}
|
|
37606
|
+
var ESCALATION_DISPLAY_WINDOW_DAYS = 7;
|
|
37607
|
+
var init_knowledge_escalator = __esm(() => {
|
|
37608
|
+
init_knowledge_events();
|
|
37609
|
+
init_knowledge_store();
|
|
37610
|
+
});
|
|
37611
|
+
|
|
37612
|
+
// src/services/learning-metrics.ts
|
|
37613
|
+
function safeDivide(numerator, denominator) {
|
|
37614
|
+
return denominator === 0 ? 0 : numerator / denominator;
|
|
37615
|
+
}
|
|
37616
|
+
function truncateLesson(lesson) {
|
|
37617
|
+
if (lesson.length <= MAX_LESSON_DISPLAY_CHARS)
|
|
37618
|
+
return lesson;
|
|
37619
|
+
return `${lesson.slice(0, MAX_LESSON_DISPLAY_CHARS - 3)}...`;
|
|
37620
|
+
}
|
|
37621
|
+
function isReceiptType(event) {
|
|
37622
|
+
return event.type === "acknowledged" || event.type === "applied" || event.type === "ignored" || event.type === "contradicted" || event.type === "violated" || event.type === "n_a" || event.type === "override";
|
|
37623
|
+
}
|
|
37624
|
+
function countWindowedReceipts(events, entryId, windowMs, nowMs) {
|
|
37625
|
+
const cutoff = nowMs - windowMs;
|
|
37626
|
+
let violations = 0;
|
|
37627
|
+
let totalReceipts = 0;
|
|
37628
|
+
for (const e of events) {
|
|
37629
|
+
if (!isReceiptType(e))
|
|
37630
|
+
continue;
|
|
37631
|
+
if (e.knowledge_id !== entryId)
|
|
37632
|
+
continue;
|
|
37633
|
+
const t = Date.parse(e.timestamp);
|
|
37634
|
+
if (Number.isNaN(t) || t < cutoff)
|
|
37635
|
+
continue;
|
|
37636
|
+
totalReceipts++;
|
|
37637
|
+
if (e.type === "violated")
|
|
37638
|
+
violations++;
|
|
37639
|
+
}
|
|
37640
|
+
return { violations, totalReceipts };
|
|
37641
|
+
}
|
|
37642
|
+
function determineTrend(rate7d, rate30d, hasData) {
|
|
37643
|
+
if (!hasData)
|
|
37644
|
+
return "no_data";
|
|
37645
|
+
if (rate7d < rate30d)
|
|
37646
|
+
return "improving";
|
|
37647
|
+
if (rate7d > rate30d)
|
|
37648
|
+
return "worsening";
|
|
37649
|
+
return "stable";
|
|
37650
|
+
}
|
|
37651
|
+
function classifyROI(rollup) {
|
|
37652
|
+
if (rollup.applied_explicit_count > 0 && rollup.succeeded_after_shown_count > rollup.failed_after_shown_count) {
|
|
37653
|
+
return "high";
|
|
37654
|
+
}
|
|
37655
|
+
if (rollup.applied_explicit_count > 0) {
|
|
37656
|
+
return "medium";
|
|
37657
|
+
}
|
|
37658
|
+
if (rollup.shown_count > 0) {
|
|
37659
|
+
return "low";
|
|
37660
|
+
}
|
|
37661
|
+
return "unused";
|
|
37662
|
+
}
|
|
37663
|
+
async function computeLearningMetrics(directory, options) {
|
|
37664
|
+
const now = options?.now ?? new Date;
|
|
37665
|
+
const nowMs = now.getTime();
|
|
37666
|
+
const phasesThreshold = options?.currentPhase ?? DEFAULT_PHASES_ALIVE_THRESHOLD;
|
|
37667
|
+
const [events, entries] = await Promise.all([
|
|
37668
|
+
readKnowledgeEvents(directory),
|
|
37669
|
+
readKnowledge(resolveSwarmKnowledgePath(directory))
|
|
37670
|
+
]);
|
|
37671
|
+
if (events.length === 0 && entries.length === 0) {
|
|
37672
|
+
return emptyMetrics();
|
|
37673
|
+
}
|
|
37674
|
+
const rollups = recomputeCounters(events);
|
|
37675
|
+
const entryMap = new Map;
|
|
37676
|
+
for (const entry of entries) {
|
|
37677
|
+
entryMap.set(entry.id, entry);
|
|
37678
|
+
}
|
|
37679
|
+
const sessionIds = new Set;
|
|
37680
|
+
for (const e of events) {
|
|
37681
|
+
if ("session_id" in e && typeof e.session_id === "string") {
|
|
37682
|
+
sessionIds.add(e.session_id);
|
|
37683
|
+
}
|
|
37684
|
+
}
|
|
37685
|
+
const sessionCount = sessionIds.size;
|
|
37686
|
+
const violationTrends = [];
|
|
37687
|
+
for (const [entryId, rollup] of rollups) {
|
|
37688
|
+
if (rollup.violated_count === 0)
|
|
37689
|
+
continue;
|
|
37690
|
+
const entry = entryMap.get(entryId);
|
|
37691
|
+
const lesson = entry?.lesson ?? entryId;
|
|
37692
|
+
const priority = entry?.directive_priority ?? "medium";
|
|
37693
|
+
const w7 = countWindowedReceipts(events, entryId, SEVEN_DAYS_MS, nowMs);
|
|
37694
|
+
const w30 = countWindowedReceipts(events, entryId, THIRTY_DAYS_MS, nowMs);
|
|
37695
|
+
const rate7d = safeDivide(w7.violations, w7.totalReceipts);
|
|
37696
|
+
const rate30d = safeDivide(w30.violations, w30.totalReceipts);
|
|
37697
|
+
const hasData = w7.totalReceipts > 0 || w30.totalReceipts > 0;
|
|
37698
|
+
violationTrends.push({
|
|
37699
|
+
entryId,
|
|
37700
|
+
lesson,
|
|
37701
|
+
priority,
|
|
37702
|
+
violationRate7d: rate7d,
|
|
37703
|
+
violationRate30d: rate30d,
|
|
37704
|
+
trend: determineTrend(rate7d, rate30d, hasData)
|
|
37705
|
+
});
|
|
37706
|
+
}
|
|
37707
|
+
let totalViolations7d = 0;
|
|
37708
|
+
let totalReceipts7d = 0;
|
|
37709
|
+
let totalViolations30d = 0;
|
|
37710
|
+
let totalReceipts30d = 0;
|
|
37711
|
+
for (const e of events) {
|
|
37712
|
+
if (!isReceiptType(e))
|
|
37713
|
+
continue;
|
|
37714
|
+
const t = Date.parse(e.timestamp);
|
|
37715
|
+
if (Number.isNaN(t))
|
|
37716
|
+
continue;
|
|
37717
|
+
if (t >= nowMs - SEVEN_DAYS_MS) {
|
|
37718
|
+
totalReceipts7d++;
|
|
37719
|
+
if (e.type === "violated")
|
|
37720
|
+
totalViolations7d++;
|
|
37721
|
+
}
|
|
37722
|
+
if (t >= nowMs - THIRTY_DAYS_MS) {
|
|
37723
|
+
totalReceipts30d++;
|
|
37724
|
+
if (e.type === "violated")
|
|
37725
|
+
totalViolations30d++;
|
|
37726
|
+
}
|
|
37727
|
+
}
|
|
37728
|
+
const overallViolationRate = {
|
|
37729
|
+
window7d: safeDivide(totalViolations7d, totalReceipts7d),
|
|
37730
|
+
window30d: safeDivide(totalViolations30d, totalReceipts30d)
|
|
37731
|
+
};
|
|
37732
|
+
const priorityGroups = new Map;
|
|
37733
|
+
for (const entry of entries) {
|
|
37734
|
+
const priority = entry.directive_priority ?? "medium";
|
|
37735
|
+
const rollup = rollups.get(entry.id);
|
|
37736
|
+
if (!rollup)
|
|
37737
|
+
continue;
|
|
37738
|
+
let group = priorityGroups.get(priority);
|
|
37739
|
+
if (!group) {
|
|
37740
|
+
group = { applied: 0, total: 0 };
|
|
37741
|
+
priorityGroups.set(priority, group);
|
|
37742
|
+
}
|
|
37743
|
+
group.applied += rollup.applied_explicit_count;
|
|
37744
|
+
group.total += rollup.shown_count;
|
|
37745
|
+
}
|
|
37746
|
+
const applicationRateByPriority = {};
|
|
37747
|
+
for (const [priority, group] of priorityGroups) {
|
|
37748
|
+
applicationRateByPriority[priority] = {
|
|
37749
|
+
applied: group.applied,
|
|
37750
|
+
total: group.total,
|
|
37751
|
+
rate: safeDivide(group.applied, group.total)
|
|
37752
|
+
};
|
|
37753
|
+
}
|
|
37754
|
+
const timeToLatestApplication = [];
|
|
37755
|
+
for (const entry of entries) {
|
|
37756
|
+
const rollup = rollups.get(entry.id);
|
|
37757
|
+
let daysToApply = null;
|
|
37758
|
+
if (rollup?.last_applied_at && entry.created_at) {
|
|
37759
|
+
const appliedMs = Date.parse(rollup.last_applied_at);
|
|
37760
|
+
const createdMs = Date.parse(entry.created_at);
|
|
37761
|
+
if (!Number.isNaN(appliedMs) && !Number.isNaN(createdMs) && appliedMs >= createdMs) {
|
|
37762
|
+
daysToApply = (appliedMs - createdMs) / MS_PER_DAY;
|
|
37763
|
+
}
|
|
37764
|
+
}
|
|
37765
|
+
timeToLatestApplication.push({
|
|
37766
|
+
entryId: entry.id,
|
|
37767
|
+
lesson: entry.lesson,
|
|
37768
|
+
daysToApply
|
|
37769
|
+
});
|
|
37770
|
+
}
|
|
37771
|
+
const recentEscalations30d = await readRecentEscalations(directory, 30, now);
|
|
37772
|
+
const last7dCutoff = nowMs - SEVEN_DAYS_MS;
|
|
37773
|
+
const last7d = recentEscalations30d.filter((esc3) => {
|
|
37774
|
+
const t = Date.parse(esc3.at);
|
|
37775
|
+
return !Number.isNaN(t) && t >= last7dCutoff;
|
|
37776
|
+
}).length;
|
|
37777
|
+
let totalEscalations = 0;
|
|
37778
|
+
for (const e of events) {
|
|
37779
|
+
if (e.type === "escalation")
|
|
37780
|
+
totalEscalations++;
|
|
37781
|
+
}
|
|
37782
|
+
const escalationFrequency = {
|
|
37783
|
+
total: totalEscalations,
|
|
37784
|
+
last7d,
|
|
37785
|
+
last30d: recentEscalations30d.length
|
|
37786
|
+
};
|
|
37787
|
+
let unacknowledgedCriticalCount = 0;
|
|
37788
|
+
for (const entry of entries) {
|
|
37789
|
+
if (entry.directive_priority !== "critical")
|
|
37790
|
+
continue;
|
|
37791
|
+
const rollup = rollups.get(entry.id);
|
|
37792
|
+
if (!rollup)
|
|
37793
|
+
continue;
|
|
37794
|
+
if (rollup.shown_count > 0 && rollup.acknowledged_count === 0 && rollup.applied_explicit_count === 0) {
|
|
37795
|
+
unacknowledgedCriticalCount++;
|
|
37796
|
+
}
|
|
37797
|
+
}
|
|
37798
|
+
const entryROI = [];
|
|
37799
|
+
for (const entry of entries) {
|
|
37800
|
+
const rollup = rollups.get(entry.id);
|
|
37801
|
+
if (!rollup) {
|
|
37802
|
+
entryROI.push({
|
|
37803
|
+
entryId: entry.id,
|
|
37804
|
+
lesson: entry.lesson,
|
|
37805
|
+
appliedCount: 0,
|
|
37806
|
+
shownCount: 0,
|
|
37807
|
+
succeededCount: 0,
|
|
37808
|
+
failedCount: 0,
|
|
37809
|
+
roi: "unused"
|
|
37810
|
+
});
|
|
37811
|
+
continue;
|
|
37812
|
+
}
|
|
37813
|
+
entryROI.push({
|
|
37814
|
+
entryId: entry.id,
|
|
37815
|
+
lesson: entry.lesson,
|
|
37816
|
+
appliedCount: rollup.applied_explicit_count,
|
|
37817
|
+
shownCount: rollup.shown_count,
|
|
37818
|
+
succeededCount: rollup.succeeded_after_shown_count,
|
|
37819
|
+
failedCount: rollup.failed_after_shown_count,
|
|
37820
|
+
roi: classifyROI(rollup)
|
|
37821
|
+
});
|
|
37822
|
+
}
|
|
37823
|
+
const neverApplied = [];
|
|
37824
|
+
for (const entry of entries) {
|
|
37825
|
+
const rollup = rollups.get(entry.id);
|
|
37826
|
+
const applied = rollup?.applied_explicit_count ?? 0;
|
|
37827
|
+
const phasesAlive = entry.phases_alive ?? 0;
|
|
37828
|
+
if (applied === 0 && phasesAlive >= phasesThreshold) {
|
|
37829
|
+
neverApplied.push({
|
|
37830
|
+
entryId: entry.id,
|
|
37831
|
+
lesson: entry.lesson,
|
|
37832
|
+
phasesAlive
|
|
37833
|
+
});
|
|
37834
|
+
}
|
|
37835
|
+
}
|
|
37836
|
+
const learningSummary = buildLearningSummary(overallViolationRate, violationTrends, sessionCount);
|
|
37837
|
+
return {
|
|
37838
|
+
violationTrends,
|
|
37839
|
+
overallViolationRate,
|
|
37840
|
+
applicationRateByPriority,
|
|
37841
|
+
timeToLatestApplication,
|
|
37842
|
+
escalationFrequency,
|
|
37843
|
+
unacknowledgedCriticalCount,
|
|
37844
|
+
entryROI,
|
|
37845
|
+
neverApplied,
|
|
37846
|
+
learningSummary,
|
|
37847
|
+
sessionCount
|
|
37848
|
+
};
|
|
37849
|
+
}
|
|
37850
|
+
function emptyMetrics() {
|
|
37851
|
+
return {
|
|
37852
|
+
violationTrends: [],
|
|
37853
|
+
overallViolationRate: { window7d: 0, window30d: 0 },
|
|
37854
|
+
applicationRateByPriority: {},
|
|
37855
|
+
timeToLatestApplication: [],
|
|
37856
|
+
escalationFrequency: { total: 0, last7d: 0, last30d: 0 },
|
|
37857
|
+
unacknowledgedCriticalCount: 0,
|
|
37858
|
+
entryROI: [],
|
|
37859
|
+
neverApplied: [],
|
|
37860
|
+
learningSummary: "No learning data yet",
|
|
37861
|
+
sessionCount: 0
|
|
37862
|
+
};
|
|
37863
|
+
}
|
|
37864
|
+
function buildLearningSummary(overallRate, trends, sessionCount) {
|
|
37865
|
+
const overallTrend = overallRate.window7d < overallRate.window30d ? "improving" : overallRate.window7d > overallRate.window30d ? "worsening" : "stable";
|
|
37866
|
+
const rate7dPct = (overallRate.window7d * 100).toFixed(1);
|
|
37867
|
+
const rate30dPct = (overallRate.window30d * 100).toFixed(1);
|
|
37868
|
+
const line1 = `Learning trend: ${overallTrend} \u2014 ${rate7dPct}% violation rate (7d), ${rate30dPct}% (30d) across ${sessionCount} sessions`;
|
|
37869
|
+
let line2 = "No improvement data yet";
|
|
37870
|
+
let bestDrop = 0;
|
|
37871
|
+
let bestDropEntry;
|
|
37872
|
+
for (const t of trends) {
|
|
37873
|
+
if (t.trend !== "improving")
|
|
37874
|
+
continue;
|
|
37875
|
+
const drop = t.violationRate30d - t.violationRate7d;
|
|
37876
|
+
if (drop > bestDrop) {
|
|
37877
|
+
bestDrop = drop;
|
|
37878
|
+
bestDropEntry = t;
|
|
37879
|
+
}
|
|
37880
|
+
}
|
|
37881
|
+
if (bestDropEntry) {
|
|
37882
|
+
line2 = `Top improvement: ${truncateLesson(bestDropEntry.lesson)}`;
|
|
37883
|
+
}
|
|
37884
|
+
let line3 = "No concerns detected";
|
|
37885
|
+
let worstRise = 0;
|
|
37886
|
+
let worstEntry;
|
|
37887
|
+
for (const t of trends) {
|
|
37888
|
+
if (t.trend !== "worsening")
|
|
37889
|
+
continue;
|
|
37890
|
+
const rise = t.violationRate7d - t.violationRate30d;
|
|
37891
|
+
if (rise > worstRise) {
|
|
37892
|
+
worstRise = rise;
|
|
37893
|
+
worstEntry = t;
|
|
37894
|
+
}
|
|
37895
|
+
}
|
|
37896
|
+
if (worstEntry) {
|
|
37897
|
+
line3 = `Watch: ${truncateLesson(worstEntry.lesson)}`;
|
|
37898
|
+
}
|
|
37899
|
+
return `${line1}
|
|
37900
|
+
${line2}
|
|
37901
|
+
${line3}`;
|
|
37902
|
+
}
|
|
37903
|
+
function formatLearningMarkdown(metrics) {
|
|
37904
|
+
const lines = [];
|
|
37905
|
+
lines.push("## Learning Summary", "", metrics.learningSummary, "");
|
|
37906
|
+
lines.push("## Violation Trends", "");
|
|
37907
|
+
if (metrics.violationTrends.length === 0) {
|
|
37908
|
+
lines.push("No violation trends recorded.", "");
|
|
37909
|
+
} else {
|
|
37910
|
+
lines.push("| Entry | Priority | 7d Rate | 30d Rate | Trend |");
|
|
37911
|
+
lines.push("|-------|----------|---------|----------|-------|");
|
|
37912
|
+
for (const t of metrics.violationTrends) {
|
|
37913
|
+
lines.push(`| ${truncateLesson(t.lesson)} | ${t.priority} | ${(t.violationRate7d * 100).toFixed(1)}% | ${(t.violationRate30d * 100).toFixed(1)}% | ${t.trend} |`);
|
|
37914
|
+
}
|
|
37915
|
+
lines.push("");
|
|
37916
|
+
}
|
|
37917
|
+
lines.push("## Application Rates by Priority", "");
|
|
37918
|
+
const priorities = Object.keys(metrics.applicationRateByPriority);
|
|
37919
|
+
if (priorities.length === 0) {
|
|
37920
|
+
lines.push("No application data recorded.", "");
|
|
37921
|
+
} else {
|
|
37922
|
+
lines.push("| Priority | Applied | Total | Rate |");
|
|
37923
|
+
lines.push("|----------|---------|-------|------|");
|
|
37924
|
+
for (const p of priorities) {
|
|
37925
|
+
const g = metrics.applicationRateByPriority[p];
|
|
37926
|
+
lines.push(`| ${p} | ${g.applied} | ${g.total} | ${(g.rate * 100).toFixed(1)}% |`);
|
|
37927
|
+
}
|
|
37928
|
+
lines.push("");
|
|
37929
|
+
}
|
|
37930
|
+
lines.push("## Escalation Activity", "");
|
|
37931
|
+
lines.push(`- Total escalations: ${metrics.escalationFrequency.total}`);
|
|
37932
|
+
lines.push(`- Last 7 days: ${metrics.escalationFrequency.last7d}`);
|
|
37933
|
+
lines.push(`- Last 30 days: ${metrics.escalationFrequency.last30d}`);
|
|
37934
|
+
lines.push("");
|
|
37935
|
+
lines.push("## Entry ROI", "");
|
|
37936
|
+
const sortedROI = [...metrics.entryROI].sort((a, b) => b.shownCount - a.shownCount);
|
|
37937
|
+
const top10 = sortedROI.slice(0, 10);
|
|
37938
|
+
const unused = metrics.entryROI.filter((r) => r.roi === "unused");
|
|
37939
|
+
const roiEntries = [...top10];
|
|
37940
|
+
for (const u of unused) {
|
|
37941
|
+
if (!roiEntries.some((r) => r.entryId === u.entryId)) {
|
|
37942
|
+
roiEntries.push(u);
|
|
37943
|
+
}
|
|
37944
|
+
}
|
|
37945
|
+
if (roiEntries.length === 0) {
|
|
37946
|
+
lines.push("No ROI data recorded.", "");
|
|
37947
|
+
} else {
|
|
37948
|
+
lines.push("| Entry | Applied | Shown | Succeeded | Failed | ROI |");
|
|
37949
|
+
lines.push("|-------|---------|-------|-----------|--------|-----|");
|
|
37950
|
+
for (const r of roiEntries) {
|
|
37951
|
+
lines.push(`| ${truncateLesson(r.lesson)} | ${r.appliedCount} | ${r.shownCount} | ${r.succeededCount} | ${r.failedCount} | ${r.roi} |`);
|
|
37952
|
+
}
|
|
37953
|
+
lines.push("");
|
|
37954
|
+
}
|
|
37955
|
+
lines.push("## Never Applied", "");
|
|
37956
|
+
if (metrics.neverApplied.length === 0) {
|
|
37957
|
+
lines.push("All entries have been applied or are below the phase threshold.", "");
|
|
37958
|
+
} else {
|
|
37959
|
+
lines.push("| Entry | Phases Alive |");
|
|
37960
|
+
lines.push("|-------|-------------|");
|
|
37961
|
+
for (const n of metrics.neverApplied) {
|
|
37962
|
+
lines.push(`| ${truncateLesson(n.lesson)} | ${n.phasesAlive} |`);
|
|
37963
|
+
}
|
|
37964
|
+
lines.push("");
|
|
37965
|
+
}
|
|
37966
|
+
lines.push("## Time to Latest Application", "");
|
|
37967
|
+
const applied = metrics.timeToLatestApplication.filter((t) => t.daysToApply !== null).map((t) => t.daysToApply);
|
|
37968
|
+
if (applied.length === 0) {
|
|
37969
|
+
lines.push("No application timing data available.", "");
|
|
37970
|
+
} else {
|
|
37971
|
+
applied.sort((a, b) => a - b);
|
|
37972
|
+
const median = applied.length % 2 === 0 ? (applied[applied.length / 2 - 1] + applied[applied.length / 2]) / 2 : applied[Math.floor(applied.length / 2)];
|
|
37973
|
+
const min = applied[0];
|
|
37974
|
+
const max = applied[applied.length - 1];
|
|
37975
|
+
lines.push(`- Median: ${median.toFixed(1)} days`);
|
|
37976
|
+
lines.push(`- Min: ${min.toFixed(1)} days`);
|
|
37977
|
+
lines.push(`- Max: ${max.toFixed(1)} days`);
|
|
37978
|
+
lines.push(`- Entries with data: ${applied.length}`);
|
|
37979
|
+
lines.push("");
|
|
37980
|
+
}
|
|
37981
|
+
return lines.join(`
|
|
37982
|
+
`);
|
|
37983
|
+
}
|
|
37984
|
+
function formatLearningJSON(metrics) {
|
|
37985
|
+
return metrics;
|
|
37986
|
+
}
|
|
37987
|
+
var SEVEN_DAYS_MS, THIRTY_DAYS_MS, MS_PER_DAY, DEFAULT_PHASES_ALIVE_THRESHOLD = 3, MAX_LESSON_DISPLAY_CHARS = 60;
|
|
37988
|
+
var init_learning_metrics = __esm(() => {
|
|
37989
|
+
init_knowledge_escalator();
|
|
37990
|
+
init_knowledge_events();
|
|
37991
|
+
init_knowledge_store();
|
|
37992
|
+
SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
37993
|
+
THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
37994
|
+
MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
37343
37995
|
});
|
|
37344
37996
|
|
|
37345
37997
|
// src/hooks/knowledge-validator.ts
|
|
37346
|
-
import { appendFile as
|
|
37347
|
-
import * as
|
|
37998
|
+
import { appendFile as appendFile4, mkdir as mkdir4 } from "fs/promises";
|
|
37999
|
+
import * as path15 from "path";
|
|
37348
38000
|
function normalizeText(text) {
|
|
37349
38001
|
return text.normalize("NFKC").toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
37350
38002
|
}
|
|
@@ -37497,7 +38149,7 @@ function validateSkillPath(p) {
|
|
|
37497
38149
|
return false;
|
|
37498
38150
|
if (p.includes("\x00"))
|
|
37499
38151
|
return false;
|
|
37500
|
-
if (
|
|
38152
|
+
if (path15.isAbsolute(p))
|
|
37501
38153
|
return false;
|
|
37502
38154
|
if (p.includes(".."))
|
|
37503
38155
|
return false;
|
|
@@ -37599,15 +38251,15 @@ function validateActionability(entry) {
|
|
|
37599
38251
|
return { actionable: false, reason };
|
|
37600
38252
|
}
|
|
37601
38253
|
function resolveUnactionablePath(directory) {
|
|
37602
|
-
return
|
|
38254
|
+
return path15.join(directory, ".swarm", "knowledge-unactionable.jsonl");
|
|
37603
38255
|
}
|
|
37604
38256
|
async function appendUnactionable(directory, entry, reason) {
|
|
37605
38257
|
const filePath = resolveUnactionablePath(directory);
|
|
37606
|
-
const dirPath =
|
|
37607
|
-
await
|
|
38258
|
+
const dirPath = path15.dirname(filePath);
|
|
38259
|
+
await mkdir4(dirPath, { recursive: true });
|
|
37608
38260
|
let release = null;
|
|
37609
38261
|
try {
|
|
37610
|
-
release = await
|
|
38262
|
+
release = await import_proper_lockfile5.default.lock(dirPath, {
|
|
37611
38263
|
retries: { retries: 50, minTimeout: 10, maxTimeout: 100 },
|
|
37612
38264
|
stale: 5000
|
|
37613
38265
|
});
|
|
@@ -37617,7 +38269,7 @@ async function appendUnactionable(directory, entry, reason) {
|
|
|
37617
38269
|
unactionable_reason: reason,
|
|
37618
38270
|
quarantined_at: new Date().toISOString()
|
|
37619
38271
|
};
|
|
37620
|
-
await
|
|
38272
|
+
await appendFile4(filePath, `${JSON.stringify(record3)}
|
|
37621
38273
|
`, "utf-8");
|
|
37622
38274
|
const existing = await readKnowledge(filePath);
|
|
37623
38275
|
if (existing.length > 200) {
|
|
@@ -37646,14 +38298,14 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
37646
38298
|
return;
|
|
37647
38299
|
}
|
|
37648
38300
|
const sanitizedReason = reason.slice(0, 500).replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\x0d]/g, "");
|
|
37649
|
-
const knowledgePath =
|
|
37650
|
-
const quarantinePath =
|
|
37651
|
-
const rejectedPath =
|
|
37652
|
-
const swarmDir =
|
|
37653
|
-
await
|
|
38301
|
+
const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
|
|
38302
|
+
const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
|
|
38303
|
+
const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
|
|
38304
|
+
const swarmDir = path15.join(directory, ".swarm");
|
|
38305
|
+
await mkdir4(swarmDir, { recursive: true });
|
|
37654
38306
|
let release;
|
|
37655
38307
|
try {
|
|
37656
|
-
release = await
|
|
38308
|
+
release = await import_proper_lockfile5.default.lock(swarmDir, {
|
|
37657
38309
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37658
38310
|
stale: 5000
|
|
37659
38311
|
});
|
|
@@ -37675,7 +38327,7 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
37675
38327
|
`)}
|
|
37676
38328
|
` : "";
|
|
37677
38329
|
await atomicWriteFile(knowledgePath, jsonlContent);
|
|
37678
|
-
await
|
|
38330
|
+
await appendFile4(quarantinePath, `${JSON.stringify(quarantined)}
|
|
37679
38331
|
`, "utf-8");
|
|
37680
38332
|
const quarantinedEntries = await readKnowledge(quarantinePath);
|
|
37681
38333
|
if (quarantinedEntries.length > 100) {
|
|
@@ -37692,7 +38344,7 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
37692
38344
|
rejected_at: new Date().toISOString(),
|
|
37693
38345
|
rejection_layer: 3
|
|
37694
38346
|
};
|
|
37695
|
-
await
|
|
38347
|
+
await appendFile4(rejectedPath, `${JSON.stringify(rejectedRecord)}
|
|
37696
38348
|
`, "utf-8");
|
|
37697
38349
|
} finally {
|
|
37698
38350
|
if (release) {
|
|
@@ -37710,14 +38362,14 @@ async function restoreEntry(directory, entryId) {
|
|
|
37710
38362
|
warn("[knowledge-validator] restoreEntry: invalid entryId rejected");
|
|
37711
38363
|
return;
|
|
37712
38364
|
}
|
|
37713
|
-
const knowledgePath =
|
|
37714
|
-
const quarantinePath =
|
|
37715
|
-
const rejectedPath =
|
|
37716
|
-
const swarmDir =
|
|
37717
|
-
await
|
|
38365
|
+
const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
|
|
38366
|
+
const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
|
|
38367
|
+
const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
|
|
38368
|
+
const swarmDir = path15.join(directory, ".swarm");
|
|
38369
|
+
await mkdir4(swarmDir, { recursive: true });
|
|
37718
38370
|
let release;
|
|
37719
38371
|
try {
|
|
37720
|
-
release = await
|
|
38372
|
+
release = await import_proper_lockfile5.default.lock(swarmDir, {
|
|
37721
38373
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
37722
38374
|
stale: 5000
|
|
37723
38375
|
});
|
|
@@ -37749,7 +38401,7 @@ async function restoreEntry(directory, entryId) {
|
|
|
37749
38401
|
`)}
|
|
37750
38402
|
` : "";
|
|
37751
38403
|
await atomicWriteFile(quarantinePath, jsonlContent);
|
|
37752
|
-
await
|
|
38404
|
+
await appendFile4(knowledgePath, `${JSON.stringify(original)}
|
|
37753
38405
|
`, "utf-8");
|
|
37754
38406
|
const rejectedEntries = await readKnowledge(rejectedPath);
|
|
37755
38407
|
const filtered = rejectedEntries.filter((e) => e.id !== entryId);
|
|
@@ -37763,12 +38415,12 @@ async function restoreEntry(directory, entryId) {
|
|
|
37763
38415
|
}
|
|
37764
38416
|
}
|
|
37765
38417
|
}
|
|
37766
|
-
var
|
|
38418
|
+
var import_proper_lockfile5, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, VALID_CATEGORIES, TECH_REFERENCE_WORDS, ACTION_VERB_WORDS, NEGATION_PAIRS, ACTIONABLE_STRING_MAX = 200, ACTIONABLE_LIST_MAX = 20, NAME_PATTERN, SOURCE_REF_FORBIDDEN, ALLOWED_SKILL_PATH_PREFIXES, VALID_DIRECTIVE_PRIORITIES;
|
|
37767
38419
|
var init_knowledge_validator = __esm(() => {
|
|
37768
38420
|
init_task_file();
|
|
37769
38421
|
init_logger();
|
|
37770
38422
|
init_knowledge_store();
|
|
37771
|
-
|
|
38423
|
+
import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
|
|
37772
38424
|
DANGEROUS_COMMAND_PATTERNS = [
|
|
37773
38425
|
/\brm\s+-rf\b/,
|
|
37774
38426
|
/\bsudo\s+rm\b/,
|
|
@@ -37885,10 +38537,15 @@ var init_knowledge_validator = __esm(() => {
|
|
|
37885
38537
|
]);
|
|
37886
38538
|
});
|
|
37887
38539
|
|
|
38540
|
+
// src/services/skill-changelog.ts
|
|
38541
|
+
var init_skill_changelog = __esm(() => {
|
|
38542
|
+
init_logger();
|
|
38543
|
+
});
|
|
38544
|
+
|
|
37888
38545
|
// src/services/skill-generator.ts
|
|
37889
|
-
import { existsSync as
|
|
37890
|
-
import { mkdir as
|
|
37891
|
-
import * as
|
|
38546
|
+
import { existsSync as existsSync11, unlinkSync as unlinkSync5 } from "fs";
|
|
38547
|
+
import { mkdir as mkdir5, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
|
|
38548
|
+
import * as path16 from "path";
|
|
37892
38549
|
function sanitizeSlug(input) {
|
|
37893
38550
|
const lc = input.toLowerCase().trim();
|
|
37894
38551
|
const mapped = lc.replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-");
|
|
@@ -37899,10 +38556,10 @@ function isValidSlug(slug) {
|
|
|
37899
38556
|
return SLUG_PATTERN.test(slug);
|
|
37900
38557
|
}
|
|
37901
38558
|
function proposalPath(directory, slug) {
|
|
37902
|
-
return
|
|
38559
|
+
return path16.join(directory, ".swarm", "skills", "proposals", `${slug}.md`);
|
|
37903
38560
|
}
|
|
37904
38561
|
function activePath(directory, slug) {
|
|
37905
|
-
return
|
|
38562
|
+
return path16.join(directory, ".opencode", "skills", "generated", slug, "SKILL.md");
|
|
37906
38563
|
}
|
|
37907
38564
|
function activeRepoRelativePath(slug) {
|
|
37908
38565
|
return `.opencode/skills/generated/${slug}/SKILL.md`;
|
|
@@ -37910,7 +38567,7 @@ function activeRepoRelativePath(slug) {
|
|
|
37910
38567
|
async function selectCandidateEntries(directory, opts) {
|
|
37911
38568
|
const swarm = await readKnowledge(resolveSwarmKnowledgePath(directory));
|
|
37912
38569
|
const hivePath = resolveHiveKnowledgePath();
|
|
37913
|
-
const hive =
|
|
38570
|
+
const hive = existsSync11(hivePath) ? await readKnowledge(hivePath) : [];
|
|
37914
38571
|
const all = [...swarm, ...hive];
|
|
37915
38572
|
return all.filter((e) => {
|
|
37916
38573
|
if (e.status === "archived")
|
|
@@ -37993,10 +38650,12 @@ function clusterEntries(entries) {
|
|
|
37993
38650
|
function uniqueStrings(arr) {
|
|
37994
38651
|
return [...new Set(arr.filter((s) => typeof s === "string" && s.length > 0))];
|
|
37995
38652
|
}
|
|
37996
|
-
function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().toISOString()) {
|
|
38653
|
+
function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().toISOString(), overrides) {
|
|
37997
38654
|
const description = cluster.title.length > 200 ? `${cluster.title.slice(0, 197)}\u2026` : cluster.title;
|
|
37998
38655
|
const ids = cluster.entries.map((e) => ` - ${e.id}`).join(`
|
|
37999
38656
|
`);
|
|
38657
|
+
const version3 = overrides?.version ?? 1;
|
|
38658
|
+
const skillOrigin = overrides?.skillOrigin ?? "generated";
|
|
38000
38659
|
const lines = [];
|
|
38001
38660
|
lines.push("---");
|
|
38002
38661
|
lines.push(`name: ${cluster.slug}`);
|
|
@@ -38008,6 +38667,8 @@ function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().
|
|
|
38008
38667
|
lines.push(`generated_at: ${generatedAt}`);
|
|
38009
38668
|
lines.push(`confidence: ${cluster.avgConfidence.toFixed(2)}`);
|
|
38010
38669
|
lines.push(`status: ${mode === "active" ? "active" : "draft"}`);
|
|
38670
|
+
lines.push(`version: ${version3}`);
|
|
38671
|
+
lines.push(`skill_origin: ${skillOrigin}`);
|
|
38011
38672
|
lines.push("---");
|
|
38012
38673
|
lines.push("");
|
|
38013
38674
|
lines.push("<!-- generated by opencode-swarm skill-generator. Do not edit by hand; edits will be preserved on regeneration only with controlled update mode. -->");
|
|
@@ -38080,9 +38741,9 @@ function escapeMarkdown(s) {
|
|
|
38080
38741
|
return s.replace(/[\r\n]+/g, " ").slice(0, 280);
|
|
38081
38742
|
}
|
|
38082
38743
|
async function atomicWrite(p, content) {
|
|
38083
|
-
await
|
|
38744
|
+
await mkdir5(path16.dirname(p), { recursive: true });
|
|
38084
38745
|
const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
|
|
38085
|
-
await
|
|
38746
|
+
await writeFile4(tmp, content, "utf-8");
|
|
38086
38747
|
await rename3(tmp, p);
|
|
38087
38748
|
}
|
|
38088
38749
|
async function generateSkills(req) {
|
|
@@ -38097,7 +38758,7 @@ async function generateSkills(req) {
|
|
|
38097
38758
|
const idSet = new Set(req.sourceKnowledgeIds);
|
|
38098
38759
|
const swarm = await readKnowledge(resolveSwarmKnowledgePath(req.directory));
|
|
38099
38760
|
const hivePath = resolveHiveKnowledgePath();
|
|
38100
|
-
const hive =
|
|
38761
|
+
const hive = existsSync11(hivePath) ? await readKnowledge(hivePath) : [];
|
|
38101
38762
|
pool = [...swarm, ...hive].filter((e) => idSet.has(e.id) && e.status !== "archived");
|
|
38102
38763
|
} else {
|
|
38103
38764
|
pool = candidates;
|
|
@@ -38125,7 +38786,7 @@ async function generateSkills(req) {
|
|
|
38125
38786
|
continue;
|
|
38126
38787
|
}
|
|
38127
38788
|
const targetPath = req.mode === "active" ? activePath(req.directory, cluster.slug) : proposalPath(req.directory, cluster.slug);
|
|
38128
|
-
const repoRel =
|
|
38789
|
+
const repoRel = path16.relative(req.directory, targetPath).replace(/\\/g, "/");
|
|
38129
38790
|
if (!validateSkillPath(repoRel)) {
|
|
38130
38791
|
result.skipped.push({
|
|
38131
38792
|
slug: cluster.slug,
|
|
@@ -38134,8 +38795,8 @@ async function generateSkills(req) {
|
|
|
38134
38795
|
continue;
|
|
38135
38796
|
}
|
|
38136
38797
|
let preserved = false;
|
|
38137
|
-
if (req.mode === "active" &&
|
|
38138
|
-
const existing = await
|
|
38798
|
+
if (req.mode === "active" && existsSync11(targetPath) && !req.force) {
|
|
38799
|
+
const existing = await readFile4(targetPath, "utf-8");
|
|
38139
38800
|
if (!existing.includes("generated by opencode-swarm skill-generator")) {
|
|
38140
38801
|
preserved = true;
|
|
38141
38802
|
result.skipped.push({
|
|
@@ -38179,7 +38840,7 @@ async function stampSourceEntries(directory, slug, ids) {
|
|
|
38179
38840
|
if (touched)
|
|
38180
38841
|
await rewriteKnowledge(swarmPath, swarm);
|
|
38181
38842
|
const hivePath = resolveHiveKnowledgePath();
|
|
38182
|
-
if (!
|
|
38843
|
+
if (!existsSync11(hivePath))
|
|
38183
38844
|
return;
|
|
38184
38845
|
const hive = await readKnowledge(hivePath);
|
|
38185
38846
|
let touchedHive = false;
|
|
@@ -38239,6 +38900,16 @@ function parseDraftFrontmatter(content) {
|
|
|
38239
38900
|
out.generatedAt = ga[1];
|
|
38240
38901
|
continue;
|
|
38241
38902
|
}
|
|
38903
|
+
const vm = line.match(/^version:\s*(\d+)\s*$/);
|
|
38904
|
+
if (vm) {
|
|
38905
|
+
out.version = parseInt(vm[1], 10);
|
|
38906
|
+
continue;
|
|
38907
|
+
}
|
|
38908
|
+
const so = line.match(/^skill_origin:\s*(\S+)\s*$/);
|
|
38909
|
+
if (so) {
|
|
38910
|
+
out.skillOrigin = so[1];
|
|
38911
|
+
continue;
|
|
38912
|
+
}
|
|
38242
38913
|
if (/^generated_from_knowledge:\s*$/.test(line)) {
|
|
38243
38914
|
inLegacyIdsList = true;
|
|
38244
38915
|
continue;
|
|
@@ -38255,10 +38926,10 @@ async function listSkills(directory) {
|
|
|
38255
38926
|
proposals: [],
|
|
38256
38927
|
active: []
|
|
38257
38928
|
};
|
|
38258
|
-
const proposalsDir =
|
|
38259
|
-
const activeDir =
|
|
38929
|
+
const proposalsDir = path16.join(directory, ".swarm", "skills", "proposals");
|
|
38930
|
+
const activeDir = path16.join(directory, ".opencode", "skills", "generated");
|
|
38260
38931
|
const fs9 = await import("fs/promises");
|
|
38261
|
-
if (
|
|
38932
|
+
if (existsSync11(proposalsDir)) {
|
|
38262
38933
|
const entries = await fs9.readdir(proposalsDir);
|
|
38263
38934
|
for (const f of entries) {
|
|
38264
38935
|
if (!f.endsWith(".md"))
|
|
@@ -38266,20 +38937,20 @@ async function listSkills(directory) {
|
|
|
38266
38937
|
const slug = f.replace(/\.md$/, "");
|
|
38267
38938
|
result.proposals.push({
|
|
38268
38939
|
slug,
|
|
38269
|
-
path:
|
|
38940
|
+
path: path16.join(proposalsDir, f)
|
|
38270
38941
|
});
|
|
38271
38942
|
}
|
|
38272
38943
|
}
|
|
38273
|
-
if (
|
|
38944
|
+
if (existsSync11(activeDir)) {
|
|
38274
38945
|
const entries = await fs9.readdir(activeDir, { withFileTypes: true });
|
|
38275
38946
|
for (const e of entries) {
|
|
38276
38947
|
if (!e.isDirectory())
|
|
38277
38948
|
continue;
|
|
38278
|
-
const retiredMarker =
|
|
38279
|
-
if (
|
|
38949
|
+
const retiredMarker = path16.join(activeDir, e.name, "retired.marker");
|
|
38950
|
+
if (existsSync11(retiredMarker))
|
|
38280
38951
|
continue;
|
|
38281
|
-
const skillPath =
|
|
38282
|
-
if (
|
|
38952
|
+
const skillPath = path16.join(activeDir, e.name, "SKILL.md");
|
|
38953
|
+
if (existsSync11(skillPath)) {
|
|
38283
38954
|
result.active.push({
|
|
38284
38955
|
slug: e.name,
|
|
38285
38956
|
path: skillPath
|
|
@@ -38294,13 +38965,155 @@ var init_skill_generator = __esm(() => {
|
|
|
38294
38965
|
init_knowledge_store();
|
|
38295
38966
|
init_knowledge_validator();
|
|
38296
38967
|
init_logger();
|
|
38968
|
+
init_skill_changelog();
|
|
38297
38969
|
SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
38298
38970
|
});
|
|
38299
38971
|
|
|
38972
|
+
// src/services/skill-improver-quota.ts
|
|
38973
|
+
import { existsSync as existsSync12 } from "fs";
|
|
38974
|
+
import { mkdir as mkdir6, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
38975
|
+
import * as path17 from "path";
|
|
38976
|
+
async function acquireLock(dir) {
|
|
38977
|
+
const acquire = import_proper_lockfile6.default.lock(dir, LOCK_RETRY_OPTS);
|
|
38978
|
+
let timer;
|
|
38979
|
+
const timeout = new Promise((_, reject) => {
|
|
38980
|
+
timer = setTimeout(() => {
|
|
38981
|
+
reject(new Error(`SKILL_IMPROVER_QUOTA_LOCK_TIMEOUT: failed to acquire lock on ${dir} within ${LOCK_ACQUIRE_TIMEOUT_MS}ms`));
|
|
38982
|
+
}, LOCK_ACQUIRE_TIMEOUT_MS);
|
|
38983
|
+
});
|
|
38984
|
+
try {
|
|
38985
|
+
const release = await Promise.race([acquire, timeout]);
|
|
38986
|
+
return release;
|
|
38987
|
+
} finally {
|
|
38988
|
+
if (timer)
|
|
38989
|
+
clearTimeout(timer);
|
|
38990
|
+
}
|
|
38991
|
+
}
|
|
38992
|
+
function resolveQuotaPath(directory) {
|
|
38993
|
+
return path17.join(directory, ".swarm", "skill-improver-quota.json");
|
|
38994
|
+
}
|
|
38995
|
+
function todayKey(window, now = new Date) {
|
|
38996
|
+
if (window === "utc") {
|
|
38997
|
+
return now.toISOString().slice(0, 10);
|
|
38998
|
+
}
|
|
38999
|
+
const yr = now.getFullYear();
|
|
39000
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
39001
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
39002
|
+
return `${yr}-${m}-${d}`;
|
|
39003
|
+
}
|
|
39004
|
+
async function readState(filePath) {
|
|
39005
|
+
if (!existsSync12(filePath))
|
|
39006
|
+
return null;
|
|
39007
|
+
try {
|
|
39008
|
+
const raw = await readFile5(filePath, "utf-8");
|
|
39009
|
+
const parsed = JSON.parse(raw);
|
|
39010
|
+
if (typeof parsed.date !== "string" || typeof parsed.calls_used !== "number" || typeof parsed.max_calls !== "number" || parsed.window !== "utc" && parsed.window !== "local") {
|
|
39011
|
+
return null;
|
|
39012
|
+
}
|
|
39013
|
+
return parsed;
|
|
39014
|
+
} catch {
|
|
39015
|
+
return null;
|
|
39016
|
+
}
|
|
39017
|
+
}
|
|
39018
|
+
async function writeState(filePath, state) {
|
|
39019
|
+
await mkdir6(path17.dirname(filePath), { recursive: true });
|
|
39020
|
+
const tmp = `${filePath}.tmp-${process.pid}`;
|
|
39021
|
+
await writeFile5(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
39022
|
+
await rename4(tmp, filePath);
|
|
39023
|
+
}
|
|
39024
|
+
async function getQuotaState(directory, opts) {
|
|
39025
|
+
const filePath = resolveQuotaPath(directory);
|
|
39026
|
+
const today = todayKey(opts.window, opts.now);
|
|
39027
|
+
const existing = await readState(filePath);
|
|
39028
|
+
if (!existing || existing.date !== today || existing.window !== opts.window) {
|
|
39029
|
+
const fresh = {
|
|
39030
|
+
date: today,
|
|
39031
|
+
calls_used: 0,
|
|
39032
|
+
max_calls: opts.maxCalls,
|
|
39033
|
+
window: opts.window
|
|
39034
|
+
};
|
|
39035
|
+
await writeState(filePath, fresh);
|
|
39036
|
+
return fresh;
|
|
39037
|
+
}
|
|
39038
|
+
return { ...existing, max_calls: opts.maxCalls };
|
|
39039
|
+
}
|
|
39040
|
+
async function reserveQuota(directory, opts) {
|
|
39041
|
+
const filePath = resolveQuotaPath(directory);
|
|
39042
|
+
await mkdir6(path17.dirname(filePath), { recursive: true });
|
|
39043
|
+
let release = null;
|
|
39044
|
+
try {
|
|
39045
|
+
release = await acquireLock(path17.dirname(filePath));
|
|
39046
|
+
const state = await getQuotaState(directory, opts);
|
|
39047
|
+
if (state.calls_used + opts.nCalls > opts.maxCalls) {
|
|
39048
|
+
return {
|
|
39049
|
+
allowed: false,
|
|
39050
|
+
state,
|
|
39051
|
+
reason: `daily quota exhausted: used=${state.calls_used} requested=${opts.nCalls} max=${opts.maxCalls}`
|
|
39052
|
+
};
|
|
39053
|
+
}
|
|
39054
|
+
const next = {
|
|
39055
|
+
...state,
|
|
39056
|
+
calls_used: state.calls_used + opts.nCalls,
|
|
39057
|
+
max_calls: opts.maxCalls,
|
|
39058
|
+
last_run_at: (opts.now ?? new Date).toISOString()
|
|
39059
|
+
};
|
|
39060
|
+
await writeState(filePath, next);
|
|
39061
|
+
return { allowed: true, state: next };
|
|
39062
|
+
} finally {
|
|
39063
|
+
if (release) {
|
|
39064
|
+
try {
|
|
39065
|
+
await release();
|
|
39066
|
+
} catch {}
|
|
39067
|
+
}
|
|
39068
|
+
}
|
|
39069
|
+
}
|
|
39070
|
+
async function releaseQuota(directory, opts) {
|
|
39071
|
+
const filePath = resolveQuotaPath(directory);
|
|
39072
|
+
await mkdir6(path17.dirname(filePath), { recursive: true });
|
|
39073
|
+
let release = null;
|
|
39074
|
+
try {
|
|
39075
|
+
release = await acquireLock(path17.dirname(filePath));
|
|
39076
|
+
const state = await getQuotaState(directory, opts);
|
|
39077
|
+
const next = {
|
|
39078
|
+
...state,
|
|
39079
|
+
calls_used: Math.max(0, state.calls_used - opts.nCalls),
|
|
39080
|
+
max_calls: opts.maxCalls
|
|
39081
|
+
};
|
|
39082
|
+
await writeState(filePath, next);
|
|
39083
|
+
return next;
|
|
39084
|
+
} finally {
|
|
39085
|
+
if (release) {
|
|
39086
|
+
try {
|
|
39087
|
+
await release();
|
|
39088
|
+
} catch {}
|
|
39089
|
+
}
|
|
39090
|
+
}
|
|
39091
|
+
}
|
|
39092
|
+
var import_proper_lockfile6, LOCK_ACQUIRE_TIMEOUT_MS = 1e4, LOCK_RETRY_OPTS;
|
|
39093
|
+
var init_skill_improver_quota = __esm(() => {
|
|
39094
|
+
import_proper_lockfile6 = __toESM(require_proper_lockfile(), 1);
|
|
39095
|
+
LOCK_RETRY_OPTS = {
|
|
39096
|
+
retries: {
|
|
39097
|
+
retries: 30,
|
|
39098
|
+
minTimeout: 50,
|
|
39099
|
+
maxTimeout: 200,
|
|
39100
|
+
factor: 1.5
|
|
39101
|
+
},
|
|
39102
|
+
stale: 5000
|
|
39103
|
+
};
|
|
39104
|
+
});
|
|
39105
|
+
|
|
39106
|
+
// src/services/skill-reviser.ts
|
|
39107
|
+
var init_skill_reviser = __esm(() => {
|
|
39108
|
+
init_logger();
|
|
39109
|
+
init_skill_changelog();
|
|
39110
|
+
init_skill_improver_quota();
|
|
39111
|
+
});
|
|
39112
|
+
|
|
38300
39113
|
// src/hooks/skill-usage-log.ts
|
|
38301
39114
|
import * as crypto3 from "crypto";
|
|
38302
39115
|
import * as fs9 from "fs";
|
|
38303
|
-
import * as
|
|
39116
|
+
import * as path18 from "path";
|
|
38304
39117
|
function resolveLogPath(directory) {
|
|
38305
39118
|
return validateSwarmPath(directory, "skill-usage.jsonl");
|
|
38306
39119
|
}
|
|
@@ -38312,7 +39125,8 @@ function appendSkillUsageEntry(directory, entry) {
|
|
|
38312
39125
|
timestamp,
|
|
38313
39126
|
complianceVerdict,
|
|
38314
39127
|
sessionID,
|
|
38315
|
-
reviewerNotes
|
|
39128
|
+
reviewerNotes,
|
|
39129
|
+
skillVersion
|
|
38316
39130
|
} = entry;
|
|
38317
39131
|
if (!skillPath || typeof skillPath !== "string") {
|
|
38318
39132
|
throw new Error("skillPath is required and must be a non-empty string");
|
|
@@ -38336,7 +39150,7 @@ function appendSkillUsageEntry(directory, entry) {
|
|
|
38336
39150
|
throw new Error("sessionID is required and must be a non-empty string");
|
|
38337
39151
|
}
|
|
38338
39152
|
const resolved = validateSwarmPath(directory, "skill-usage.jsonl");
|
|
38339
|
-
const dir =
|
|
39153
|
+
const dir = path18.dirname(resolved);
|
|
38340
39154
|
if (!_internals12.existsSync(dir)) {
|
|
38341
39155
|
_internals12.mkdirSync(dir, { recursive: true });
|
|
38342
39156
|
}
|
|
@@ -38348,7 +39162,8 @@ function appendSkillUsageEntry(directory, entry) {
|
|
|
38348
39162
|
timestamp,
|
|
38349
39163
|
complianceVerdict,
|
|
38350
39164
|
sessionID,
|
|
38351
|
-
...reviewerNotes !== undefined && { reviewerNotes }
|
|
39165
|
+
...reviewerNotes !== undefined && { reviewerNotes },
|
|
39166
|
+
...skillVersion !== undefined && { skillVersion }
|
|
38352
39167
|
};
|
|
38353
39168
|
_internals12.appendFileSync(resolved, `${JSON.stringify(fullEntry)}
|
|
38354
39169
|
`, "utf-8");
|
|
@@ -38445,6 +39260,34 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
|
|
|
38445
39260
|
return [];
|
|
38446
39261
|
}
|
|
38447
39262
|
}
|
|
39263
|
+
function computeComplianceByVersion(entries, skillPath) {
|
|
39264
|
+
const map3 = new Map;
|
|
39265
|
+
const normalizedTarget = skillPath.replace(/^file:/, "").replace(/\\/g, "/");
|
|
39266
|
+
for (const e of entries) {
|
|
39267
|
+
let p = e.skillPath;
|
|
39268
|
+
if (p.startsWith("file:"))
|
|
39269
|
+
p = p.slice(5);
|
|
39270
|
+
const normalized = p.replace(/\\/g, "/");
|
|
39271
|
+
if (normalized !== normalizedTarget && !normalizedTarget.endsWith(`/${normalized}`) && !normalized.endsWith(`/${normalizedTarget}`)) {
|
|
39272
|
+
continue;
|
|
39273
|
+
}
|
|
39274
|
+
const version3 = e.skillVersion;
|
|
39275
|
+
let stats = map3.get(version3);
|
|
39276
|
+
if (!stats) {
|
|
39277
|
+
stats = { compliant: 0, violation: 0, total: 0, rate: 0 };
|
|
39278
|
+
map3.set(version3, stats);
|
|
39279
|
+
}
|
|
39280
|
+
stats.total += 1;
|
|
39281
|
+
if (e.complianceVerdict === "compliant")
|
|
39282
|
+
stats.compliant += 1;
|
|
39283
|
+
if (e.complianceVerdict === "violation")
|
|
39284
|
+
stats.violation += 1;
|
|
39285
|
+
}
|
|
39286
|
+
for (const stats of map3.values()) {
|
|
39287
|
+
stats.rate = stats.total === 0 ? 0 : stats.compliant / stats.total;
|
|
39288
|
+
}
|
|
39289
|
+
return map3;
|
|
39290
|
+
}
|
|
38448
39291
|
function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
|
|
38449
39292
|
const resolved = resolveLogPath(directory);
|
|
38450
39293
|
if (!_internals12.existsSync(resolved)) {
|
|
@@ -38477,8 +39320,8 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
|
|
|
38477
39320
|
if (pruned === 0) {
|
|
38478
39321
|
return { pruned: 0, remaining: allEntries.length };
|
|
38479
39322
|
}
|
|
38480
|
-
const dir =
|
|
38481
|
-
const tmpPath =
|
|
39323
|
+
const dir = path18.dirname(resolved);
|
|
39324
|
+
const tmpPath = path18.join(dir, `skill-usage-${Date.now()}.tmp`);
|
|
38482
39325
|
const content = surviving.map((e) => JSON.stringify(e)).join(`
|
|
38483
39326
|
`).concat(`
|
|
38484
39327
|
`);
|
|
@@ -38505,9 +39348,9 @@ async function resolveSourceKnowledgeIds(directory, skillPath) {
|
|
|
38505
39348
|
if (/\.\.[/\\]/.test(cleanPath)) {
|
|
38506
39349
|
return [];
|
|
38507
39350
|
}
|
|
38508
|
-
const absolute =
|
|
38509
|
-
const baseDir =
|
|
38510
|
-
const isContained = process.platform === "win32" ? absolute.toLowerCase().startsWith((baseDir +
|
|
39351
|
+
const absolute = path18.normalize(path18.isAbsolute(cleanPath) ? cleanPath : path18.resolve(directory, cleanPath));
|
|
39352
|
+
const baseDir = path18.normalize(path18.resolve(directory));
|
|
39353
|
+
const isContained = process.platform === "win32" ? absolute.toLowerCase().startsWith((baseDir + path18.sep).toLowerCase()) : absolute.startsWith(baseDir + path18.sep);
|
|
38511
39354
|
if (!isContained) {
|
|
38512
39355
|
return [];
|
|
38513
39356
|
}
|
|
@@ -38626,7 +39469,8 @@ var init_skill_usage_log = __esm(() => {
|
|
|
38626
39469
|
pruneSkillUsageLog,
|
|
38627
39470
|
resolveSourceKnowledgeIds,
|
|
38628
39471
|
applySkillUsageFeedback,
|
|
38629
|
-
parseGeneratedFromKnowledge
|
|
39472
|
+
parseGeneratedFromKnowledge,
|
|
39473
|
+
computeComplianceByVersion
|
|
38630
39474
|
};
|
|
38631
39475
|
TAIL_BYTES_DEFAULT = 64 * 1024;
|
|
38632
39476
|
MAX_TAIL_BYTES = TAIL_BYTES_DEFAULT;
|
|
@@ -38638,7 +39482,9 @@ var init_curator = __esm(() => {
|
|
|
38638
39482
|
init_event_bus();
|
|
38639
39483
|
init_schema();
|
|
38640
39484
|
init_manager();
|
|
39485
|
+
init_learning_metrics();
|
|
38641
39486
|
init_skill_generator();
|
|
39487
|
+
init_skill_reviser();
|
|
38642
39488
|
init_state();
|
|
38643
39489
|
init_bun_compat();
|
|
38644
39490
|
init_logger();
|
|
@@ -38649,7 +39495,7 @@ var init_curator = __esm(() => {
|
|
|
38649
39495
|
});
|
|
38650
39496
|
|
|
38651
39497
|
// src/hooks/hive-promoter.ts
|
|
38652
|
-
import
|
|
39498
|
+
import path19 from "path";
|
|
38653
39499
|
function carryActionableFields(source) {
|
|
38654
39500
|
const out = {};
|
|
38655
39501
|
if (source.triggers?.length)
|
|
@@ -38845,7 +39691,7 @@ async function promoteToHive(directory, lesson, category) {
|
|
|
38845
39691
|
schema_version: 1,
|
|
38846
39692
|
created_at: new Date().toISOString(),
|
|
38847
39693
|
updated_at: new Date().toISOString(),
|
|
38848
|
-
source_project:
|
|
39694
|
+
source_project: path19.basename(directory) || "unknown",
|
|
38849
39695
|
encounter_score: 1
|
|
38850
39696
|
};
|
|
38851
39697
|
await appendKnowledge(resolveHiveKnowledgePath(), newHiveEntry);
|
|
@@ -38901,150 +39747,16 @@ var init_hive_promoter = __esm(() => {
|
|
|
38901
39747
|
init_utils2();
|
|
38902
39748
|
});
|
|
38903
39749
|
|
|
38904
|
-
// src/services/skill-improver-quota.ts
|
|
38905
|
-
import { existsSync as existsSync12 } from "fs";
|
|
38906
|
-
import { mkdir as mkdir5, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
|
|
38907
|
-
import * as path18 from "path";
|
|
38908
|
-
async function acquireLock(dir) {
|
|
38909
|
-
const acquire = import_proper_lockfile5.default.lock(dir, LOCK_RETRY_OPTS);
|
|
38910
|
-
let timer;
|
|
38911
|
-
const timeout = new Promise((_, reject) => {
|
|
38912
|
-
timer = setTimeout(() => {
|
|
38913
|
-
reject(new Error(`SKILL_IMPROVER_QUOTA_LOCK_TIMEOUT: failed to acquire lock on ${dir} within ${LOCK_ACQUIRE_TIMEOUT_MS}ms`));
|
|
38914
|
-
}, LOCK_ACQUIRE_TIMEOUT_MS);
|
|
38915
|
-
});
|
|
38916
|
-
try {
|
|
38917
|
-
const release = await Promise.race([acquire, timeout]);
|
|
38918
|
-
return release;
|
|
38919
|
-
} finally {
|
|
38920
|
-
if (timer)
|
|
38921
|
-
clearTimeout(timer);
|
|
38922
|
-
}
|
|
38923
|
-
}
|
|
38924
|
-
function resolveQuotaPath(directory) {
|
|
38925
|
-
return path18.join(directory, ".swarm", "skill-improver-quota.json");
|
|
38926
|
-
}
|
|
38927
|
-
function todayKey(window, now = new Date) {
|
|
38928
|
-
if (window === "utc") {
|
|
38929
|
-
return now.toISOString().slice(0, 10);
|
|
38930
|
-
}
|
|
38931
|
-
const yr = now.getFullYear();
|
|
38932
|
-
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
38933
|
-
const d = String(now.getDate()).padStart(2, "0");
|
|
38934
|
-
return `${yr}-${m}-${d}`;
|
|
38935
|
-
}
|
|
38936
|
-
async function readState(filePath) {
|
|
38937
|
-
if (!existsSync12(filePath))
|
|
38938
|
-
return null;
|
|
38939
|
-
try {
|
|
38940
|
-
const raw = await readFile4(filePath, "utf-8");
|
|
38941
|
-
const parsed = JSON.parse(raw);
|
|
38942
|
-
if (typeof parsed.date !== "string" || typeof parsed.calls_used !== "number" || typeof parsed.max_calls !== "number" || parsed.window !== "utc" && parsed.window !== "local") {
|
|
38943
|
-
return null;
|
|
38944
|
-
}
|
|
38945
|
-
return parsed;
|
|
38946
|
-
} catch {
|
|
38947
|
-
return null;
|
|
38948
|
-
}
|
|
38949
|
-
}
|
|
38950
|
-
async function writeState(filePath, state) {
|
|
38951
|
-
await mkdir5(path18.dirname(filePath), { recursive: true });
|
|
38952
|
-
const tmp = `${filePath}.tmp-${process.pid}`;
|
|
38953
|
-
await writeFile4(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
38954
|
-
await rename4(tmp, filePath);
|
|
38955
|
-
}
|
|
38956
|
-
async function getQuotaState(directory, opts) {
|
|
38957
|
-
const filePath = resolveQuotaPath(directory);
|
|
38958
|
-
const today = todayKey(opts.window, opts.now);
|
|
38959
|
-
const existing = await readState(filePath);
|
|
38960
|
-
if (!existing || existing.date !== today || existing.window !== opts.window) {
|
|
38961
|
-
const fresh = {
|
|
38962
|
-
date: today,
|
|
38963
|
-
calls_used: 0,
|
|
38964
|
-
max_calls: opts.maxCalls,
|
|
38965
|
-
window: opts.window
|
|
38966
|
-
};
|
|
38967
|
-
await writeState(filePath, fresh);
|
|
38968
|
-
return fresh;
|
|
38969
|
-
}
|
|
38970
|
-
return { ...existing, max_calls: opts.maxCalls };
|
|
38971
|
-
}
|
|
38972
|
-
async function reserveQuota(directory, opts) {
|
|
38973
|
-
const filePath = resolveQuotaPath(directory);
|
|
38974
|
-
await mkdir5(path18.dirname(filePath), { recursive: true });
|
|
38975
|
-
let release = null;
|
|
38976
|
-
try {
|
|
38977
|
-
release = await acquireLock(path18.dirname(filePath));
|
|
38978
|
-
const state = await getQuotaState(directory, opts);
|
|
38979
|
-
if (state.calls_used + opts.nCalls > opts.maxCalls) {
|
|
38980
|
-
return {
|
|
38981
|
-
allowed: false,
|
|
38982
|
-
state,
|
|
38983
|
-
reason: `daily quota exhausted: used=${state.calls_used} requested=${opts.nCalls} max=${opts.maxCalls}`
|
|
38984
|
-
};
|
|
38985
|
-
}
|
|
38986
|
-
const next = {
|
|
38987
|
-
...state,
|
|
38988
|
-
calls_used: state.calls_used + opts.nCalls,
|
|
38989
|
-
max_calls: opts.maxCalls,
|
|
38990
|
-
last_run_at: (opts.now ?? new Date).toISOString()
|
|
38991
|
-
};
|
|
38992
|
-
await writeState(filePath, next);
|
|
38993
|
-
return { allowed: true, state: next };
|
|
38994
|
-
} finally {
|
|
38995
|
-
if (release) {
|
|
38996
|
-
try {
|
|
38997
|
-
await release();
|
|
38998
|
-
} catch {}
|
|
38999
|
-
}
|
|
39000
|
-
}
|
|
39001
|
-
}
|
|
39002
|
-
async function releaseQuota(directory, opts) {
|
|
39003
|
-
const filePath = resolveQuotaPath(directory);
|
|
39004
|
-
await mkdir5(path18.dirname(filePath), { recursive: true });
|
|
39005
|
-
let release = null;
|
|
39006
|
-
try {
|
|
39007
|
-
release = await acquireLock(path18.dirname(filePath));
|
|
39008
|
-
const state = await getQuotaState(directory, opts);
|
|
39009
|
-
const next = {
|
|
39010
|
-
...state,
|
|
39011
|
-
calls_used: Math.max(0, state.calls_used - opts.nCalls),
|
|
39012
|
-
max_calls: opts.maxCalls
|
|
39013
|
-
};
|
|
39014
|
-
await writeState(filePath, next);
|
|
39015
|
-
return next;
|
|
39016
|
-
} finally {
|
|
39017
|
-
if (release) {
|
|
39018
|
-
try {
|
|
39019
|
-
await release();
|
|
39020
|
-
} catch {}
|
|
39021
|
-
}
|
|
39022
|
-
}
|
|
39023
|
-
}
|
|
39024
|
-
var import_proper_lockfile5, LOCK_ACQUIRE_TIMEOUT_MS = 1e4, LOCK_RETRY_OPTS;
|
|
39025
|
-
var init_skill_improver_quota = __esm(() => {
|
|
39026
|
-
import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
|
|
39027
|
-
LOCK_RETRY_OPTS = {
|
|
39028
|
-
retries: {
|
|
39029
|
-
retries: 30,
|
|
39030
|
-
minTimeout: 50,
|
|
39031
|
-
maxTimeout: 200,
|
|
39032
|
-
factor: 1.5
|
|
39033
|
-
},
|
|
39034
|
-
stale: 5000
|
|
39035
|
-
};
|
|
39036
|
-
});
|
|
39037
|
-
|
|
39038
39750
|
// src/services/synonym-map.ts
|
|
39039
39751
|
import {
|
|
39040
|
-
mkdir as
|
|
39041
|
-
readFile as
|
|
39752
|
+
mkdir as mkdir7,
|
|
39753
|
+
readFile as readFile6,
|
|
39042
39754
|
rename as rename5,
|
|
39043
39755
|
stat as stat2,
|
|
39044
39756
|
unlink as unlink2,
|
|
39045
|
-
writeFile as
|
|
39757
|
+
writeFile as writeFile6
|
|
39046
39758
|
} from "fs/promises";
|
|
39047
|
-
import * as
|
|
39759
|
+
import * as path20 from "path";
|
|
39048
39760
|
function emptySynonymMap() {
|
|
39049
39761
|
return { version: 1, cursor: 0, pairs: {} };
|
|
39050
39762
|
}
|
|
@@ -39186,17 +39898,17 @@ async function readSynonymMap(directory, maxPairs = DEFAULT_MAX_PAIRS) {
|
|
|
39186
39898
|
const st = await stat2(filePath);
|
|
39187
39899
|
if (st.size > ceiling)
|
|
39188
39900
|
return emptySynonymMap();
|
|
39189
|
-
const raw = await
|
|
39901
|
+
const raw = await readFile6(filePath, "utf-8");
|
|
39190
39902
|
return coerceSynonymMap(JSON.parse(raw), maxPairs);
|
|
39191
39903
|
} catch {
|
|
39192
39904
|
return emptySynonymMap();
|
|
39193
39905
|
}
|
|
39194
39906
|
}
|
|
39195
39907
|
async function writeSynonymMapAtomic(filePath, map3) {
|
|
39196
|
-
await
|
|
39908
|
+
await mkdir7(path20.dirname(filePath), { recursive: true });
|
|
39197
39909
|
const tmp = `${filePath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
39198
39910
|
try {
|
|
39199
|
-
await
|
|
39911
|
+
await writeFile6(tmp, JSON.stringify(map3, null, 2), "utf-8");
|
|
39200
39912
|
await rename5(tmp, filePath);
|
|
39201
39913
|
} finally {
|
|
39202
39914
|
try {
|
|
@@ -39206,11 +39918,11 @@ async function writeSynonymMapAtomic(filePath, map3) {
|
|
|
39206
39918
|
}
|
|
39207
39919
|
async function rebuildSynonymMap(directory, entries, maxPairs = DEFAULT_MAX_PAIRS) {
|
|
39208
39920
|
const filePath = resolveSynonymMapPath(directory);
|
|
39209
|
-
const dir =
|
|
39210
|
-
await
|
|
39921
|
+
const dir = path20.dirname(filePath);
|
|
39922
|
+
await mkdir7(dir, { recursive: true });
|
|
39211
39923
|
let release = null;
|
|
39212
39924
|
try {
|
|
39213
|
-
release = await
|
|
39925
|
+
release = await import_proper_lockfile7.default.lock(dir, {
|
|
39214
39926
|
retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
|
|
39215
39927
|
stale: 5000
|
|
39216
39928
|
});
|
|
@@ -39225,217 +39937,14 @@ async function rebuildSynonymMap(directory, entries, maxPairs = DEFAULT_MAX_PAIR
|
|
|
39225
39937
|
}
|
|
39226
39938
|
}
|
|
39227
39939
|
}
|
|
39228
|
-
var
|
|
39940
|
+
var import_proper_lockfile7, SYNONYM_MAP_FILENAME = "synonym-map.json", MAX_TOKEN_LENGTH = 64, DEFAULT_MAX_PAIRS = 500, PAIR_SEP, APPROX_BYTES_PER_PAIR = 512, MIN_READ_CEILING_BYTES;
|
|
39229
39941
|
var init_synonym_map = __esm(() => {
|
|
39230
39942
|
init_utils2();
|
|
39231
|
-
|
|
39943
|
+
import_proper_lockfile7 = __toESM(require_proper_lockfile(), 1);
|
|
39232
39944
|
PAIR_SEP = String.fromCharCode(0);
|
|
39233
39945
|
MIN_READ_CEILING_BYTES = 64 * 1024;
|
|
39234
39946
|
});
|
|
39235
39947
|
|
|
39236
|
-
// src/hooks/knowledge-events.ts
|
|
39237
|
-
import { existsSync as existsSync13 } from "fs";
|
|
39238
|
-
import { appendFile as appendFile4, mkdir as mkdir7, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
39239
|
-
import * as path20 from "path";
|
|
39240
|
-
function resolveKnowledgeEventsPath(directory) {
|
|
39241
|
-
return path20.join(directory, ".swarm", "knowledge-events.jsonl");
|
|
39242
|
-
}
|
|
39243
|
-
function resolveLegacyApplicationLogPath(directory) {
|
|
39244
|
-
return path20.join(directory, ".swarm", "knowledge-application.jsonl");
|
|
39245
|
-
}
|
|
39246
|
-
async function readKnowledgeEvents(directory) {
|
|
39247
|
-
const filePath = resolveKnowledgeEventsPath(directory);
|
|
39248
|
-
if (!existsSync13(filePath))
|
|
39249
|
-
return [];
|
|
39250
|
-
const content = await readFile6(filePath, "utf-8");
|
|
39251
|
-
const out = [];
|
|
39252
|
-
for (const line of content.split(`
|
|
39253
|
-
`)) {
|
|
39254
|
-
const trimmed = line.trim();
|
|
39255
|
-
if (!trimmed)
|
|
39256
|
-
continue;
|
|
39257
|
-
try {
|
|
39258
|
-
out.push(JSON.parse(trimmed));
|
|
39259
|
-
} catch {
|
|
39260
|
-
warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
|
|
39261
|
-
}
|
|
39262
|
-
}
|
|
39263
|
-
return out;
|
|
39264
|
-
}
|
|
39265
|
-
async function readLegacyApplicationRecords(directory) {
|
|
39266
|
-
const filePath = resolveLegacyApplicationLogPath(directory);
|
|
39267
|
-
if (!existsSync13(filePath))
|
|
39268
|
-
return [];
|
|
39269
|
-
const content = await readFile6(filePath, "utf-8");
|
|
39270
|
-
const out = [];
|
|
39271
|
-
for (const line of content.split(`
|
|
39272
|
-
`)) {
|
|
39273
|
-
const trimmed = line.trim();
|
|
39274
|
-
if (!trimmed)
|
|
39275
|
-
continue;
|
|
39276
|
-
try {
|
|
39277
|
-
out.push(JSON.parse(trimmed));
|
|
39278
|
-
} catch {
|
|
39279
|
-
warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
|
|
39280
|
-
}
|
|
39281
|
-
}
|
|
39282
|
-
return out;
|
|
39283
|
-
}
|
|
39284
|
-
function emptyRollup() {
|
|
39285
|
-
return {
|
|
39286
|
-
shown_count: 0,
|
|
39287
|
-
acknowledged_count: 0,
|
|
39288
|
-
applied_explicit_count: 0,
|
|
39289
|
-
ignored_count: 0,
|
|
39290
|
-
violated_count: 0,
|
|
39291
|
-
contradicted_count: 0,
|
|
39292
|
-
n_a_count: 0,
|
|
39293
|
-
succeeded_after_shown_count: 0,
|
|
39294
|
-
failed_after_shown_count: 0,
|
|
39295
|
-
partial_after_shown_count: 0,
|
|
39296
|
-
violation_timestamps: []
|
|
39297
|
-
};
|
|
39298
|
-
}
|
|
39299
|
-
function get(map3, id) {
|
|
39300
|
-
let r = map3.get(id);
|
|
39301
|
-
if (!r) {
|
|
39302
|
-
r = emptyRollup();
|
|
39303
|
-
map3.set(id, r);
|
|
39304
|
-
}
|
|
39305
|
-
return r;
|
|
39306
|
-
}
|
|
39307
|
-
function maxIso(current, candidate) {
|
|
39308
|
-
if (!current)
|
|
39309
|
-
return candidate;
|
|
39310
|
-
return candidate > current ? candidate : current;
|
|
39311
|
-
}
|
|
39312
|
-
function recomputeCounters(events, legacyRecords = []) {
|
|
39313
|
-
const map3 = new Map;
|
|
39314
|
-
const retrievedIds = new Set;
|
|
39315
|
-
for (const e of events) {
|
|
39316
|
-
switch (e.type) {
|
|
39317
|
-
case "retrieved": {
|
|
39318
|
-
for (const id of e.result_ids) {
|
|
39319
|
-
retrievedIds.add(id);
|
|
39320
|
-
get(map3, id).shown_count += 1;
|
|
39321
|
-
}
|
|
39322
|
-
break;
|
|
39323
|
-
}
|
|
39324
|
-
case "acknowledged": {
|
|
39325
|
-
const r = get(map3, e.knowledge_id);
|
|
39326
|
-
r.acknowledged_count += 1;
|
|
39327
|
-
r.last_acknowledged_at = maxIso(r.last_acknowledged_at, e.timestamp);
|
|
39328
|
-
break;
|
|
39329
|
-
}
|
|
39330
|
-
case "applied": {
|
|
39331
|
-
const r = get(map3, e.knowledge_id);
|
|
39332
|
-
r.applied_explicit_count += 1;
|
|
39333
|
-
r.last_applied_at = maxIso(r.last_applied_at, e.timestamp);
|
|
39334
|
-
break;
|
|
39335
|
-
}
|
|
39336
|
-
case "ignored":
|
|
39337
|
-
get(map3, e.knowledge_id).ignored_count += 1;
|
|
39338
|
-
break;
|
|
39339
|
-
case "violated": {
|
|
39340
|
-
const r = get(map3, e.knowledge_id);
|
|
39341
|
-
r.violated_count += 1;
|
|
39342
|
-
r.violation_timestamps.push(e.timestamp);
|
|
39343
|
-
break;
|
|
39344
|
-
}
|
|
39345
|
-
case "contradicted":
|
|
39346
|
-
get(map3, e.knowledge_id).contradicted_count += 1;
|
|
39347
|
-
break;
|
|
39348
|
-
case "n_a":
|
|
39349
|
-
get(map3, e.knowledge_id).n_a_count += 1;
|
|
39350
|
-
break;
|
|
39351
|
-
case "outcome": {
|
|
39352
|
-
if (!e.knowledge_id)
|
|
39353
|
-
break;
|
|
39354
|
-
const r = get(map3, e.knowledge_id);
|
|
39355
|
-
if (e.outcome === "success")
|
|
39356
|
-
r.succeeded_after_shown_count += 1;
|
|
39357
|
-
else if (e.outcome === "failure")
|
|
39358
|
-
r.failed_after_shown_count += 1;
|
|
39359
|
-
else if (e.outcome === "partial")
|
|
39360
|
-
r.partial_after_shown_count += 1;
|
|
39361
|
-
break;
|
|
39362
|
-
}
|
|
39363
|
-
}
|
|
39364
|
-
}
|
|
39365
|
-
for (const rec of legacyRecords) {
|
|
39366
|
-
const r = get(map3, rec.knowledgeId);
|
|
39367
|
-
switch (rec.result) {
|
|
39368
|
-
case "shown":
|
|
39369
|
-
if (!retrievedIds.has(rec.knowledgeId))
|
|
39370
|
-
r.shown_count += 1;
|
|
39371
|
-
break;
|
|
39372
|
-
case "acknowledged":
|
|
39373
|
-
r.acknowledged_count += 1;
|
|
39374
|
-
r.last_acknowledged_at = maxIso(r.last_acknowledged_at, rec.timestamp);
|
|
39375
|
-
break;
|
|
39376
|
-
case "applied":
|
|
39377
|
-
r.applied_explicit_count += 1;
|
|
39378
|
-
r.last_applied_at = maxIso(r.last_applied_at, rec.timestamp);
|
|
39379
|
-
break;
|
|
39380
|
-
case "ignored":
|
|
39381
|
-
r.ignored_count += 1;
|
|
39382
|
-
break;
|
|
39383
|
-
case "violated":
|
|
39384
|
-
r.violated_count += 1;
|
|
39385
|
-
r.violation_timestamps.push(rec.timestamp);
|
|
39386
|
-
break;
|
|
39387
|
-
}
|
|
39388
|
-
}
|
|
39389
|
-
for (const r of map3.values()) {
|
|
39390
|
-
if (r.violation_timestamps.length > 1) {
|
|
39391
|
-
r.violation_timestamps.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
|
|
39392
|
-
}
|
|
39393
|
-
if (r.violation_timestamps.length > MAX_VIOLATION_TIMESTAMPS) {
|
|
39394
|
-
r.violation_timestamps = r.violation_timestamps.slice(0, MAX_VIOLATION_TIMESTAMPS);
|
|
39395
|
-
}
|
|
39396
|
-
}
|
|
39397
|
-
return map3;
|
|
39398
|
-
}
|
|
39399
|
-
async function readKnowledgeCounterRollups(directory) {
|
|
39400
|
-
try {
|
|
39401
|
-
const [events, legacyRecords] = await Promise.all([
|
|
39402
|
-
readKnowledgeEvents(directory),
|
|
39403
|
-
readLegacyApplicationRecords(directory)
|
|
39404
|
-
]);
|
|
39405
|
-
return recomputeCounters(events, legacyRecords);
|
|
39406
|
-
} catch (err) {
|
|
39407
|
-
warn(`[knowledge-events] readKnowledgeCounterRollups failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
39408
|
-
return new Map;
|
|
39409
|
-
}
|
|
39410
|
-
}
|
|
39411
|
-
function effectiveRetrievalOutcomes(stored, rollup) {
|
|
39412
|
-
const base = stored ?? {
|
|
39413
|
-
applied_count: 0,
|
|
39414
|
-
succeeded_after_count: 0,
|
|
39415
|
-
failed_after_count: 0
|
|
39416
|
-
};
|
|
39417
|
-
if (!rollup)
|
|
39418
|
-
return base;
|
|
39419
|
-
return {
|
|
39420
|
-
...base,
|
|
39421
|
-
...rollup
|
|
39422
|
-
};
|
|
39423
|
-
}
|
|
39424
|
-
var import_proper_lockfile7, RECEIPT_EVENT_TYPES, MAX_VIOLATION_TIMESTAMPS = 10;
|
|
39425
|
-
var init_knowledge_events = __esm(() => {
|
|
39426
|
-
init_logger();
|
|
39427
|
-
import_proper_lockfile7 = __toESM(require_proper_lockfile(), 1);
|
|
39428
|
-
RECEIPT_EVENT_TYPES = new Set([
|
|
39429
|
-
"acknowledged",
|
|
39430
|
-
"applied",
|
|
39431
|
-
"ignored",
|
|
39432
|
-
"contradicted",
|
|
39433
|
-
"violated",
|
|
39434
|
-
"n_a",
|
|
39435
|
-
"override"
|
|
39436
|
-
]);
|
|
39437
|
-
});
|
|
39438
|
-
|
|
39439
39948
|
// src/hooks/skill-scoring.ts
|
|
39440
39949
|
import * as fs10 from "fs";
|
|
39441
39950
|
import * as path21 from "path";
|
|
@@ -44221,7 +44730,7 @@ async function computeKnowledgeDebug(directory) {
|
|
|
44221
44730
|
try {
|
|
44222
44731
|
const events = await readKnowledgeEvents(directory);
|
|
44223
44732
|
eventCount = events.length;
|
|
44224
|
-
const cutoff = Date.now() -
|
|
44733
|
+
const cutoff = Date.now() - SEVEN_DAYS_MS2;
|
|
44225
44734
|
for (const ev of events) {
|
|
44226
44735
|
eventsByType[ev.type] = (eventsByType[ev.type] ?? 0) + 1;
|
|
44227
44736
|
if (ev.type !== "retrieved")
|
|
@@ -44331,7 +44840,7 @@ async function checkKnowledgeHealth(directory) {
|
|
|
44331
44840
|
}
|
|
44332
44841
|
return { name: "Knowledge health", status: "\u2705", detail: summary };
|
|
44333
44842
|
}
|
|
44334
|
-
var version3,
|
|
44843
|
+
var version3, SEVEN_DAYS_MS2, UNACTIONABLE_BACKLOG_WARN = 100, INSIGHT_BACKLOG_WARN = 50;
|
|
44335
44844
|
var init_knowledge_diagnostics = __esm(() => {
|
|
44336
44845
|
init_package();
|
|
44337
44846
|
init_knowledge_events();
|
|
@@ -44341,7 +44850,7 @@ var init_knowledge_diagnostics = __esm(() => {
|
|
|
44341
44850
|
init_synonym_map();
|
|
44342
44851
|
init_version_check();
|
|
44343
44852
|
({ version: version3 } = package_default);
|
|
44344
|
-
|
|
44853
|
+
SEVEN_DAYS_MS2 = 7 * 24 * 60 * 60 * 1000;
|
|
44345
44854
|
});
|
|
44346
44855
|
|
|
44347
44856
|
// src/services/diagnose-service.ts
|
|
@@ -47958,7 +48467,7 @@ function formatEvidenceEntry(index, entry) {
|
|
|
47958
48467
|
type: entry.type,
|
|
47959
48468
|
verdict: entry.verdict,
|
|
47960
48469
|
verdictIcon: getVerdictEmoji(entry.verdict),
|
|
47961
|
-
agent: entry.agent,
|
|
48470
|
+
agent: entry.agent ?? "",
|
|
47962
48471
|
summary: entry.summary,
|
|
47963
48472
|
timestamp: entry.timestamp,
|
|
47964
48473
|
details
|
|
@@ -49641,7 +50150,7 @@ function inferCategoryFromText(text) {
|
|
|
49641
50150
|
return "process";
|
|
49642
50151
|
return "other";
|
|
49643
50152
|
}
|
|
49644
|
-
function
|
|
50153
|
+
function truncateLesson2(text) {
|
|
49645
50154
|
if (text.length <= 280)
|
|
49646
50155
|
return text;
|
|
49647
50156
|
return `${text.slice(0, 277)}...`;
|
|
@@ -49683,7 +50192,7 @@ var init_knowledge_migrator = __esm(() => {
|
|
|
49683
50192
|
splitIntoSections,
|
|
49684
50193
|
extractBullets,
|
|
49685
50194
|
inferCategoryFromText,
|
|
49686
|
-
truncateLesson,
|
|
50195
|
+
truncateLesson: truncateLesson2,
|
|
49687
50196
|
inferProjectName,
|
|
49688
50197
|
writeSentinel
|
|
49689
50198
|
};
|
|
@@ -49811,6 +50320,34 @@ var init_knowledge = __esm(() => {
|
|
|
49811
50320
|
init_knowledge_validator();
|
|
49812
50321
|
});
|
|
49813
50322
|
|
|
50323
|
+
// src/commands/learning.ts
|
|
50324
|
+
async function handleLearningCommand(directory, args) {
|
|
50325
|
+
try {
|
|
50326
|
+
const jsonMode = args.includes("--json");
|
|
50327
|
+
let currentPhase;
|
|
50328
|
+
const phaseIdx = args.indexOf("--phase");
|
|
50329
|
+
if (phaseIdx !== -1 && phaseIdx + 1 < args.length) {
|
|
50330
|
+
const parsed = Number(args[phaseIdx + 1]);
|
|
50331
|
+
if (Number.isFinite(parsed)) {
|
|
50332
|
+
currentPhase = parsed;
|
|
50333
|
+
}
|
|
50334
|
+
}
|
|
50335
|
+
const metrics = await computeLearningMetrics(directory, { currentPhase });
|
|
50336
|
+
if (jsonMode) {
|
|
50337
|
+
return `[LEARNING_JSON]
|
|
50338
|
+
${JSON.stringify(formatLearningJSON(metrics), null, 2)}
|
|
50339
|
+
[/LEARNING_JSON]`;
|
|
50340
|
+
}
|
|
50341
|
+
return formatLearningMarkdown(metrics);
|
|
50342
|
+
} catch (err) {
|
|
50343
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50344
|
+
return `Error computing learning metrics: ${message}. Run /swarm diagnose to check .swarm/ health.`;
|
|
50345
|
+
}
|
|
50346
|
+
}
|
|
50347
|
+
var init_learning = __esm(() => {
|
|
50348
|
+
init_learning_metrics();
|
|
50349
|
+
});
|
|
50350
|
+
|
|
49814
50351
|
// src/memory/config.ts
|
|
49815
50352
|
function resolveMemoryConfig(input) {
|
|
49816
50353
|
return {
|
|
@@ -61127,38 +61664,6 @@ async function handleSpecifyCommand(_directory, args) {
|
|
|
61127
61664
|
return "[MODE: SPECIFY] Please enter MODE: SPECIFY and generate a spec for this project.";
|
|
61128
61665
|
}
|
|
61129
61666
|
|
|
61130
|
-
// src/hooks/knowledge-escalator.ts
|
|
61131
|
-
async function readRecentEscalations(directory, windowDays = ESCALATION_DISPLAY_WINDOW_DAYS, now = new Date) {
|
|
61132
|
-
try {
|
|
61133
|
-
const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;
|
|
61134
|
-
const events = await readKnowledgeEvents(directory);
|
|
61135
|
-
const out = [];
|
|
61136
|
-
for (const e of events) {
|
|
61137
|
-
if (e.type !== "escalation")
|
|
61138
|
-
continue;
|
|
61139
|
-
const t = Date.parse(e.timestamp);
|
|
61140
|
-
if (Number.isNaN(t) || t < cutoff)
|
|
61141
|
-
continue;
|
|
61142
|
-
out.push({
|
|
61143
|
-
entry_id: e.entry_id,
|
|
61144
|
-
from: e.from,
|
|
61145
|
-
to: e.to,
|
|
61146
|
-
reason: e.reason,
|
|
61147
|
-
at: e.timestamp
|
|
61148
|
-
});
|
|
61149
|
-
}
|
|
61150
|
-
out.sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
|
|
61151
|
-
return out;
|
|
61152
|
-
} catch {
|
|
61153
|
-
return [];
|
|
61154
|
-
}
|
|
61155
|
-
}
|
|
61156
|
-
var ESCALATION_DISPLAY_WINDOW_DAYS = 7;
|
|
61157
|
-
var init_knowledge_escalator = __esm(() => {
|
|
61158
|
-
init_knowledge_events();
|
|
61159
|
-
init_knowledge_store();
|
|
61160
|
-
});
|
|
61161
|
-
|
|
61162
61667
|
// src/turbo/lean/state.ts
|
|
61163
61668
|
import * as fs34 from "fs";
|
|
61164
61669
|
import * as path61 from "path";
|
|
@@ -62304,6 +62809,7 @@ __export(exports_commands, {
|
|
|
62304
62809
|
handleMemoryImportCommand: () => handleMemoryImportCommand,
|
|
62305
62810
|
handleMemoryExportCommand: () => handleMemoryExportCommand,
|
|
62306
62811
|
handleMemoryCommand: () => handleMemoryCommand,
|
|
62812
|
+
handleLearningCommand: () => handleLearningCommand,
|
|
62307
62813
|
handleKnowledgeRestoreCommand: () => handleKnowledgeRestoreCommand,
|
|
62308
62814
|
handleKnowledgeQuarantineCommand: () => handleKnowledgeQuarantineCommand,
|
|
62309
62815
|
handleKnowledgeMigrateCommand: () => handleKnowledgeMigrateCommand,
|
|
@@ -62603,6 +63109,7 @@ var init_commands = __esm(() => {
|
|
|
62603
63109
|
init_handoff();
|
|
62604
63110
|
init_history();
|
|
62605
63111
|
init_knowledge();
|
|
63112
|
+
init_learning();
|
|
62606
63113
|
init_memory2();
|
|
62607
63114
|
init_plan();
|
|
62608
63115
|
init_preflight();
|
|
@@ -62836,6 +63343,7 @@ var init_registry = __esm(() => {
|
|
|
62836
63343
|
init_history();
|
|
62837
63344
|
init_issue();
|
|
62838
63345
|
init_knowledge();
|
|
63346
|
+
init_learning();
|
|
62839
63347
|
init_memory2();
|
|
62840
63348
|
init_plan();
|
|
62841
63349
|
init_pr_feedback();
|
|
@@ -62956,6 +63464,13 @@ var init_registry = __esm(() => {
|
|
|
62956
63464
|
args: "--cumulative, --ci-gate",
|
|
62957
63465
|
category: "diagnostics"
|
|
62958
63466
|
},
|
|
63467
|
+
learning: {
|
|
63468
|
+
handler: (ctx) => handleLearningCommand(ctx.directory, ctx.args),
|
|
63469
|
+
description: "Show learning metrics and violation trends",
|
|
63470
|
+
args: "--json, --phase <N>",
|
|
63471
|
+
details: "Computes aggregate learning metrics from knowledge events: violation-rate trends, directive application rates, escalation frequency, per-entry ROI, and never-applied entries. Surfaces a learning summary for the curator digest.",
|
|
63472
|
+
category: "diagnostics"
|
|
63473
|
+
},
|
|
62959
63474
|
export: {
|
|
62960
63475
|
handler: (ctx) => handleExportCommand(ctx.directory, ctx.args),
|
|
62961
63476
|
description: "Export plan and context as JSON",
|