opencode-swarm 7.73.1 → 7.73.3
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/.opencode/skills/swarm-pr-feedback/SKILL.md +19 -0
- package/dist/cli/index.js +282 -202
- package/dist/commands/_shared/url-security.d.ts +44 -0
- package/dist/commands/issue.d.ts +1 -1
- package/dist/commands/pr-ref.d.ts +2 -45
- package/dist/hooks/knowledge-curator.d.ts +1 -0
- package/dist/hooks/knowledge-reinforcement.d.ts +10 -0
- package/dist/hooks/skill-usage-log.d.ts +13 -1
- package/dist/index.js +345 -223
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ var package_default;
|
|
|
69
69
|
var init_package = __esm(() => {
|
|
70
70
|
package_default = {
|
|
71
71
|
name: "opencode-swarm",
|
|
72
|
-
version: "7.73.
|
|
72
|
+
version: "7.73.3",
|
|
73
73
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
74
74
|
main: "dist/index.js",
|
|
75
75
|
types: "dist/index.d.ts",
|
|
@@ -62433,6 +62433,9 @@ import * as path37 from "node:path";
|
|
|
62433
62433
|
function resolveLogPath(directory) {
|
|
62434
62434
|
return validateSwarmPath(directory, "skill-usage.jsonl");
|
|
62435
62435
|
}
|
|
62436
|
+
function normalizeComplianceVerdict(verdict) {
|
|
62437
|
+
return verdict === "violation" ? "violated" : verdict;
|
|
62438
|
+
}
|
|
62436
62439
|
function appendSkillUsageEntry(directory, entry) {
|
|
62437
62440
|
const {
|
|
62438
62441
|
skillPath,
|
|
@@ -62503,7 +62506,9 @@ function readSkillUsageEntries(directory, options) {
|
|
|
62503
62506
|
if (!trimmed)
|
|
62504
62507
|
continue;
|
|
62505
62508
|
try {
|
|
62506
|
-
|
|
62509
|
+
const entry = JSON.parse(trimmed);
|
|
62510
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
62511
|
+
entries.push(entry);
|
|
62507
62512
|
} catch {}
|
|
62508
62513
|
}
|
|
62509
62514
|
if (!options)
|
|
@@ -62562,6 +62567,7 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
|
|
|
62562
62567
|
continue;
|
|
62563
62568
|
try {
|
|
62564
62569
|
const entry = JSON.parse(line);
|
|
62570
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
62565
62571
|
if (filters.sessionID !== undefined && entry.sessionID !== filters.sessionID) {
|
|
62566
62572
|
continue;
|
|
62567
62573
|
}
|
|
@@ -62596,7 +62602,7 @@ function computeComplianceByVersion(entries, skillPath) {
|
|
|
62596
62602
|
stats.total += 1;
|
|
62597
62603
|
if (e.complianceVerdict === "compliant")
|
|
62598
62604
|
stats.compliant += 1;
|
|
62599
|
-
if (e.complianceVerdict === "
|
|
62605
|
+
if (e.complianceVerdict === "violated")
|
|
62600
62606
|
stats.violation += 1;
|
|
62601
62607
|
}
|
|
62602
62608
|
for (const stats of map3.values()) {
|
|
@@ -62709,7 +62715,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
62709
62715
|
try {
|
|
62710
62716
|
const allEntries = readSkillUsageEntries(directory);
|
|
62711
62717
|
const actionable = allEntries.filter((e) => {
|
|
62712
|
-
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "
|
|
62718
|
+
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "violated") {
|
|
62713
62719
|
return false;
|
|
62714
62720
|
}
|
|
62715
62721
|
if (options?.sinceTimestamp && e.timestamp <= options.sinceTimestamp) {
|
|
@@ -62735,7 +62741,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
62735
62741
|
for (const entry of entries) {
|
|
62736
62742
|
if (entry.complianceVerdict === "compliant")
|
|
62737
62743
|
compliantCount++;
|
|
62738
|
-
else if (entry.complianceVerdict === "
|
|
62744
|
+
else if (entry.complianceVerdict === "violated")
|
|
62739
62745
|
violationCount++;
|
|
62740
62746
|
}
|
|
62741
62747
|
if (compliantCount === 0 && violationCount === 0)
|
|
@@ -62819,7 +62825,7 @@ async function autoRetireSkills(directory, curatorKnowledgePath, excludeSlugs) {
|
|
|
62819
62825
|
return true;
|
|
62820
62826
|
return false;
|
|
62821
62827
|
});
|
|
62822
|
-
const violations = skillUsage.filter((e) => e.complianceVerdict === "
|
|
62828
|
+
const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
|
|
62823
62829
|
const violationRate = skillUsage.length > 0 ? violations / skillUsage.length : 0;
|
|
62824
62830
|
let allArchived = false;
|
|
62825
62831
|
try {
|
|
@@ -63484,7 +63490,7 @@ ${phaseDigest.summary}`,
|
|
|
63484
63490
|
});
|
|
63485
63491
|
if (skillUsage.length === 0)
|
|
63486
63492
|
continue;
|
|
63487
|
-
const violations = skillUsage.filter((e) => e.complianceVerdict === "
|
|
63493
|
+
const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
|
|
63488
63494
|
const violationRate = violations / skillUsage.length;
|
|
63489
63495
|
if (violationRate > REVISION_VIOLATION_THRESHOLD && violationRate <= 0.3) {
|
|
63490
63496
|
const content = await _internals27.readFileAsync(active.path, "utf-8");
|
|
@@ -63492,7 +63498,7 @@ ${phaseDigest.summary}`,
|
|
|
63492
63498
|
if (fm && fm.skillOrigin === "promoted_external")
|
|
63493
63499
|
continue;
|
|
63494
63500
|
const currentVersion = fm?.version ?? 1;
|
|
63495
|
-
const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "
|
|
63501
|
+
const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "violated").slice(-10).map((e) => ({
|
|
63496
63502
|
taskId: e.taskID,
|
|
63497
63503
|
agent: e.agentName,
|
|
63498
63504
|
verdict: e.complianceVerdict,
|
|
@@ -64285,6 +64291,49 @@ var init_synonym_map = __esm(() => {
|
|
|
64285
64291
|
MIN_READ_CEILING_BYTES = 64 * 1024;
|
|
64286
64292
|
});
|
|
64287
64293
|
|
|
64294
|
+
// src/hooks/knowledge-reinforcement.ts
|
|
64295
|
+
function isActiveSwarmKnowledgeEntry(entry) {
|
|
64296
|
+
return !INACTIVE_STATUSES.has(entry.status);
|
|
64297
|
+
}
|
|
64298
|
+
function findActiveSwarmNearDuplicate(lesson, entries, threshold) {
|
|
64299
|
+
return findNearDuplicate(lesson, entries.filter(isActiveSwarmKnowledgeEntry), threshold);
|
|
64300
|
+
}
|
|
64301
|
+
function distinctPhaseCount(records) {
|
|
64302
|
+
const phases = new Set;
|
|
64303
|
+
for (const record3 of records ?? []) {
|
|
64304
|
+
if (Number.isInteger(record3.phase_number)) {
|
|
64305
|
+
phases.add(record3.phase_number);
|
|
64306
|
+
}
|
|
64307
|
+
}
|
|
64308
|
+
return phases.size;
|
|
64309
|
+
}
|
|
64310
|
+
function reinforceSwarmKnowledgeEntry(entry, confirmation) {
|
|
64311
|
+
if (!isActiveSwarmKnowledgeEntry(entry)) {
|
|
64312
|
+
return { entryId: entry.id, reinforced: false, reason: "inactive" };
|
|
64313
|
+
}
|
|
64314
|
+
if ((entry.confirmed_by ?? []).some((record3) => record3.phase_number === confirmation.phase_number)) {
|
|
64315
|
+
return {
|
|
64316
|
+
entryId: entry.id,
|
|
64317
|
+
reinforced: false,
|
|
64318
|
+
reason: "already_confirmed_phase"
|
|
64319
|
+
};
|
|
64320
|
+
}
|
|
64321
|
+
entry.confirmed_by = [...entry.confirmed_by ?? [], confirmation];
|
|
64322
|
+
entry.updated_at = confirmation.confirmed_at;
|
|
64323
|
+
entry.phases_alive = 0;
|
|
64324
|
+
entry.confidence = computeConfidence(distinctPhaseCount(entry.confirmed_by), entry.auto_generated ?? false);
|
|
64325
|
+
return { entryId: entry.id, reinforced: true, reason: "reinforced" };
|
|
64326
|
+
}
|
|
64327
|
+
var INACTIVE_STATUSES;
|
|
64328
|
+
var init_knowledge_reinforcement = __esm(() => {
|
|
64329
|
+
init_knowledge_store();
|
|
64330
|
+
INACTIVE_STATUSES = new Set([
|
|
64331
|
+
"archived",
|
|
64332
|
+
"quarantined",
|
|
64333
|
+
"quarantined_unactionable"
|
|
64334
|
+
]);
|
|
64335
|
+
});
|
|
64336
|
+
|
|
64288
64337
|
// src/hooks/skill-scoring.ts
|
|
64289
64338
|
import * as fs21 from "node:fs";
|
|
64290
64339
|
import * as path41 from "node:path";
|
|
@@ -65838,6 +65887,7 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
65838
65887
|
]);
|
|
65839
65888
|
const snapshotPlusNew = [...snapshot];
|
|
65840
65889
|
const toAdd = [];
|
|
65890
|
+
const pendingReinforcementIds = new Set;
|
|
65841
65891
|
for (const lesson of lessons) {
|
|
65842
65892
|
const tags = inferTags(lesson);
|
|
65843
65893
|
let category = "process";
|
|
@@ -65867,8 +65917,9 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
65867
65917
|
continue;
|
|
65868
65918
|
}
|
|
65869
65919
|
}
|
|
65870
|
-
const duplicate =
|
|
65920
|
+
const duplicate = findActiveSwarmNearDuplicate(lesson, snapshotPlusNew, config3.dedup_threshold);
|
|
65871
65921
|
if (duplicate) {
|
|
65922
|
+
pendingReinforcementIds.add(duplicate.id);
|
|
65872
65923
|
skipped++;
|
|
65873
65924
|
continue;
|
|
65874
65925
|
}
|
|
@@ -65949,7 +66000,9 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
65949
66000
|
} catch {}
|
|
65950
66001
|
continue;
|
|
65951
66002
|
}
|
|
65952
|
-
|
|
66003
|
+
const duplicate = findActiveSwarmNearDuplicate(entry.lesson, snapshotPlusNew, config3.dedup_threshold);
|
|
66004
|
+
if (duplicate) {
|
|
66005
|
+
pendingReinforcementIds.add(duplicate.id);
|
|
65953
66006
|
skipped++;
|
|
65954
66007
|
continue;
|
|
65955
66008
|
}
|
|
@@ -65958,15 +66011,48 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
65958
66011
|
}
|
|
65959
66012
|
} catch {}
|
|
65960
66013
|
let stored = 0;
|
|
65961
|
-
|
|
66014
|
+
let reinforced = 0;
|
|
66015
|
+
if (toAdd.length > 0 || pendingReinforcementIds.size > 0) {
|
|
65962
66016
|
await transactKnowledge(knowledgePath, (current) => {
|
|
65963
|
-
|
|
65964
|
-
const
|
|
65965
|
-
|
|
65966
|
-
|
|
65967
|
-
|
|
65968
|
-
|
|
65969
|
-
|
|
66017
|
+
let changed = false;
|
|
66018
|
+
for (const id of pendingReinforcementIds) {
|
|
66019
|
+
const existing = current.find((entry) => entry.id === id);
|
|
66020
|
+
if (!existing)
|
|
66021
|
+
continue;
|
|
66022
|
+
const result = reinforceSwarmKnowledgeEntry(existing, {
|
|
66023
|
+
phase_number: phaseInfo.phase_number,
|
|
66024
|
+
confirmed_at: new Date().toISOString(),
|
|
66025
|
+
project_name: projectName
|
|
66026
|
+
});
|
|
66027
|
+
if (result.reinforced) {
|
|
66028
|
+
reinforced++;
|
|
66029
|
+
changed = true;
|
|
66030
|
+
}
|
|
66031
|
+
}
|
|
66032
|
+
const trulyNew = [];
|
|
66033
|
+
for (const entry of toAdd) {
|
|
66034
|
+
const duplicate = findActiveSwarmNearDuplicate(entry.lesson, current, config3.dedup_threshold);
|
|
66035
|
+
if (duplicate) {
|
|
66036
|
+
skipped++;
|
|
66037
|
+
const result = reinforceSwarmKnowledgeEntry(duplicate, {
|
|
66038
|
+
phase_number: phaseInfo.phase_number,
|
|
66039
|
+
confirmed_at: new Date().toISOString(),
|
|
66040
|
+
project_name: projectName
|
|
66041
|
+
});
|
|
66042
|
+
if (result.reinforced) {
|
|
66043
|
+
reinforced++;
|
|
66044
|
+
changed = true;
|
|
66045
|
+
}
|
|
66046
|
+
continue;
|
|
66047
|
+
}
|
|
66048
|
+
trulyNew.push(entry);
|
|
66049
|
+
}
|
|
66050
|
+
if (trulyNew.length > 0) {
|
|
66051
|
+
current.push(...trulyNew);
|
|
66052
|
+
stored = trulyNew.length;
|
|
66053
|
+
changed = true;
|
|
66054
|
+
}
|
|
66055
|
+
return changed ? current : null;
|
|
65970
66056
|
});
|
|
65971
66057
|
}
|
|
65972
66058
|
await enforceKnowledgeCap(knowledgePath, config3.swarm_max_entries);
|
|
@@ -65984,7 +66070,7 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
65984
66070
|
if (!options?.skipAutoPromotion) {
|
|
65985
66071
|
await _internals30.runAutoPromotion(directory, config3);
|
|
65986
66072
|
}
|
|
65987
|
-
return { stored, skipped, rejected, quarantined };
|
|
66073
|
+
return { stored, reinforced, skipped, rejected, quarantined };
|
|
65988
66074
|
}
|
|
65989
66075
|
async function runAutoPromotion(directory, config3) {
|
|
65990
66076
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
@@ -66097,6 +66183,7 @@ var init_knowledge_curator = __esm(() => {
|
|
|
66097
66183
|
init_synonym_map();
|
|
66098
66184
|
init_logger();
|
|
66099
66185
|
init_knowledge_events();
|
|
66186
|
+
init_knowledge_reinforcement();
|
|
66100
66187
|
init_knowledge_store();
|
|
66101
66188
|
init_knowledge_validator();
|
|
66102
66189
|
init_micro_reflector();
|
|
@@ -75112,8 +75199,8 @@ var init_history = __esm(() => {
|
|
|
75112
75199
|
init_history_service();
|
|
75113
75200
|
});
|
|
75114
75201
|
|
|
75115
|
-
// src/commands/
|
|
75116
|
-
import {
|
|
75202
|
+
// src/commands/_shared/url-security.ts
|
|
75203
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
75117
75204
|
function sanitizeUrl(raw) {
|
|
75118
75205
|
let urlStr = raw.trim();
|
|
75119
75206
|
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
@@ -75131,13 +75218,29 @@ function sanitizeUrl(raw) {
|
|
|
75131
75218
|
}
|
|
75132
75219
|
return urlStr.trim();
|
|
75133
75220
|
}
|
|
75134
|
-
function
|
|
75135
|
-
|
|
75136
|
-
const
|
|
75137
|
-
|
|
75138
|
-
|
|
75139
|
-
|
|
75140
|
-
|
|
75221
|
+
function sanitizeErrorEcho(raw, maxLength = 80) {
|
|
75222
|
+
let stripped = "";
|
|
75223
|
+
for (const ch of raw) {
|
|
75224
|
+
const cp = ch.codePointAt(0);
|
|
75225
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
75226
|
+
stripped += " ";
|
|
75227
|
+
continue;
|
|
75228
|
+
}
|
|
75229
|
+
stripped += ch;
|
|
75230
|
+
}
|
|
75231
|
+
const collapsed = stripped.replace(/\s+/g, " ").trim();
|
|
75232
|
+
if (collapsed.length <= maxLength)
|
|
75233
|
+
return collapsed;
|
|
75234
|
+
return `${collapsed.slice(0, maxLength)}…`;
|
|
75235
|
+
}
|
|
75236
|
+
function containsControlCharacters(value) {
|
|
75237
|
+
for (const ch of value) {
|
|
75238
|
+
const cp = ch.codePointAt(0);
|
|
75239
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
75240
|
+
return true;
|
|
75241
|
+
}
|
|
75242
|
+
}
|
|
75243
|
+
return false;
|
|
75141
75244
|
}
|
|
75142
75245
|
function hasNonAsciiHostname(hostname5) {
|
|
75143
75246
|
for (const ch of hostname5) {
|
|
@@ -75147,31 +75250,38 @@ function hasNonAsciiHostname(hostname5) {
|
|
|
75147
75250
|
}
|
|
75148
75251
|
return false;
|
|
75149
75252
|
}
|
|
75253
|
+
function isIpv4MappedPrivateHost(inner) {
|
|
75254
|
+
if (IPV4_PRIVATE.test(inner) || IPV4_LOOPBACK.test(inner) || IPV4_LINK_LOCAL.test(inner) || IPV4_PRIVATE_172.test(inner) || IPV4_PRIVATE_192.test(inner) || IPV4_ZERO_NETWORK.test(inner)) {
|
|
75255
|
+
return true;
|
|
75256
|
+
}
|
|
75257
|
+
const firstSegment = inner.split(":", 1)[0];
|
|
75258
|
+
if (!firstSegment)
|
|
75259
|
+
return false;
|
|
75260
|
+
const firstWord = Number.parseInt(firstSegment, 16);
|
|
75261
|
+
if (!Number.isFinite(firstWord))
|
|
75262
|
+
return false;
|
|
75263
|
+
return firstWord >= 0 && firstWord <= 255 || firstWord >= 2560 && firstWord <= 2815 || firstWord >= 32512 && firstWord <= 32767 || firstWord === 43518 || firstWord >= 44048 && firstWord <= 44063 || firstWord === 49320;
|
|
75264
|
+
}
|
|
75150
75265
|
function isPrivateHost(url3) {
|
|
75151
|
-
const host = url3.hostname.toLowerCase();
|
|
75152
|
-
if (host === "localhost" || host === "
|
|
75266
|
+
const host = url3.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
75267
|
+
if (host === "localhost" || host === "::1" || host === "0.0.0.0" || IPV4_LOOPBACK.test(host) || IPV4_ZERO_NETWORK.test(host)) {
|
|
75153
75268
|
return true;
|
|
75154
75269
|
}
|
|
75155
75270
|
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
75156
75271
|
return true;
|
|
75157
75272
|
}
|
|
75158
|
-
|
|
75159
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
75160
|
-
const ipv4192 = /^192\.168\./;
|
|
75161
|
-
const ipv6Private = /^fe80:/i;
|
|
75162
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
75163
|
-
if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
|
|
75273
|
+
if (IPV4_PRIVATE.test(host) || IPV4_LINK_LOCAL.test(host) || IPV4_PRIVATE_172.test(host) || IPV4_PRIVATE_192.test(host) || IPV6_LINK_LOCAL.test(host) || IPV6_UNIQUE_LOCAL.test(host)) {
|
|
75164
75274
|
return true;
|
|
75165
75275
|
}
|
|
75166
75276
|
if (host.startsWith("::ffff:")) {
|
|
75167
75277
|
const inner = host.slice(7);
|
|
75168
|
-
if (
|
|
75278
|
+
if (isIpv4MappedPrivateHost(inner)) {
|
|
75169
75279
|
return true;
|
|
75170
75280
|
}
|
|
75171
75281
|
}
|
|
75172
75282
|
return false;
|
|
75173
75283
|
}
|
|
75174
|
-
function
|
|
75284
|
+
function validateAndSanitizeGithubUrl(rawUrl, resource) {
|
|
75175
75285
|
const sanitized = sanitizeUrl(rawUrl);
|
|
75176
75286
|
if (!sanitized) {
|
|
75177
75287
|
return { error: "Empty URL" };
|
|
@@ -75187,10 +75297,10 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
75187
75297
|
if (isPrivateHost(url3)) {
|
|
75188
75298
|
return { error: "Private or localhost URLs are not allowed" };
|
|
75189
75299
|
}
|
|
75190
|
-
const
|
|
75191
|
-
if (!
|
|
75300
|
+
const githubPattern = new RegExp(`^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/${resource}\\/([0-9]+)\\/?$`);
|
|
75301
|
+
if (!githubPattern.test(sanitized)) {
|
|
75192
75302
|
return {
|
|
75193
|
-
error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
|
|
75303
|
+
error: resource === "issues" ? "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)" : "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
|
|
75194
75304
|
};
|
|
75195
75305
|
}
|
|
75196
75306
|
return { sanitized };
|
|
@@ -75198,50 +75308,18 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
75198
75308
|
return { error: "Invalid URL format" };
|
|
75199
75309
|
}
|
|
75200
75310
|
}
|
|
75201
|
-
function parsePrRef(input, cwd) {
|
|
75202
|
-
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
75203
|
-
if (urlMatch) {
|
|
75204
|
-
return {
|
|
75205
|
-
owner: urlMatch[1],
|
|
75206
|
-
repo: urlMatch[2],
|
|
75207
|
-
number: parseInt(urlMatch[3], 10)
|
|
75208
|
-
};
|
|
75209
|
-
}
|
|
75210
|
-
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
75211
|
-
if (shorthandMatch) {
|
|
75212
|
-
return {
|
|
75213
|
-
owner: shorthandMatch[1],
|
|
75214
|
-
repo: shorthandMatch[2],
|
|
75215
|
-
number: parseInt(shorthandMatch[3], 10)
|
|
75216
|
-
};
|
|
75217
|
-
}
|
|
75218
|
-
const bareMatch = input.match(/^(\d+)$/);
|
|
75219
|
-
if (bareMatch) {
|
|
75220
|
-
const prNumber = parseInt(bareMatch[1], 10);
|
|
75221
|
-
const remoteUrl = detectGitRemote(cwd);
|
|
75222
|
-
if (!remoteUrl) {
|
|
75223
|
-
return null;
|
|
75224
|
-
}
|
|
75225
|
-
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
75226
|
-
if (!parsed) {
|
|
75227
|
-
return null;
|
|
75228
|
-
}
|
|
75229
|
-
return {
|
|
75230
|
-
owner: parsed.owner,
|
|
75231
|
-
repo: parsed.repo,
|
|
75232
|
-
number: prNumber
|
|
75233
|
-
};
|
|
75234
|
-
}
|
|
75235
|
-
return null;
|
|
75236
|
-
}
|
|
75237
75311
|
function detectGitRemote(cwd) {
|
|
75238
75312
|
try {
|
|
75239
|
-
const
|
|
75313
|
+
const result = _internals39.spawnSync("git", ["remote", "get-url", "origin"], {
|
|
75240
75314
|
encoding: "utf-8",
|
|
75241
|
-
stdio: ["
|
|
75315
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
75242
75316
|
timeout: 5000,
|
|
75243
75317
|
...cwd ? { cwd } : {}
|
|
75244
|
-
})
|
|
75318
|
+
});
|
|
75319
|
+
if (result.status !== 0 || result.error) {
|
|
75320
|
+
return null;
|
|
75321
|
+
}
|
|
75322
|
+
const remoteUrl = (result.stdout ?? "").trim();
|
|
75245
75323
|
return remoteUrl || null;
|
|
75246
75324
|
} catch {
|
|
75247
75325
|
return null;
|
|
@@ -75250,123 +75328,49 @@ function detectGitRemote(cwd) {
|
|
|
75250
75328
|
function parseGitRemoteUrl(remoteUrl) {
|
|
75251
75329
|
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
|
|
75252
75330
|
if (httpsMatch) {
|
|
75253
|
-
|
|
75254
|
-
|
|
75255
|
-
|
|
75256
|
-
|
|
75331
|
+
const owner = httpsMatch[1];
|
|
75332
|
+
const repo = httpsMatch[2].replace(/\.git$/, "");
|
|
75333
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75334
|
+
return null;
|
|
75335
|
+
}
|
|
75336
|
+
return { owner, repo };
|
|
75257
75337
|
}
|
|
75258
75338
|
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
75259
75339
|
if (sshMatch) {
|
|
75260
|
-
|
|
75261
|
-
|
|
75262
|
-
|
|
75263
|
-
|
|
75340
|
+
const owner = sshMatch[1];
|
|
75341
|
+
const repo = sshMatch[2].replace(/\.git$/, "");
|
|
75342
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75343
|
+
return null;
|
|
75344
|
+
}
|
|
75345
|
+
return { owner, repo };
|
|
75264
75346
|
}
|
|
75265
75347
|
const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
75266
75348
|
if (pathMatch) {
|
|
75267
|
-
|
|
75268
|
-
|
|
75269
|
-
|
|
75270
|
-
|
|
75349
|
+
const owner = pathMatch[1];
|
|
75350
|
+
const repo = pathMatch[2].replace(/\.git$/, "");
|
|
75351
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75352
|
+
return null;
|
|
75353
|
+
}
|
|
75354
|
+
return { owner, repo };
|
|
75271
75355
|
}
|
|
75272
75356
|
return null;
|
|
75273
75357
|
}
|
|
75274
|
-
|
|
75275
|
-
|
|
75276
|
-
|
|
75277
|
-
|
|
75278
|
-
|
|
75279
|
-
|
|
75280
|
-
|
|
75281
|
-
|
|
75282
|
-
|
|
75283
|
-
|
|
75284
|
-
|
|
75285
|
-
if (!prInfo) {
|
|
75286
|
-
return { error: `Could not parse PR reference from "${refToken}"` };
|
|
75287
|
-
}
|
|
75288
|
-
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
75289
|
-
const result = validateAndSanitizeUrl(prUrl);
|
|
75290
|
-
if ("error" in result) {
|
|
75291
|
-
return { error: result.error };
|
|
75292
|
-
}
|
|
75293
|
-
return { prUrl: result.sanitized, instructions };
|
|
75294
|
-
}
|
|
75295
|
-
var _internals39, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
|
|
75296
|
-
var init_pr_ref = __esm(() => {
|
|
75297
|
-
_internals39 = { execSync: execSync2 };
|
|
75358
|
+
var MAX_URL_LEN = 2048, IPV4_PRIVATE, IPV4_LOOPBACK, IPV4_LINK_LOCAL, IPV4_PRIVATE_172, IPV4_PRIVATE_192, IPV4_ZERO_NETWORK, IPV6_LINK_LOCAL, IPV6_UNIQUE_LOCAL, _internals39;
|
|
75359
|
+
var init_url_security = __esm(() => {
|
|
75360
|
+
IPV4_PRIVATE = /^10\./;
|
|
75361
|
+
IPV4_LOOPBACK = /^127\./;
|
|
75362
|
+
IPV4_LINK_LOCAL = /^169\.254\./;
|
|
75363
|
+
IPV4_PRIVATE_172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
75364
|
+
IPV4_PRIVATE_192 = /^192\.168\./;
|
|
75365
|
+
IPV4_ZERO_NETWORK = /^0\./;
|
|
75366
|
+
IPV6_LINK_LOCAL = /^fe80:/i;
|
|
75367
|
+
IPV6_UNIQUE_LOCAL = /^f[cd][0-9a-f]{2}:/i;
|
|
75368
|
+
_internals39 = { spawnSync: spawnSync8 };
|
|
75298
75369
|
});
|
|
75299
75370
|
|
|
75300
75371
|
// src/commands/issue.ts
|
|
75301
|
-
|
|
75302
|
-
|
|
75303
|
-
let urlStr = raw.trim();
|
|
75304
|
-
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
75305
|
-
const fragmentIdx = urlStr.indexOf("#");
|
|
75306
|
-
if (fragmentIdx !== -1) {
|
|
75307
|
-
urlStr = urlStr.slice(0, fragmentIdx);
|
|
75308
|
-
}
|
|
75309
|
-
const queryIdx = urlStr.indexOf("?");
|
|
75310
|
-
if (queryIdx !== -1) {
|
|
75311
|
-
urlStr = urlStr.slice(0, queryIdx);
|
|
75312
|
-
}
|
|
75313
|
-
urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
|
|
75314
|
-
if (urlStr.length > MAX_URL_LEN2) {
|
|
75315
|
-
urlStr = urlStr.slice(0, MAX_URL_LEN2);
|
|
75316
|
-
}
|
|
75317
|
-
return urlStr.trim();
|
|
75318
|
-
}
|
|
75319
|
-
function isPrivateHost2(url3) {
|
|
75320
|
-
const host = url3.hostname.toLowerCase();
|
|
75321
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
|
|
75322
|
-
return true;
|
|
75323
|
-
}
|
|
75324
|
-
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
75325
|
-
return true;
|
|
75326
|
-
}
|
|
75327
|
-
const ipv4Private = /^10\./;
|
|
75328
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
75329
|
-
const ipv4192 = /^192\.168\./;
|
|
75330
|
-
const ipv6Private = /^fe80:/i;
|
|
75331
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
75332
|
-
if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
|
|
75333
|
-
return true;
|
|
75334
|
-
}
|
|
75335
|
-
if (host.startsWith("::ffff:")) {
|
|
75336
|
-
const inner = host.slice(7);
|
|
75337
|
-
if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
|
|
75338
|
-
return true;
|
|
75339
|
-
}
|
|
75340
|
-
}
|
|
75341
|
-
return false;
|
|
75342
|
-
}
|
|
75343
|
-
function validateAndSanitizeUrl2(rawUrl) {
|
|
75344
|
-
const sanitized = sanitizeUrl2(rawUrl);
|
|
75345
|
-
if (!sanitized) {
|
|
75346
|
-
return { error: "Empty URL" };
|
|
75347
|
-
}
|
|
75348
|
-
if (!sanitized.startsWith("https://")) {
|
|
75349
|
-
return { error: "URL must use HTTPS scheme" };
|
|
75350
|
-
}
|
|
75351
|
-
try {
|
|
75352
|
-
const url3 = new URL(sanitized);
|
|
75353
|
-
const hostname5 = url3.hostname;
|
|
75354
|
-
if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
|
|
75355
|
-
return { error: "Non-ASCII hostnames are not allowed" };
|
|
75356
|
-
}
|
|
75357
|
-
if (isPrivateHost2(url3)) {
|
|
75358
|
-
return { error: "Private or localhost URLs are not allowed" };
|
|
75359
|
-
}
|
|
75360
|
-
const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
|
|
75361
|
-
if (!githubIssuePattern.test(sanitized)) {
|
|
75362
|
-
return {
|
|
75363
|
-
error: "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)"
|
|
75364
|
-
};
|
|
75365
|
-
}
|
|
75366
|
-
return { sanitized };
|
|
75367
|
-
} catch {
|
|
75368
|
-
return { error: "Invalid URL format" };
|
|
75369
|
-
}
|
|
75372
|
+
function validateAndSanitizeUrl(rawUrl) {
|
|
75373
|
+
return validateAndSanitizeGithubUrl(rawUrl, "issues");
|
|
75370
75374
|
}
|
|
75371
75375
|
function parseArgs6(args2) {
|
|
75372
75376
|
const out2 = {
|
|
@@ -75393,9 +75397,12 @@ function parseArgs6(args2) {
|
|
|
75393
75397
|
}
|
|
75394
75398
|
return out2;
|
|
75395
75399
|
}
|
|
75396
|
-
function parseIssueRef(input) {
|
|
75400
|
+
function parseIssueRef(input, directory) {
|
|
75397
75401
|
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/i);
|
|
75398
75402
|
if (urlMatch) {
|
|
75403
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
75404
|
+
return null;
|
|
75405
|
+
}
|
|
75399
75406
|
return {
|
|
75400
75407
|
owner: urlMatch[1],
|
|
75401
75408
|
repo: urlMatch[2],
|
|
@@ -75404,6 +75411,9 @@ function parseIssueRef(input) {
|
|
|
75404
75411
|
}
|
|
75405
75412
|
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
75406
75413
|
if (shorthandMatch) {
|
|
75414
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
75415
|
+
return null;
|
|
75416
|
+
}
|
|
75407
75417
|
return {
|
|
75408
75418
|
owner: shorthandMatch[1],
|
|
75409
75419
|
repo: shorthandMatch[2],
|
|
@@ -75413,7 +75423,7 @@ function parseIssueRef(input) {
|
|
|
75413
75423
|
const bareMatch = input.match(/^(\d+)$/);
|
|
75414
75424
|
if (bareMatch) {
|
|
75415
75425
|
const issueNumber = parseInt(bareMatch[1], 10);
|
|
75416
|
-
const remoteUrl =
|
|
75426
|
+
const remoteUrl = detectGitRemote(directory);
|
|
75417
75427
|
if (!remoteUrl) {
|
|
75418
75428
|
return null;
|
|
75419
75429
|
}
|
|
@@ -75429,33 +75439,21 @@ function parseIssueRef(input) {
|
|
|
75429
75439
|
}
|
|
75430
75440
|
return null;
|
|
75431
75441
|
}
|
|
75432
|
-
function
|
|
75433
|
-
try {
|
|
75434
|
-
const remoteUrl = execSync3("git remote get-url origin", {
|
|
75435
|
-
encoding: "utf-8",
|
|
75436
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
75437
|
-
timeout: 5000
|
|
75438
|
-
}).trim();
|
|
75439
|
-
return remoteUrl || null;
|
|
75440
|
-
} catch {
|
|
75441
|
-
return null;
|
|
75442
|
-
}
|
|
75443
|
-
}
|
|
75444
|
-
function handleIssueCommand(_directory, args2) {
|
|
75442
|
+
function handleIssueCommand(directory, args2) {
|
|
75445
75443
|
const parsed = parseArgs6(args2);
|
|
75446
75444
|
const rawInput = parsed.rest.join(" ").trim();
|
|
75447
75445
|
if (!rawInput) {
|
|
75448
75446
|
return USAGE6;
|
|
75449
75447
|
}
|
|
75450
75448
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
75451
|
-
const issueInfo = parseIssueRef(isFullUrl ?
|
|
75449
|
+
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput, directory);
|
|
75452
75450
|
if (!issueInfo) {
|
|
75453
|
-
return `Error: Could not parse issue reference from "${rawInput}"
|
|
75451
|
+
return `Error: Could not parse issue reference from "${sanitizeErrorEcho(rawInput)}"
|
|
75454
75452
|
|
|
75455
75453
|
${USAGE6}`;
|
|
75456
75454
|
}
|
|
75457
75455
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
75458
|
-
const result =
|
|
75456
|
+
const result = validateAndSanitizeUrl(issueUrl);
|
|
75459
75457
|
if ("error" in result) {
|
|
75460
75458
|
return `Error: ${result.error}
|
|
75461
75459
|
|
|
@@ -75471,9 +75469,9 @@ ${USAGE6}`;
|
|
|
75471
75469
|
const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
|
|
75472
75470
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
75473
75471
|
}
|
|
75474
|
-
var
|
|
75472
|
+
var USAGE6;
|
|
75475
75473
|
var init_issue = __esm(() => {
|
|
75476
|
-
|
|
75474
|
+
init_url_security();
|
|
75477
75475
|
USAGE6 = [
|
|
75478
75476
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
75479
75477
|
"",
|
|
@@ -80656,6 +80654,88 @@ var init_post_mortem = __esm(() => {
|
|
|
80656
80654
|
init_curator_postmortem();
|
|
80657
80655
|
});
|
|
80658
80656
|
|
|
80657
|
+
// src/commands/pr-ref.ts
|
|
80658
|
+
function sanitizeInstructions(raw) {
|
|
80659
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
80660
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
80661
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
80662
|
+
if (normalized.length <= MAX_INSTRUCTIONS_LEN)
|
|
80663
|
+
return normalized;
|
|
80664
|
+
return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
|
|
80665
|
+
}
|
|
80666
|
+
function validateAndSanitizeUrl2(rawUrl) {
|
|
80667
|
+
return validateAndSanitizeGithubUrl(rawUrl, "pull");
|
|
80668
|
+
}
|
|
80669
|
+
function parsePrRef(input, cwd) {
|
|
80670
|
+
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
80671
|
+
if (urlMatch) {
|
|
80672
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
80673
|
+
return null;
|
|
80674
|
+
}
|
|
80675
|
+
return {
|
|
80676
|
+
owner: urlMatch[1],
|
|
80677
|
+
repo: urlMatch[2],
|
|
80678
|
+
number: parseInt(urlMatch[3], 10)
|
|
80679
|
+
};
|
|
80680
|
+
}
|
|
80681
|
+
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
80682
|
+
if (shorthandMatch) {
|
|
80683
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
80684
|
+
return null;
|
|
80685
|
+
}
|
|
80686
|
+
return {
|
|
80687
|
+
owner: shorthandMatch[1],
|
|
80688
|
+
repo: shorthandMatch[2],
|
|
80689
|
+
number: parseInt(shorthandMatch[3], 10)
|
|
80690
|
+
};
|
|
80691
|
+
}
|
|
80692
|
+
const bareMatch = input.match(/^(\d+)$/);
|
|
80693
|
+
if (bareMatch) {
|
|
80694
|
+
const prNumber = parseInt(bareMatch[1], 10);
|
|
80695
|
+
const remoteUrl = detectGitRemote(cwd);
|
|
80696
|
+
if (!remoteUrl) {
|
|
80697
|
+
return null;
|
|
80698
|
+
}
|
|
80699
|
+
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
80700
|
+
if (!parsed) {
|
|
80701
|
+
return null;
|
|
80702
|
+
}
|
|
80703
|
+
return {
|
|
80704
|
+
owner: parsed.owner,
|
|
80705
|
+
repo: parsed.repo,
|
|
80706
|
+
number: prNumber
|
|
80707
|
+
};
|
|
80708
|
+
}
|
|
80709
|
+
return null;
|
|
80710
|
+
}
|
|
80711
|
+
function looksLikePrRef(token) {
|
|
80712
|
+
return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
|
|
80713
|
+
}
|
|
80714
|
+
function resolvePrCommandInput(rest, cwd) {
|
|
80715
|
+
if (rest.length === 0) {
|
|
80716
|
+
return null;
|
|
80717
|
+
}
|
|
80718
|
+
const refToken = rest[0];
|
|
80719
|
+
const instructions = sanitizeInstructions(rest.slice(1).join(" "));
|
|
80720
|
+
const isFullUrl = /^https?:\/\//i.test(refToken);
|
|
80721
|
+
const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
|
|
80722
|
+
if (!prInfo) {
|
|
80723
|
+
return {
|
|
80724
|
+
error: `Could not parse PR reference from "${sanitizeErrorEcho(refToken)}"`
|
|
80725
|
+
};
|
|
80726
|
+
}
|
|
80727
|
+
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
80728
|
+
const result = validateAndSanitizeUrl2(prUrl);
|
|
80729
|
+
if ("error" in result) {
|
|
80730
|
+
return { error: result.error };
|
|
80731
|
+
}
|
|
80732
|
+
return { prUrl: result.sanitized, instructions };
|
|
80733
|
+
}
|
|
80734
|
+
var MAX_INSTRUCTIONS_LEN = 1000;
|
|
80735
|
+
var init_pr_ref = __esm(() => {
|
|
80736
|
+
init_url_security();
|
|
80737
|
+
});
|
|
80738
|
+
|
|
80659
80739
|
// src/commands/pr-feedback.ts
|
|
80660
80740
|
function handlePrFeedbackCommand(directory, args2) {
|
|
80661
80741
|
const rest = args2.filter((t) => t.trim().length > 0);
|
|
@@ -124942,10 +125022,10 @@ var knowledge_ack = createSwarmTool({
|
|
|
124942
125022
|
// src/tools/knowledge-add.ts
|
|
124943
125023
|
init_zod();
|
|
124944
125024
|
init_config();
|
|
125025
|
+
init_knowledge_reinforcement();
|
|
124945
125026
|
init_knowledge_store();
|
|
124946
125027
|
init_knowledge_validator();
|
|
124947
125028
|
init_manager();
|
|
124948
|
-
init_utils();
|
|
124949
125029
|
init_create_tool();
|
|
124950
125030
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
124951
125031
|
var VALID_CATEGORIES2 = [
|
|
@@ -125037,9 +125117,13 @@ var knowledge_add = createSwarmTool({
|
|
|
125037
125117
|
});
|
|
125038
125118
|
}
|
|
125039
125119
|
let project_name = "";
|
|
125120
|
+
let phase_number = 1;
|
|
125040
125121
|
try {
|
|
125041
125122
|
const plan = await loadPlan(directory);
|
|
125042
125123
|
project_name = plan?.title ?? "";
|
|
125124
|
+
if (typeof plan?.current_phase === "number") {
|
|
125125
|
+
phase_number = plan.current_phase;
|
|
125126
|
+
}
|
|
125043
125127
|
} catch {}
|
|
125044
125128
|
const entry = {
|
|
125045
125129
|
id: randomUUID15(),
|
|
@@ -125084,19 +125168,6 @@ var knowledge_add = createSwarmTool({
|
|
|
125084
125168
|
}
|
|
125085
125169
|
}
|
|
125086
125170
|
} catch {}
|
|
125087
|
-
try {
|
|
125088
|
-
const existingEntries = await readKnowledge(resolveSwarmKnowledgePath(directory));
|
|
125089
|
-
const duplicate = findNearDuplicate(lesson, existingEntries, dedupThreshold);
|
|
125090
|
-
if (duplicate) {
|
|
125091
|
-
return JSON.stringify({
|
|
125092
|
-
success: false,
|
|
125093
|
-
id: duplicate.id,
|
|
125094
|
-
message: "near-duplicate of existing entry"
|
|
125095
|
-
});
|
|
125096
|
-
}
|
|
125097
|
-
} catch (err2) {
|
|
125098
|
-
warn("knowledge_add: dedup check failed — skipping near-duplicate detection", err2);
|
|
125099
|
-
}
|
|
125100
125171
|
const actionability = validateActionability(entry);
|
|
125101
125172
|
if (!actionability.actionable) {
|
|
125102
125173
|
try {
|
|
@@ -125112,7 +125183,55 @@ var knowledge_add = createSwarmTool({
|
|
|
125112
125183
|
}
|
|
125113
125184
|
try {
|
|
125114
125185
|
const maxEntries = config3?.knowledge?.swarm_max_entries ?? 100;
|
|
125115
|
-
|
|
125186
|
+
let duplicateResponse;
|
|
125187
|
+
await transactKnowledge(resolveSwarmKnowledgePath(directory), (existingEntries) => {
|
|
125188
|
+
const activeDuplicate = findActiveSwarmNearDuplicate(lesson, existingEntries, dedupThreshold);
|
|
125189
|
+
if (activeDuplicate) {
|
|
125190
|
+
const result = reinforceSwarmKnowledgeEntry(activeDuplicate, {
|
|
125191
|
+
phase_number,
|
|
125192
|
+
confirmed_at: new Date().toISOString(),
|
|
125193
|
+
project_name
|
|
125194
|
+
});
|
|
125195
|
+
duplicateResponse = {
|
|
125196
|
+
id: activeDuplicate.id,
|
|
125197
|
+
reinforced: result.reinforced,
|
|
125198
|
+
idempotent: result.reason === "already_confirmed_phase",
|
|
125199
|
+
inactive: false
|
|
125200
|
+
};
|
|
125201
|
+
return result.reinforced ? existingEntries : null;
|
|
125202
|
+
}
|
|
125203
|
+
const inactiveDuplicate = findNearDuplicate(lesson, existingEntries, dedupThreshold);
|
|
125204
|
+
if (inactiveDuplicate) {
|
|
125205
|
+
duplicateResponse = {
|
|
125206
|
+
id: inactiveDuplicate.id,
|
|
125207
|
+
reinforced: false,
|
|
125208
|
+
idempotent: false,
|
|
125209
|
+
inactive: true
|
|
125210
|
+
};
|
|
125211
|
+
return null;
|
|
125212
|
+
}
|
|
125213
|
+
const updated = [...existingEntries, entry];
|
|
125214
|
+
if (updated.length > maxEntries) {
|
|
125215
|
+
return updated.slice(updated.length - maxEntries);
|
|
125216
|
+
}
|
|
125217
|
+
return updated;
|
|
125218
|
+
});
|
|
125219
|
+
if (duplicateResponse) {
|
|
125220
|
+
if (duplicateResponse.inactive) {
|
|
125221
|
+
return JSON.stringify({
|
|
125222
|
+
success: false,
|
|
125223
|
+
id: duplicateResponse.id,
|
|
125224
|
+
message: "near-duplicate of inactive existing entry"
|
|
125225
|
+
});
|
|
125226
|
+
}
|
|
125227
|
+
return JSON.stringify({
|
|
125228
|
+
success: true,
|
|
125229
|
+
id: duplicateResponse.id,
|
|
125230
|
+
reinforced: duplicateResponse.reinforced,
|
|
125231
|
+
idempotent: duplicateResponse.idempotent,
|
|
125232
|
+
message: duplicateResponse.reinforced ? "near-duplicate reinforced existing entry" : "near-duplicate already confirmed for this phase"
|
|
125233
|
+
});
|
|
125234
|
+
}
|
|
125116
125235
|
} catch (err2) {
|
|
125117
125236
|
const message = err2 instanceof Error ? err2.message : "Unknown error";
|
|
125118
125237
|
return JSON.stringify({
|
|
@@ -127725,7 +127844,7 @@ import * as fs103 from "node:fs";
|
|
|
127725
127844
|
import * as path156 from "node:path";
|
|
127726
127845
|
|
|
127727
127846
|
// src/mutation/engine.ts
|
|
127728
|
-
import { spawnSync as
|
|
127847
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
127729
127848
|
import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync27 } from "node:fs";
|
|
127730
127849
|
import * as path155 from "node:path";
|
|
127731
127850
|
|
|
@@ -127903,7 +128022,7 @@ var _internals88 = {
|
|
|
127903
128022
|
executeMutation,
|
|
127904
128023
|
computeReport,
|
|
127905
128024
|
executeMutationSuite,
|
|
127906
|
-
spawnSync:
|
|
128025
|
+
spawnSync: spawnSync11
|
|
127907
128026
|
};
|
|
127908
128027
|
async function executeMutation(patch, testCommand, testFiles, workingDir) {
|
|
127909
128028
|
const startTime = Date.now();
|
|
@@ -130197,7 +130316,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
130197
130316
|
const sessionState = swarmState.agentSessions.get(sessionID);
|
|
130198
130317
|
if (sessionState) {
|
|
130199
130318
|
sessionState.pendingAdvisoryMessages ??= [];
|
|
130200
|
-
sessionState.pendingAdvisoryMessages.push(`[CURATOR] Knowledge curation: ${curationResult.stored} stored, ${curationResult.skipped} skipped, ${curationResult.rejected} rejected, ${curationResult.quarantined} quarantined (unactionable).`);
|
|
130319
|
+
sessionState.pendingAdvisoryMessages.push(`[CURATOR] Knowledge curation: ${curationResult.stored} stored, ${curationResult.reinforced} reinforced, ${curationResult.skipped} skipped, ${curationResult.rejected} rejected, ${curationResult.quarantined} quarantined (unactionable).`);
|
|
130201
130320
|
}
|
|
130202
130321
|
}
|
|
130203
130322
|
await updateRetrievalOutcome(dir, `Phase ${phase}`, true);
|
|
@@ -141956,6 +142075,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
|
|
|
141956
142075
|
};
|
|
141957
142076
|
const evidencePath = writeSupervisorReport(dirResult.directory, report);
|
|
141958
142077
|
let knowledgeProposed = 0;
|
|
142078
|
+
let knowledgeReinforced = 0;
|
|
141959
142079
|
let knowledgeQuarantined = 0;
|
|
141960
142080
|
try {
|
|
141961
142081
|
const config3 = loadPluginConfig(dirResult.directory);
|
|
@@ -141972,6 +142092,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
|
|
|
141972
142092
|
}
|
|
141973
142093
|
});
|
|
141974
142094
|
knowledgeProposed = result.stored;
|
|
142095
|
+
knowledgeReinforced = result.reinforced;
|
|
141975
142096
|
knowledgeQuarantined = result.quarantined;
|
|
141976
142097
|
}
|
|
141977
142098
|
} catch {}
|
|
@@ -141993,6 +142114,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
|
|
|
141993
142114
|
verdict: args2.verdict,
|
|
141994
142115
|
findings_count: args2.findings.length,
|
|
141995
142116
|
knowledge_proposed: knowledgeProposed,
|
|
142117
|
+
knowledge_reinforced: knowledgeReinforced,
|
|
141996
142118
|
knowledge_quarantined: knowledgeQuarantined,
|
|
141997
142119
|
skills_proposed: skillsProposed,
|
|
141998
142120
|
evidence_path: evidencePath
|