@kntic/kntic 0.4.3 → 0.4.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kntic/kntic",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "author": "Thomas Robak <contact@kntic.ai> (https://kntic.ai)",
5
5
  "description": "KNTIC CLI — bootstrap and manage KNTIC projects",
6
6
  "main": "src/index.js",
@@ -183,8 +183,12 @@ function extractLibOnly(tarball, destDir) {
183
183
  }
184
184
 
185
185
  /**
186
- * Extract .kntic/lib/** and .kntic/adrs/** from a tarball into the destination
187
- * directory. Clears both directories first, then extracts fresh content.
186
+ * Extract .kntic/lib/**, .kntic/adrs/**, and .kntic/hooks/gdr/** from a tarball
187
+ * into the destination directory.
188
+ *
189
+ * - .kntic/lib and .kntic/adrs are **replaced** (cleared first, then extracted).
190
+ * - .kntic/hooks/gdr is **updated** (existing files are overwritten or new files
191
+ * added, but files not present in the archive are preserved).
188
192
  *
189
193
  * @param {string} tarball – path to the .tar.gz file
190
194
  * @param {string} destDir – target directory (usually ".")
@@ -192,20 +196,34 @@ function extractLibOnly(tarball, destDir) {
192
196
  function extractUpdate(tarball, destDir) {
193
197
  const libDir = path.join(destDir, ".kntic", "lib");
194
198
  const adrsDir = path.join(destDir, ".kntic", "adrs");
199
+ const gdrDir = path.join(destDir, ".kntic", "hooks", "gdr");
195
200
 
196
201
  // Ensure target directories exist
197
202
  fs.mkdirSync(libDir, { recursive: true });
198
203
  fs.mkdirSync(adrsDir, { recursive: true });
204
+ fs.mkdirSync(gdrDir, { recursive: true });
199
205
 
200
- // Clear existing contents
206
+ // Clear lib and adrs (full replacement)
201
207
  clearDirectory(libDir);
202
208
  clearDirectory(adrsDir);
203
209
 
204
- // Extract both .kntic/lib/ and .kntic/adrs/ from the archive.
210
+ // Note: gdr is NOT cleared — update semantics (overwrite/add, preserve others)
211
+
212
+ // Extract .kntic/lib/ and .kntic/adrs/ (guaranteed to be in the archive)
205
213
  execSync(
206
214
  `tar xzf "${tarball}" -C "${destDir}" "./.kntic/lib/" "./.kntic/adrs/"`,
207
215
  { stdio: "pipe" }
208
216
  );
217
+
218
+ // Extract .kntic/hooks/gdr/ separately — archive may not contain it
219
+ try {
220
+ execSync(
221
+ `tar xzf "${tarball}" -C "${destDir}" "./.kntic/hooks/gdr/"`,
222
+ { stdio: "pipe" }
223
+ );
224
+ } catch (_) {
225
+ // No .kntic/hooks/gdr/ in the archive — that's fine
226
+ }
209
227
  }
210
228
 
211
229
  async function update(options = {}) {
@@ -224,7 +242,7 @@ async function update(options = {}) {
224
242
  console.log("Updating .kntic/lib …");
225
243
  extractLibOnly(tmpFile, ".");
226
244
  } else {
227
- console.log("Updating .kntic/lib and .kntic/adrs …");
245
+ console.log("Updating .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr …");
228
246
  extractUpdate(tmpFile, ".");
229
247
  }
230
248
 
@@ -238,7 +256,7 @@ async function update(options = {}) {
238
256
  if (libOnly) {
239
257
  console.log("Done. .kntic/lib updated successfully.");
240
258
  } else {
241
- console.log("Done. .kntic/lib and .kntic/adrs updated successfully.");
259
+ console.log("Done. .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr updated successfully.");
242
260
  }
243
261
  }
244
262
 
@@ -216,12 +216,13 @@ describe("extractUpdate", () => {
216
216
  fs.rmSync(tmpDir, { recursive: true, force: true });
217
217
  });
218
218
 
219
- it("extracts both .kntic/lib and .kntic/adrs from the archive", () => {
219
+ it("extracts .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr from the archive", () => {
220
220
  const tarball = createTarball(tmpDir, {
221
221
  ".kntic/lib/orchestrator.py": "# orchestrator\n",
222
222
  ".kntic/lib/skills/validator.py": "# validator\n",
223
223
  ".kntic/adrs/ADR-001.md": "# ADR 001\n",
224
224
  ".kntic/adrs/ADR-002.md": "# ADR 002\n",
225
+ ".kntic/hooks/gdr/check.sh": "#!/bin/sh\necho check\n",
225
226
  ".kntic/MEMORY.MD": "# Memory\n",
226
227
  "README.md": "# Hello\n",
227
228
  });
@@ -248,7 +249,13 @@ describe("extractUpdate", () => {
248
249
  "# ADR 002\n"
249
250
  );
250
251
 
251
- // Files outside lib/adrs must NOT be extracted
252
+ // hooks/gdr files must be extracted
253
+ assert.equal(
254
+ fs.readFileSync(path.join(destDir, ".kntic", "hooks", "gdr", "check.sh"), "utf8"),
255
+ "#!/bin/sh\necho check\n"
256
+ );
257
+
258
+ // Files outside lib/adrs/hooks/gdr must NOT be extracted
252
259
  assert.ok(
253
260
  !fs.existsSync(path.join(destDir, ".kntic", "MEMORY.MD")),
254
261
  "MEMORY.MD must not be extracted"
@@ -289,7 +296,69 @@ describe("extractUpdate", () => {
289
296
  );
290
297
  });
291
298
 
292
- it("preserves files outside .kntic/lib and .kntic/adrs", () => {
299
+ it("updates .kntic/hooks/gdr without clearing existing files (update semantics)", () => {
300
+ const gdrDir = path.join(destDir, ".kntic", "hooks", "gdr");
301
+ fs.mkdirSync(gdrDir, { recursive: true });
302
+ fs.writeFileSync(path.join(gdrDir, "existing-hook.sh"), "#!/bin/sh\necho existing\n");
303
+ fs.writeFileSync(path.join(gdrDir, "shared-hook.sh"), "#!/bin/sh\necho old\n");
304
+
305
+ const tarball = createTarball(tmpDir, {
306
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
307
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
308
+ ".kntic/hooks/gdr/shared-hook.sh": "#!/bin/sh\necho new\n",
309
+ ".kntic/hooks/gdr/new-hook.sh": "#!/bin/sh\necho added\n",
310
+ });
311
+
312
+ extractUpdate(tarball, destDir);
313
+
314
+ // Existing file NOT in archive must be preserved (update semantics, not replace)
315
+ assert.equal(
316
+ fs.readFileSync(path.join(gdrDir, "existing-hook.sh"), "utf8"),
317
+ "#!/bin/sh\necho existing\n",
318
+ "existing hook not in archive must be preserved"
319
+ );
320
+
321
+ // File in both archive and disk must be overwritten
322
+ assert.equal(
323
+ fs.readFileSync(path.join(gdrDir, "shared-hook.sh"), "utf8"),
324
+ "#!/bin/sh\necho new\n",
325
+ "shared hook must be overwritten with archive version"
326
+ );
327
+
328
+ // New file from archive must be added
329
+ assert.equal(
330
+ fs.readFileSync(path.join(gdrDir, "new-hook.sh"), "utf8"),
331
+ "#!/bin/sh\necho added\n",
332
+ "new hook from archive must be added"
333
+ );
334
+ });
335
+
336
+ it("works when archive has no .kntic/hooks/gdr directory", () => {
337
+ const gdrDir = path.join(destDir, ".kntic", "hooks", "gdr");
338
+ fs.mkdirSync(gdrDir, { recursive: true });
339
+ fs.writeFileSync(path.join(gdrDir, "my-hook.sh"), "#!/bin/sh\necho mine\n");
340
+
341
+ const tarball = createTarball(tmpDir, {
342
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
343
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
344
+ });
345
+
346
+ // Should not throw
347
+ extractUpdate(tarball, destDir);
348
+
349
+ // Existing gdr hook must be preserved
350
+ assert.equal(
351
+ fs.readFileSync(path.join(gdrDir, "my-hook.sh"), "utf8"),
352
+ "#!/bin/sh\necho mine\n",
353
+ "existing gdr hook must be preserved when archive has no gdr"
354
+ );
355
+
356
+ // lib and adrs must still be extracted
357
+ assert.ok(fs.existsSync(path.join(destDir, ".kntic", "lib", "orchestrator.py")));
358
+ assert.ok(fs.existsSync(path.join(destDir, ".kntic", "adrs", "ADR-001.md")));
359
+ });
360
+
361
+ it("preserves files outside .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr", () => {
293
362
  const knticDir = path.join(destDir, ".kntic");
294
363
  fs.mkdirSync(knticDir, { recursive: true });
295
364
  fs.writeFileSync(path.join(knticDir, "MEMORY.MD"), "# My memory\n");
@@ -298,6 +367,7 @@ describe("extractUpdate", () => {
298
367
  const tarball = createTarball(tmpDir, {
299
368
  ".kntic/lib/orchestrator.py": "# orchestrator\n",
300
369
  ".kntic/adrs/ADR-001.md": "# ADR 001\n",
370
+ ".kntic/hooks/gdr/check.sh": "#!/bin/sh\n",
301
371
  ".kntic/MEMORY.MD": "# Archive memory\n",
302
372
  "README.md": "# Archive readme\n",
303
373
  });
@@ -9,8 +9,8 @@ function usage() {
9
9
  console.log(" start Build and start KNTIC services via docker compose (uses kntic.yml + .kntic.env)");
10
10
  console.log(" --screen Run inside a GNU screen session");
11
11
  console.log(" stop Stop KNTIC services via docker compose");
12
- console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib and .kntic/adrs");
13
- console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs)");
12
+ console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr");
13
+ console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs and .kntic/hooks/gdr)");
14
14
  console.log("");
15
15
  }
16
16