@uzysjung/agent-harness 26.131.1 → 26.132.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/index.js CHANGED
@@ -712,7 +712,7 @@ var cac = (name = "") => new CAC(name);
712
712
  // package.json
713
713
  var package_default = {
714
714
  name: "@uzysjung/agent-harness",
715
- version: "26.131.1",
715
+ version: "26.132.0",
716
716
  description: "Curate vetted AI-coding skills & plugins by your tech stack \u2014 install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
717
717
  type: "module",
718
718
  publishConfig: {
@@ -1743,6 +1743,12 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as rea
1743
1743
  import { dirname as dirname3, join as join8 } from "path";
1744
1744
  var INSTALL_LOG_FILENAME = ".harness-install.json";
1745
1745
  var INSTALL_LOG_VERSION = 1;
1746
+ var POLICY_DIRS = [
1747
+ { dir: "rules", ext: ".md" },
1748
+ { dir: "agents", ext: ".md" },
1749
+ { dir: "commands/uzys", ext: ".md" },
1750
+ { dir: "hooks", ext: ".sh" }
1751
+ ];
1746
1752
  function buildAssetEntries(report, scope) {
1747
1753
  if (!report) return [];
1748
1754
  return report.attempted.filter((r) => r.ok).map((r) => assetToLogEntry(r.asset, scope, r.version));
@@ -1857,6 +1863,22 @@ function collectSkillHashes(projectDir) {
1857
1863
  sha256: hashContent(readFileSync7(join8(skillsDir, rel), "utf8"))
1858
1864
  }));
1859
1865
  }
