@brawnen/harnessly 0.1.0-alpha.3 → 0.1.0-alpha.6

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.
Files changed (2) hide show
  1. package/dist/index.js +61 -36
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -834,8 +834,26 @@ async function loadHarnessConfig(workDir) {
834
834
  const configText = await readFile(getHarnessPaths(workDir).configFile, "utf8");
835
835
  return parseHarnessConfig2(configText);
836
836
  }
837
+ function isMissingFileError(error) {
838
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
839
+ }
837
840
  async function writeHarnessConfig(workDir, config, force = false) {
838
- return writeFileIfChanged(getHarnessPaths(workDir).configFile, serializeHarnessConfig2(config), force);
841
+ const configPath = getHarnessPaths(workDir).configFile;
842
+ let mergedConfig = config;
843
+ let didMerge = false;
844
+ try {
845
+ const existing = await loadHarnessConfig(workDir);
846
+ const mergedHosts = [.../* @__PURE__ */ new Set([...existing.enabledHosts, ...config.enabledHosts])];
847
+ if (mergedHosts.length > existing.enabledHosts.length) {
848
+ mergedConfig = { ...existing, ...config, enabledHosts: mergedHosts };
849
+ didMerge = true;
850
+ } else if (!force) {
851
+ return "skipped";
852
+ }
853
+ } catch (error) {
854
+ if (!isMissingFileError(error)) throw error;
855
+ }
856
+ return writeFileIfChanged(configPath, serializeHarnessConfig2(mergedConfig), force || didMerge);
839
857
  }
840
858
  var AGENT_ROLES = [
841
859
  "requirement",
@@ -1013,14 +1031,14 @@ function getDefaultAgentManifest(role) {
1013
1031
  toolWhitelist: [...source.toolWhitelist]
1014
1032
  };
1015
1033
  }
