@salaros/ai-harness 0.4.0 → 0.4.1

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/README.md CHANGED
@@ -45,7 +45,7 @@ If the repository already has agent files of its own, the first install keeps th
45
45
  | File | What the first install does |
46
46
  | --- | --- |
47
47
  | `AGENTS.md`, `docs/README.md` | Writes the harness's version and appends yours under `## This project`, for you to fold in |
48
- | `CLAUDE.md` | Adds `@AGENTS.md` at the top if it's missing |
48
+ | `CLAUDE.md` | Adds `@AGENTS.md` at the top if it's missing (on every update, too) |
49
49
  | `.claude/settings.json` | Merges by key: replaces the harness's hooks, keeps your permissions and hooks (on every update, too) |
50
50
  | `.mcp.json` | Adds the harness's MCP servers; yours win where both define one |
51
51
  | `.gitignore` | Appends the harness's patterns you don't have, under a comment |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salaros/ai-harness",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Installs and updates the agent harness from its upstream repository: hooks, skills, agents and the documentation chain, merged into an existing repository without touching its own work.",
5
5
  "bin": {
6
6
  "ai-harness": "scripts/update-harness.js"
@@ -141,7 +141,6 @@ const unionMerge = (from, ours, up) => ({ text: mergeRows(from, ours, up), confl
141
141
  // null to keep the file as it is. A later run has the receipt, so each runs once per file.
142
142
  const WITHOUT_BASE = {
143
143
  reconcile: appendProject,
144
- import: addImport,
145
144
  ignore: appendPatterns,
146
145
  keyed: (ours, theirs) => {
147
146
  const merged = mergeJson(ours, theirs, (o, t) => mergeKeys(o, t, 2));
@@ -179,12 +178,18 @@ function demoteHeadings(text) {
179
178
  return lines.map((line, i) => level[i] ? "#".repeat(Math.min(6, level[i] + 3 - top)) + line.slice(level[i]) : line).join("\n");
180
179
  }
181
180
 
182
- // CLAUDE.md is how Claude Code reaches AGENTS.md. A project's own CLAUDE.md, kept whole, leaves Claude
183
- // reading instructions that never mention the harness, so the import goes on top and the rest stays.
181
+ // CLAUDE.md is how Claude Code reaches AGENTS.md, so its import line is not something to merge: it is
182
+ // there or the harness is unreachable, whatever else the file says. The line is added on top of
183
+ // whatever the rest of the decision left, which covers both ways it goes missing: a project that
184
+ // wrote its own CLAUDE.md before it had the harness, and one that has a receipt but dropped the line
185
+ // since. Adding it to a file left full of conflict markers helps nobody, so that one is passed
186
+ // through; the run is already exiting 1 over it.
184
187
  const IMPORT = "@AGENTS.md";
185
- function addImport(ours) {
186
- if (ours.split("\n").some(l => l.trim() === IMPORT)) return null;
187
- return { outcome: "import added", bucket: "merged", write: `${IMPORT}\n\n${ours}` };
188
+ function ensureImport(done, raw, crlf) {
189
+ if (done.bucket === "conflicted") return done;
190
+ const text = lib.toLf(done.write === undefined ? raw : done.write);
191
+ if (text.split("\n").some(l => l.trim() === IMPORT)) return done;
192
+ return { outcome: "import added", bucket: "merged", write: lib.asFound(`${IMPORT}\n\n${text}`, crlf) };
188
193
  }
189
194
 
190
195
  // A .gitignore is a set of patterns, so the upstream's that this one lacks go at the end, under a
@@ -267,7 +272,8 @@ function merging(policy) {
267
272
  const held = reads.held();
268
273
  if (Buffer.isBuffer(facts.theirs)) return decideBinary({ ...facts, held }, reads);
269
274
  // A union table has no lines to conflict over, and no base means the project's rows win.
270
- return decideText({ ...facts, policy, raw: held }, policy === "union" ? { ...reads, merge: unionMerge } : reads);
275
+ const done = decideText({ ...facts, policy, raw: held }, policy === "union" ? { ...reads, merge: unionMerge } : reads);
276
+ return policy === "import" ? ensureImport(done, held, lib.isCrlf(held)) : done;
271
277
  };
272
278
  }
273
279