1866
+ function collectPolicyHashes(projectDir, templatesDir) {
1867
+ const out = [];
1868
+ for (const { dir, ext } of POLICY_DIRS) {
1869
+ const targetDir = join8(projectDir, ".claude", dir);
1870
+ const sourceDir = join8(templatesDir, dir);
1871
+ for (const rel of listFilesRecursive(targetDir)) {
1872
+ if (!rel.endsWith(ext)) continue;
1873
+ if (!existsSync8(join8(sourceDir, rel))) continue;
1874
+ out.push({
1875
+ path: `${dir}/${rel}`,
1876
+ sha256: hashContent(readFileSync7(join8(targetDir, rel), "utf8"))
1877
+ });
1878
+ }
1879
+ }
1880
+ return out;
1881
+ }
1860
1882
  function writeInstallLog(projectDir, log) {
1861
1883
  const path = join8(projectDir, ".claude", INSTALL_LOG_FILENAME);
1862
1884
  mkdirSync3(dirname3(path), { recursive: true });
@@ -2167,38 +2189,21 @@ function runUpdateMode(projectDir, templatesDir) {
2167
2189
  pruned: {},
2168
2190
  staleHookRefs: [],
2169
2191
  claudeMdUpdated: false,
2170
- skillsBackedUp: []
2192
+ skillsBackedUp: [],
2193
+ policyBackedUp: []
2171
2194
  };
2172
- const targets = [
2173
- {
2174
- target: join10(claudeDir, "rules"),
2175
- source: join10(templatesDir, "rules"),
2176
- pattern: ".md",
2177
- label: ".claude/rules"
2178
- },
2179
- {
2180
- target: join10(claudeDir, "agents"),
2181
- source: join10(templatesDir, "agents"),
2182
- pattern: ".md",
2183
- label: ".claude/agents"
2184
- },
2185
- {
2186
- target: join10(claudeDir, "commands/uzys"),
2187
- source: join10(templatesDir, "commands/uzys"),
2188
- pattern: ".md",
2189
- label: ".claude/commands/uzys"
2190
- },
2191
- {
2192
- target: join10(claudeDir, "hooks"),
2193
- source: join10(templatesDir, "hooks"),
2194
- pattern: ".sh",
2195
- label: ".claude/hooks"
2196
- }
2197
- ];
2198
- for (const t of targets) {
2199
- report.updated[t.label] = updateDir(t.target, t.source, t.pattern);
2200
- report.pruned[t.label] = pruneOrphans(t.target, t.source, t.pattern);
2201
- }
2195
+ const policyBase = policyBaseline(projectDir);
2196
+ for (const { dir, ext } of POLICY_DIRS) {
2197
+ const target = join10(claudeDir, dir);
2198
+ const source = join10(templatesDir, dir);
2199
+ const label = `.claude/${dir}`;
2200
+ const ctx = { prefix: dir, baseline: policyBase };
2201
+ const synced = updateDir(target, source, ext, ctx);
2202
+ report.updated[label] = synced.updated;
2203
+ report.policyBackedUp.push(...synced.backedUp);
2204
+ report.pruned[label] = pruneOrphans(target, source, ext, ctx);
2205
+ }
2206
+ refreshPolicyBaseline(projectDir, templatesDir);
2202
2207
  const skillSync = syncSkills(
2203
2208
  join10(claudeDir, "skills"),
2204
2209
  join10(templatesDir, "skills"),
@@ -2219,19 +2224,30 @@ function runUpdateMode(projectDir, templatesDir) {
2219
2224
  }
2220
2225
  return report;
2221
2226
  }
2222
- function updateDir(target, source, ext) {
2223
- if (!existsSync11(target) || !existsSync11(source)) return 0;
2224
- let count = 0;
2227
+ function isHarnessOwned(ctx, rel, current) {
2228
+ const recorded = ctx.baseline.get(`${ctx.prefix}/${rel}`);
2229
+ return recorded !== void 0 && recorded === hashContent(current);
2230
+ }
2231
+ function updateDir(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2232
+ if (!existsSync11(target) || !existsSync11(source)) return { updated: 0, backedUp: [] };
2233
+ let updated = 0;
2234
+ const backedUp = [];
2225
2235
  for (const file of readdirSync3(target)) {
2226
2236
  if (!file.endsWith(ext)) continue;
2227
2237
  const targetFile = join10(target, file);
2228
2238
  const sourceFile = join10(source, file);
2229
- if (existsSync11(sourceFile)) {
2230
- copyFileSync2(sourceFile, targetFile);
2231
- count++;
2239
+ if (!existsSync11(sourceFile)) continue;
2240
+ const next = readFileSync10(sourceFile, "utf8");
2241
+ const current = readFileSync10(targetFile, "utf8");
2242
+ if (current === next) continue;
2243
+ if (!isHarnessOwned(ctx, file, current)) {
2244
+ backupFile(targetFile, now);
2245
+ backedUp.push(`${ctx.prefix}/${file}`);
2232
2246
  }
2247
+ writeFileSync8(targetFile, next);
2248
+ updated++;
2233
2249
  }
2234
- return count;
2250
+ return { updated, backedUp };
2235
2251
  }
2236
2252
  function syncSkills(targetDir, sourceDir, baseline, now = /* @__PURE__ */ new Date()) {
2237
2253
  if (!existsSync11(targetDir) || !existsSync11(sourceDir)) return { updated: 0, backedUp: [] };
@@ -2267,6 +2283,22 @@ function skillBaseline(projectDir) {
2267
2283
  const log = readInstallLog(projectDir);
2268
2284
  return new Map((log?.skillFiles ?? []).map((f) => [f.path, f.sha256]));
2269
2285
  }
2286
+ function policyBaseline(projectDir) {
2287
+ const log = readInstallLog(projectDir);
2288
+ return new Map((log?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
2289
+ }
2290
+ function refreshPolicyBaseline(projectDir, templatesDir) {
2291
+ const log = readInstallLog(projectDir);
2292
+ if (!log) return;
2293
+ const policyFiles = collectPolicyHashes(projectDir, templatesDir);
2294
+ const next = { ...log };
2295
+ if (policyFiles.length > 0) next.policyFiles = policyFiles;
2296
+ else delete next.policyFiles;
2297
+ try {
2298
+ writeInstallLog(projectDir, next);
2299
+ } catch {
2300
+ }
2301
+ }
2270
2302
  function refreshSkillBaseline(projectDir) {
2271
2303
  const log = readInstallLog(projectDir);
2272
2304
  if (!log) return;
@@ -2279,7 +2311,7 @@ function refreshSkillBaseline(projectDir) {
2279
2311
  } catch {
2280
2312
  }
2281
2313
  }
2282
- function pruneOrphans(target, source, ext) {
2314
+ function pruneOrphans(target, source, ext, ctx, now = /* @__PURE__ */ new Date()) {
2283
2315
  if (!existsSync11(target) || !existsSync11(source)) return [];
2284
2316
  const removed = [];
2285
2317
  for (const file of readdirSync3(target)) {
@@ -2287,7 +2319,10 @@ function pruneOrphans(target, source, ext) {
2287
2319
  const sourceFile = join10(source, file);
2288
2320
  if (!existsSync11(sourceFile)) {
2289
2321
  const targetFile = join10(target, file);
2322
+ if (!ctx.baseline.has(`${ctx.prefix}/${file}`)) continue;
2290
2323
  try {
2324
+ const current = readFileSync10(targetFile, "utf8");
2325
+ if (!isHarnessOwned(ctx, file, current)) backupFile(targetFile, now);
2291
2326
  unlinkSync(targetFile);
2292
2327
  removed.push(file);
2293
2328
  } catch {
@@ -2353,7 +2388,8 @@ function runInstall(ctx) {
2353
2388
  return runUpdateInstall(ctx, templatesDir, backupPath);
2354
2389
  }
2355
2390
  const manifestSpec = buildManifestSpec(spec);
2356
- const base = spec.cli.includes("claude") ? installClaudeBaseline(manifestSpec, projectDir, templatesDir) : emptyClaudeBaseline();
2391
+ const policyBase = new Map((previousLog?.policyFiles ?? []).map((f) => [f.path, f.sha256]));
2392
+ const base = spec.cli.includes("claude") ? installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBase) : emptyClaudeBaseline();
2357
2393
  const mcpResult = composeAndWriteMcp(harnessRoot, projectDir, spec);
2358
2394
  const ciScaffold = isAssetSelected("ci-scaffold", {
2359
2395
  tracks: spec.tracks,
@@ -2453,7 +2489,21 @@ function emptyClaudeBaseline() {
2453
2489
  backups: []
2454
2490
  };
2455
2491
  }
2456
- function installClaudeBaseline(manifestSpec, projectDir, templatesDir) {
2492
+ function backupEditedPolicyFile(entryTarget, target, source, baseline) {
2493
+ const prefix = ".claude/";
2494
+ if (!entryTarget.startsWith(prefix)) return null;
2495
+ const rel = entryTarget.slice(prefix.length);
2496
+ if (!POLICY_DIRS.some(({ dir, ext }) => rel.startsWith(`${dir}/`) && rel.endsWith(ext))) {
2497
+ return null;
2498
+ }
2499
+ if (!existsSync12(target)) return null;
2500
+ const current = readFileSync11(target, "utf-8");
2501
+ if (current === readFileSync11(source, "utf-8")) return null;
2502
+ const recorded = baseline.get(rel);
2503
+ if (recorded !== void 0 && recorded === hashContent(current)) return null;
2504
+ return backupFile(target);
2505
+ }
2506
+ function installClaudeBaseline(manifestSpec, projectDir, templatesDir, policyBase) {
2457
2507
  ensureProjectSkeleton(projectDir);
2458
2508
  const result = emptyClaudeBaseline();
2459
2509
  const manifest = buildManifest(manifestSpec);
@@ -2473,6 +2523,11 @@ function installClaudeBaseline(manifestSpec, projectDir, templatesDir) {
2473
2523
  if (backup) {
2474
2524
  result.backups.push(backup);
2475
2525
  }
2526
+ } else {
2527
+ const backup = backupEditedPolicyFile(entry.target, target, source, policyBase);
2528
+ if (backup) {
2529
+ result.backups.push(backup);
2530
+ }
2476
2531
  }
2477
2532
  copyFile(source, target);
2478
2533
  result.filesCopied += 1;
@@ -2580,7 +2635,12 @@ function writeInstallLogSafe(ctx, external, rootClaudeMdLog, previousLog, claude
2580
2635
  rootFiles
2581
2636
  );
2582
2637
  const skillFiles = collectSkillHashes(ctx.projectDir);
2583
- writeInstallLog(ctx.projectDir, skillFiles.length > 0 ? { ...log, skillFiles } : log);
2638
+ const policyFiles = collectPolicyHashes(ctx.projectDir, join11(ctx.harnessRoot, "templates"));
2639
+ writeInstallLog(ctx.projectDir, {
2640
+ ...log,
2641
+ ...skillFiles.length > 0 ? { skillFiles } : {},
2642
+ ...policyFiles.length > 0 ? { policyFiles } : {}
2643
+ });
2584
2644
  } catch (e2) {
2585
2645
  ctx.onProgress?.({
2586
2646
  type: "install-log-error",
@@ -3033,6 +3093,15 @@ function renderPhase1Rows(log, baseline, verbose = false, withEcc = false, claud
3033
3093
  )
3034
3094
  );
3035
3095
  }
3096
+ if (baseline.updateMode.policyBackedUp.length > 0) {
3097
+ log(
3098
+ assetRow(
3099
+ "skip",
3100
+ "edited policy files",
3101
+ `${baseline.updateMode.policyBackedUp.length} backed up as *.backup-<time>`
3102
+ )
3103
+ );
3104
+ }
3036
3105
  if (baseline.updateMode.staleHookRefs.length > 0) {
3037
3106
  log(
3038
3107
  assetRow(