1016
- function isMissingFileError(error) {
1034
+ function isMissingFileError2(error) {
1017
1035
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1018
1036
  }
1019
1037
  async function readFileIfExists(filePath) {
1020
1038
  try {
1021
1039
  return await readFile2(filePath, "utf8");
1022
1040
  } catch (error) {
1023
- if (isMissingFileError(error)) {
1041
+ if (isMissingFileError2(error)) {
1024
1042
  return null;
1025
1043
  }
1026
1044
  throw error;
@@ -1134,7 +1152,7 @@ function getFeedbackPoolPath(workDir) {
1134
1152
  function getFeedbackHistoryPath(workDir) {
1135
1153
  return path4.join(getHarnessPaths(workDir).harnessDir, "feedback-history.md");
1136
1154
  }
1137
- function isMissingFileError2(error) {
1155
+ function isMissingFileError3(error) {
1138
1156
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1139
1157
  }
1140
1158
  function buildFeedbackEntry(ctx, report) {
@@ -1176,7 +1194,7 @@ async function loadFeedbackPool(workDir) {
1176
1194
  try {
1177
1195
  text = await readFile3(filePath, "utf8");
1178
1196
  } catch (error) {
1179
- if (isMissingFileError2(error)) {
1197
+ if (isMissingFileError3(error)) {
1180
1198
  return [];
1181
1199
  }
1182
1200
  throw error;
@@ -1259,7 +1277,7 @@ async function readJson(filePath) {
1259
1277
  const text = await readFile4(filePath, "utf8");
1260
1278
  return JSON.parse(text);
1261
1279
  }
1262
- function isMissingFileError3(error) {
1280
+ function isMissingFileError4(error) {
1263
1281
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1264
1282
  }
1265
1283
  function ownerForStage(stage) {
@@ -1439,7 +1457,7 @@ var TaskManager = class {
1439
1457
  const filePath = path5.join(ctx.taskDir, "commit-summary.md");
1440
1458
  try {
1441
1459
  const existing = await readFile4(filePath, "utf8").catch(
1442
- (error) => isMissingFileError3(error) ? "" : Promise.reject(error)
1460
+ (error) => isMissingFileError4(error) ? "" : Promise.reject(error)
1443
1461
  );
1444
1462
  const section = `${heading}
1445
1463
  ${content}
@@ -1477,7 +1495,7 @@ ${content}
1477
1495
  try {
1478
1496
  return parseTaskReport(await readFile4(this.getReportFile(taskDir), "utf8"));
1479
1497
  } catch (error) {
1480
- if (isMissingFileError3(error)) {
1498
+ if (isMissingFileError4(error)) {
1481
1499
  return null;
1482
1500
  }
1483
1501
  if (error instanceof Error) {
@@ -1494,7 +1512,7 @@ ${content}
1494
1512
  meta = await readJson(this.getMetaFile(taskDir));
1495
1513
  state = await readJson(this.getStateFile(taskDir));
1496
1514
  } catch (error) {
1497
- if (isMissingFileError3(error)) {
1515
+ if (isMissingFileError4(error)) {
1498
1516
  throw new Error(`task ${taskId} \u4E0D\u5B58\u5728\u3002\u53EF\u5148\u6267\u884C harnessly list \u67E5\u770B\u5DF2\u6709\u4EFB\u52A1\u3002`);
1499
1517
  }
1500
1518
  throw error;
@@ -1511,7 +1529,7 @@ ${content}
1511
1529
  try {
1512
1530
  ctx.contract = parseContract(await readFile4(this.getContractFile(taskDir), "utf8"));
1513
1531
  } catch (error) {
1514
- if (!isMissingFileError3(error)) {
1532
+ if (!isMissingFileError4(error)) {
1515
1533
  throw error;
1516
1534
  }
1517
1535
  }
@@ -1537,7 +1555,7 @@ ${content}
1537
1555
  try {
1538
1556
  entries = await readdir2(tasksDir, { withFileTypes: true });
1539
1557
  } catch (error) {
1540
- if (isMissingFileError3(error)) {
1558
+ if (isMissingFileError4(error)) {
1541
1559
  return [];
1542
1560
  }
1543
1561
  throw error;
@@ -1569,7 +1587,7 @@ ${content}
1569
1587
  var DEFAULT_TOPIC = "tasks";
1570
1588
  var AUTO_START = "<!-- harness:auto-start -->";
1571
1589
  var AUTO_END = "<!-- harness:auto-end -->";
1572
- function isMissingFileError4(error) {
1590
+ function isMissingFileError5(error) {
1573
1591
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1574
1592
  }
1575
1593
  async function fileExists2(filePath) {
@@ -1577,7 +1595,7 @@ async function fileExists2(filePath) {
1577
1595
  await access2(filePath);
1578
1596
  return true;
1579
1597
  } catch (error) {
1580
- if (isMissingFileError4(error)) return false;
1598
+ if (isMissingFileError5(error)) return false;
1581
1599
  throw error;
1582
1600
  }
1583
1601
  }
@@ -1589,7 +1607,7 @@ function pickFilesByKind(kind) {
1589
1607
  }
1590
1608
  function readTaskReportSafe(filePath) {
1591
1609
  return readFile5(filePath, "utf8").then((text) => parseTaskReport(text)).catch((error) => {
1592
- if (isMissingFileError4(error)) return null;
1610
+ if (isMissingFileError5(error)) return null;
1593
1611
  return null;
1594
1612
  });
1595
1613
  }
@@ -1601,7 +1619,7 @@ async function loadHarnessMeta(topicDir) {
1601
1619
  const text = await readFile5(getHarnessMetaPath(topicDir), "utf8");
1602
1620
  return harnessMetaFileSchema.parse(JSON.parse(text));
1603
1621
  } catch (error) {
1604
- if (isMissingFileError4(error)) return null;
1622
+ if (isMissingFileError5(error)) return null;
1605
1623
  return null;
1606
1624
  }
1607
1625
  }
@@ -1708,7 +1726,7 @@ async function writeReadme(topicDir, topic, meta) {
1708
1726
  ${existing}`;
1709
1727
  }
1710
1728
  } catch (error) {
1711
- if (!isMissingFileError4(error)) throw error;
1729
+ if (!isMissingFileError5(error)) throw error;
1712
1730
  existingBefore = `${AUTO_START}`;
1713
1731
  existingAfter = `
1714
1732
  ${AUTO_END}`;
@@ -1782,7 +1800,7 @@ async function listDirs(dirPath) {
1782
1800
  const entries = await readdir3(dirPath, { withFileTypes: true });
1783
1801
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1784
1802
  } catch (error) {
1785
- if (isMissingFileError4(error)) return [];
1803
+ if (isMissingFileError5(error)) return [];
1786
1804
  throw error;
1787
1805
  }
1788
1806
  }
@@ -1819,7 +1837,7 @@ async function showArchiveTopic(workDir, topic) {
1819
1837
  try {
1820
1838
  readme = await readFile5(path6.join(topicDir, "README.md"), "utf8");
1821
1839
  } catch (error) {
1822
- if (!isMissingFileError4(error)) throw error;
1840
+ if (!isMissingFileError5(error)) throw error;
1823
1841
  }
1824
1842
  const allFiles = /* @__PURE__ */ new Set();
1825
1843
  for (const task of meta.source_tasks) {
@@ -1908,7 +1926,7 @@ async function archiveTaskArtifacts(workDir, taskId, kind, options = {}) {
1908
1926
  const reportFile = path6.join(ctx.taskDir, "report.json");
1909
1927
  const report = await readTaskReportSafe(reportFile);
1910
1928
  const previousReadme = await readFile5(path6.join(targets.topicDir, "README.md"), "utf8").catch(
1911
- (error) => isMissingFileError4(error) ? null : Promise.reject(error)
1929
+ (error) => isMissingFileError5(error) ? null : Promise.reject(error)
1912
1930
  );
1913
1931
  await writeFile5(path6.join(targets.topicDir, "README.md"), [
1914
1932
  `# Task Archive: ${ctx.goal}`,
@@ -1937,7 +1955,7 @@ async function archiveTaskArtifacts(workDir, taskId, kind, options = {}) {
1937
1955
  });
1938
1956
  const metadataPath = path6.join(targets.topicDir, `${taskId}.promotion.json`);
1939
1957
  const previousMetadata = await readFile5(metadataPath, "utf8").catch(
1940
- (error) => isMissingFileError4(error) ? null : Promise.reject(error)
1958
+ (error) => isMissingFileError5(error) ? null : Promise.reject(error)
1941
1959
  );
1942
1960
  await writeFile5(
1943
1961
  metadataPath,
@@ -1960,7 +1978,7 @@ async function exists(filePath) {
1960
1978
  return false;
1961
1979
  }
1962
1980
  }
1963
- function isMissingFileError5(error) {
1981
+ function isMissingFileError6(error) {
1964
1982
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1965
1983
  }
1966
1984
  var REQUIRED_TASK_ARTIFACTS = [
@@ -2034,7 +2052,7 @@ async function checkWritePermission(workDir, filePath, activeTaskId) {
2034
2052
  const state = JSON.parse(stateText);
2035
2053
  currentStage = state.currentStage;
2036
2054
  } catch (error) {
2037
- if (!isMissingFileError5(error)) throw error;
2055
+ if (!isMissingFileError6(error)) throw error;
2038
2056
  return { allowed: true, reason: "state.json \u4E0D\u5B58\u5728\uFF0C\u653E\u884C", file: relative };
2039
2057
  }
2040
2058
  if (!currentStage || resolvedActiveTaskId !== taskId) {
@@ -2056,7 +2074,7 @@ async function readActiveTaskId(workDir) {
2056
2074
  const text = await readFile6(path7.join(workDir, ".harness", "active-task.txt"), "utf8");
2057
2075
  return text.trim() || void 0;
2058
2076
  } catch (error) {
2059
- if (isMissingFileError5(error)) return void 0;
2077
+ if (isMissingFileError6(error)) return void 0;
2060
2078
  throw error;
2061
2079
  }
2062
2080
  }
@@ -2291,7 +2309,7 @@ function runScopeCheck(contract, changedFiles) {
2291
2309
  };
2292
2310
  }
2293
2311
  var execAsync = promisify(exec);
2294
- function isMissingFileError6(error) {
2312
+ function isMissingFileError7(error) {
2295
2313
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2296
2314
  }
2297
2315
  async function fileExists3(filePath) {
@@ -2299,7 +2317,7 @@ async function fileExists3(filePath) {
2299
2317
  await access4(filePath);
2300
2318
  return true;
2301
2319
  } catch (error) {
2302
- if (isMissingFileError6(error)) return false;
2320
+ if (isMissingFileError7(error)) return false;
2303
2321
  throw error;
2304
2322
  }
2305
2323
  }
@@ -2568,7 +2586,7 @@ function getTaskEvidenceDir(taskDir) {
2568
2586
  function getTaskEvidencePath(taskDir, kind) {
2569
2587
  return path10.join(getTaskEvidenceDir(taskDir), `${kind}.json`);
2570
2588
  }
2571
- function isMissingFileError7(error) {
2589
+ function isMissingFileError8(error) {
2572
2590
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2573
2591
  }
2574
2592
  function buildEvidenceBaseline(evidence) {
@@ -2630,7 +2648,7 @@ async function loadEvidenceBaseline(workDir) {
2630
2648
  try {
2631
2649
  text = await readFile9(filePath, "utf8");
2632
2650
  } catch (error) {
2633
- if (isMissingFileError7(error)) return null;
2651
+ if (isMissingFileError8(error)) return null;
2634
2652
  throw error;
2635
2653
  }
2636
2654
  try {
@@ -3058,7 +3076,7 @@ function createTaskReport(ctx, adapter, evidence, commitGate) {
3058
3076
  });
3059
3077
  }
3060
3078
  var execAsync5 = promisify5(exec5);
3061
- function isMissingFileError8(error) {
3079
+ function isMissingFileError9(error) {
3062
3080
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3063
3081
  }
3064
3082
  async function loadReviewAgentsConfig(workDir) {
@@ -3090,7 +3108,7 @@ async function loadReviewAgentsConfig(workDir) {
3090
3108
  if (current?.name) agents.push(current);
3091
3109
  return { review_agents: agents };
3092
3110
  } catch (error) {
3093
- if (isMissingFileError8(error)) return null;
3111
+ if (isMissingFileError9(error)) return null;
3094
3112
  throw error;
3095
3113
  }
3096
3114
  }
@@ -3164,7 +3182,7 @@ async function readActiveTaskId2(workDir) {
3164
3182
  const text = await readFile10(getHarnessPaths(workDir).activeTaskFile, "utf8");
3165
3183
  return text.trim() || void 0;
3166
3184
  } catch (error) {
3167
- if (isMissingFileError8(error)) return void 0;
3185
+ if (isMissingFileError9(error)) return void 0;
3168
3186
  throw error;
3169
3187
  }
3170
3188
  }
@@ -3190,7 +3208,7 @@ function renderResidentReviewFromFindings(findings, trigger, agentsSpawned) {
3190
3208
  }
3191
3209
  async function renderResidentReview(workDir, findings) {
3192
3210
  const configText = await readFile10(getHarnessPaths(workDir).reviewAgentsFile, "utf8").catch(
3193
- (error) => isMissingFileError8(error) ? null : Promise.reject(error)
3211
+ (error) => isMissingFileError9(error) ? null : Promise.reject(error)
3194
3212
  );
3195
3213
  const active = Boolean(configText?.includes("review_agents:"));
3196
3214
  const failed = findings.filter((finding) => finding.status === "failed");
@@ -3266,7 +3284,7 @@ function runReviewStage(changedFiles) {
3266
3284
  }
3267
3285
  return findings;
3268
3286
  }
3269
- function isMissingFileError9(error) {
3287
+ function isMissingFileError10(error) {
3270
3288
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3271
3289
  }
3272
3290
  function parseStructureRules(text) {
@@ -3303,7 +3321,7 @@ async function runStructureCheck(workDir, changedFiles) {
3303
3321
  try {
3304
3322
  text = await readFile11(rulesPath, "utf8");
3305
3323
  } catch (error) {
3306
- if (isMissingFileError9(error)) {
3324
+ if (isMissingFileError10(error)) {
3307
3325
  return [
3308
3326
  {
3309
3327
  name: "structure.rules",
@@ -5032,6 +5050,13 @@ async function installHostShells(workDir, requestedHost) {
5032
5050
  const installedPaths = [];
5033
5051
  const hosts = await loadEnabledHosts(workDir, requestedHost);
5034
5052
  const config = await loadHarnessConfig(workDir);
5053
+ const newHosts = hosts.filter((h) => !config.enabledHosts.includes(h));
5054
+ if (newHosts.length > 0) {
5055
+ await writeHarnessConfig(workDir, {
5056
+ ...config,
5057
+ enabledHosts: newHosts
5058
+ });
5059
+ }
5035
5060
  const agentManifests = await loadAgentManifests(workDir);
5036
5061
  for (const host of hosts) {
5037
5062
  const manifest = await ensureHostManifest(workDir, host);
@@ -5149,7 +5174,7 @@ import { appendFile as appendFile3, mkdir as mkdir12, readFile as readFile14, wr
5149
5174
  import path18 from "path";
5150
5175
  var FEEDBACK_FILENAME = "intake-feedback.jsonl";
5151
5176
  var LAST_DECISION_FILENAME = "intake-last.json";
5152
- function isMissingFileError10(error) {
5177
+ function isMissingFileError11(error) {
5153
5178
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
5154
5179
  }
5155
5180
  function normalizePromptForFeedback(prompt) {
@@ -5175,7 +5200,7 @@ async function readLastIntakeDecision(workDir) {
5175
5200
  try {
5176
5201
  return JSON.parse(await readFile14(getLastIntakeDecisionPath(workDir), "utf8"));
5177
5202
  } catch (error) {
5178
- if (isMissingFileError10(error)) return null;
5203
+ if (isMissingFileError11(error)) return null;
5179
5204
  return null;
5180
5205
  }
5181
5206
  }
@@ -5212,7 +5237,7 @@ async function loadIntakeFeedback(workDir) {
5212
5237
  }
5213
5238
  return entries;
5214
5239
  } catch (error) {
5215
- if (isMissingFileError10(error)) return [];
5240
+ if (isMissingFileError11(error)) return [];
5216
5241
  throw error;
5217
5242
  }
5218
5243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brawnen/harnessly",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "harnessly": "dist/index.js"
@@ -26,9 +26,9 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@brawnen/harnessly-core": "0.1.0-alpha.0",
29
- "@brawnen/harnessly-host-claude-code": "0.1.0-alpha.0",
30
- "@brawnen/harnessly-host-shared": "0.1.0-alpha.0",
31
29
  "@brawnen/harnessly-host-codex": "0.1.0-alpha.0",
30
+ "@brawnen/harnessly-host-shared": "0.1.0-alpha.0",
31
+ "@brawnen/harnessly-host-claude-code": "0.1.0-alpha.0",
32
32
  "@brawnen/harnessly-shared": "0.1.0-alpha.0"
33
33
  },
34
34
  "scripts": {