@kntic/kntic 0.4.4 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kntic/kntic",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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,12 +183,13 @@ function extractLibOnly(tarball, destDir) {
183
183
  }
184
184
 
185
185
  /**
186
- * Extract .kntic/lib/**, .kntic/adrs/**, and .kntic/hooks/gdr/** from a tarball
186
+ * Extract .kntic/lib/**, .kntic/adrs/**, and .kntic/hooks/gia/internal/** from a tarball
187
187
  * into the destination directory.
188
188
  *
189
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
190
+ * - .kntic/hooks/gia/internal is **updated** (existing files are overwritten or new files
191
191
  * added, but files not present in the archive are preserved).
192
+ * - .kntic/gia/weights.json is **replaced** if present in the archive.
192
193
  *
193
194
  * @param {string} tarball – path to the .tar.gz file
194
195
  * @param {string} destDir – target directory (usually ".")
@@ -196,18 +197,21 @@ function extractLibOnly(tarball, destDir) {
196
197
  function extractUpdate(tarball, destDir) {
197
198
  const libDir = path.join(destDir, ".kntic", "lib");
198
199
  const adrsDir = path.join(destDir, ".kntic", "adrs");
199
- const gdrDir = path.join(destDir, ".kntic", "hooks", "gdr");
200
+ const giaInternalDir = path.join(destDir, ".kntic", "hooks", "gia", "internal");
201
+ const giaDir = path.join(destDir, ".kntic", "gia");
200
202
 
201
203
  // Ensure target directories exist
202
204
  fs.mkdirSync(libDir, { recursive: true });
203
205
  fs.mkdirSync(adrsDir, { recursive: true });
204
- fs.mkdirSync(gdrDir, { recursive: true });
206
+ fs.mkdirSync(giaInternalDir, { recursive: true });
207
+ fs.mkdirSync(giaDir, { recursive: true });
205
208
 
206
209
  // Clear lib and adrs (full replacement)
207
210
  clearDirectory(libDir);
208
211
  clearDirectory(adrsDir);
209
212
 
210
- // Note: gdr is NOT cleared — update semantics (overwrite/add, preserve others)
213
+ // Note: gia/internal hooks are NOT cleared — update semantics (overwrite/add, preserve others)
214
+ // Note: gia is NOT cleared — only weights.json is replaced
211
215
 
212
216
  // Extract .kntic/lib/ and .kntic/adrs/ (guaranteed to be in the archive)
213
217
  execSync(
@@ -215,14 +219,24 @@ function extractUpdate(tarball, destDir) {
215
219
  { stdio: "pipe" }
216
220
  );
217
221
 
218
- // Extract .kntic/hooks/gdr/ separately — archive may not contain it
222
+ // Extract .kntic/hooks/gia/internal/ separately — archive may not contain it
219
223
  try {
220
224
  execSync(
221
- `tar xzf "${tarball}" -C "${destDir}" "./.kntic/hooks/gdr/"`,
225
+ `tar xzf "${tarball}" -C "${destDir}" "./.kntic/hooks/gia/internal/"`,
222
226
  { stdio: "pipe" }
223
227
  );
224
228
  } catch (_) {
225
- // No .kntic/hooks/gdr/ in the archive — that's fine
229
+ // No .kntic/hooks/gia/internal/ in the archive — that's fine
230
+ }
231
+
232
+ // Extract .kntic/gia/weights.json separately — archive may not contain it
233
+ try {
234
+ execSync(
235
+ `tar xzf "${tarball}" -C "${destDir}" "./.kntic/gia/weights.json"`,
236
+ { stdio: "pipe" }
237
+ );
238
+ } catch (_) {
239
+ // No .kntic/gia/weights.json in the archive — that's fine
226
240
  }
227
241
  }
228
242
 
@@ -242,7 +256,7 @@ async function update(options = {}) {
242
256
  console.log("Updating .kntic/lib …");
243
257
  extractLibOnly(tmpFile, ".");
244
258
  } else {
245
- console.log("Updating .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr …");
259
+ console.log("Updating .kntic/lib, .kntic/adrs, .kntic/hooks/gia/internal, and .kntic/gia/weights.json …");
246
260
  extractUpdate(tmpFile, ".");
247
261
  }
248
262
 
@@ -256,7 +270,7 @@ async function update(options = {}) {
256
270
  if (libOnly) {
257
271
  console.log("Done. .kntic/lib updated successfully.");
258
272
  } else {
259
- console.log("Done. .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr updated successfully.");
273
+ console.log("Done. .kntic/lib, .kntic/adrs, .kntic/hooks/gia/internal, and .kntic/gia/weights.json updated successfully.");
260
274
  }
261
275
  }
262
276
 
@@ -216,13 +216,13 @@ describe("extractUpdate", () => {
216
216
  fs.rmSync(tmpDir, { recursive: true, force: true });
217
217
  });
218
218
 
219
- it("extracts .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr from the archive", () => {
219
+ it("extracts .kntic/lib, .kntic/adrs, and .kntic/hooks/gia/internal 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
+ ".kntic/hooks/gia/internal/check.sh": "#!/bin/sh\necho check\n",
226
226
  ".kntic/MEMORY.MD": "# Memory\n",
227
227
  "README.md": "# Hello\n",
228
228
  });
@@ -249,13 +249,13 @@ describe("extractUpdate", () => {
249
249
  "# ADR 002\n"
250
250
  );
251
251
 
252
- // hooks/gdr files must be extracted
252
+ // hooks/gia/internal files must be extracted
253
253
  assert.equal(
254
- fs.readFileSync(path.join(destDir, ".kntic", "hooks", "gdr", "check.sh"), "utf8"),
254
+ fs.readFileSync(path.join(destDir, ".kntic", "hooks", "gia", "internal", "check.sh"), "utf8"),
255
255
  "#!/bin/sh\necho check\n"
256
256
  );
257
257
 
258
- // Files outside lib/adrs/hooks/gdr must NOT be extracted
258
+ // Files outside lib/adrs/hooks/gia/internal must NOT be extracted
259
259
  assert.ok(
260
260
  !fs.existsSync(path.join(destDir, ".kntic", "MEMORY.MD")),
261
261
  "MEMORY.MD must not be extracted"
@@ -296,47 +296,47 @@ describe("extractUpdate", () => {
296
296
  );
297
297
  });
298
298
 
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");
299
+ it("updates .kntic/hooks/gia/internal without clearing existing files (update semantics)", () => {
300
+ const giaInternalDir = path.join(destDir, ".kntic", "hooks", "gia", "internal");
301
+ fs.mkdirSync(giaInternalDir, { recursive: true });
302
+ fs.writeFileSync(path.join(giaInternalDir, "existing-hook.sh"), "#!/bin/sh\necho existing\n");
303
+ fs.writeFileSync(path.join(giaInternalDir, "shared-hook.sh"), "#!/bin/sh\necho old\n");
304
304
 
305
305
  const tarball = createTarball(tmpDir, {
306
306
  ".kntic/lib/orchestrator.py": "# orchestrator\n",
307
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",
308
+ ".kntic/hooks/gia/internal/shared-hook.sh": "#!/bin/sh\necho new\n",
309
+ ".kntic/hooks/gia/internal/new-hook.sh": "#!/bin/sh\necho added\n",
310
310
  });
311
311
 
312
312
  extractUpdate(tarball, destDir);
313
313
 
314
314
  // Existing file NOT in archive must be preserved (update semantics, not replace)
315
315
  assert.equal(
316
- fs.readFileSync(path.join(gdrDir, "existing-hook.sh"), "utf8"),
316
+ fs.readFileSync(path.join(giaInternalDir, "existing-hook.sh"), "utf8"),
317
317
  "#!/bin/sh\necho existing\n",
318
318
  "existing hook not in archive must be preserved"
319
319
  );
320
320
 
321
321
  // File in both archive and disk must be overwritten
322
322
  assert.equal(
323
- fs.readFileSync(path.join(gdrDir, "shared-hook.sh"), "utf8"),
323
+ fs.readFileSync(path.join(giaInternalDir, "shared-hook.sh"), "utf8"),
324
324
  "#!/bin/sh\necho new\n",
325
325
  "shared hook must be overwritten with archive version"
326
326
  );
327
327
 
328
328
  // New file from archive must be added
329
329
  assert.equal(
330
- fs.readFileSync(path.join(gdrDir, "new-hook.sh"), "utf8"),
330
+ fs.readFileSync(path.join(giaInternalDir, "new-hook.sh"), "utf8"),
331
331
  "#!/bin/sh\necho added\n",
332
332
  "new hook from archive must be added"
333
333
  );
334
334
  });
335
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");
336
+ it("works when archive has no .kntic/hooks/gia/internal directory", () => {
337
+ const giaInternalDir = path.join(destDir, ".kntic", "hooks", "gia", "internal");
338
+ fs.mkdirSync(giaInternalDir, { recursive: true });
339
+ fs.writeFileSync(path.join(giaInternalDir, "my-hook.sh"), "#!/bin/sh\necho mine\n");
340
340
 
341
341
  const tarball = createTarball(tmpDir, {
342
342
  ".kntic/lib/orchestrator.py": "# orchestrator\n",
@@ -346,11 +346,11 @@ describe("extractUpdate", () => {
346
346
  // Should not throw
347
347
  extractUpdate(tarball, destDir);
348
348
 
349
- // Existing gdr hook must be preserved
349
+ // Existing gia/internal hook must be preserved
350
350
  assert.equal(
351
- fs.readFileSync(path.join(gdrDir, "my-hook.sh"), "utf8"),
351
+ fs.readFileSync(path.join(giaInternalDir, "my-hook.sh"), "utf8"),
352
352
  "#!/bin/sh\necho mine\n",
353
- "existing gdr hook must be preserved when archive has no gdr"
353
+ "existing gia/internal hook must be preserved when archive has no gia/internal"
354
354
  );
355
355
 
356
356
  // lib and adrs must still be extracted
@@ -358,7 +358,57 @@ describe("extractUpdate", () => {
358
358
  assert.ok(fs.existsSync(path.join(destDir, ".kntic", "adrs", "ADR-001.md")));
359
359
  });
360
360
 
361
- it("preserves files outside .kntic/lib, .kntic/adrs, and .kntic/hooks/gdr", () => {
361
+ it("replaces .kntic/gia/weights.json from the archive", () => {
362
+ const giaDir = path.join(destDir, ".kntic", "gia");
363
+ fs.mkdirSync(giaDir, { recursive: true });
364
+ fs.writeFileSync(path.join(giaDir, "weights.json"), '{"old": true}\n');
365
+ fs.writeFileSync(path.join(giaDir, "state.json"), '{"keep": true}\n');
366
+
367
+ const tarball = createTarball(tmpDir, {
368
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
369
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
370
+ ".kntic/gia/weights.json": '{"new": true}\n',
371
+ });
372
+
373
+ extractUpdate(tarball, destDir);
374
+
375
+ // weights.json must be replaced
376
+ assert.equal(
377
+ fs.readFileSync(path.join(giaDir, "weights.json"), "utf8"),
378
+ '{"new": true}\n',
379
+ "weights.json must be replaced with archive version"
380
+ );
381
+
382
+ // Other files in gia must be preserved
383
+ assert.equal(
384
+ fs.readFileSync(path.join(giaDir, "state.json"), "utf8"),
385
+ '{"keep": true}\n',
386
+ "state.json must not be touched"
387
+ );
388
+ });
389
+
390
+ it("works when archive has no .kntic/gia/weights.json", () => {
391
+ const giaDir = path.join(destDir, ".kntic", "gia");
392
+ fs.mkdirSync(giaDir, { recursive: true });
393
+ fs.writeFileSync(path.join(giaDir, "weights.json"), '{"existing": true}\n');
394
+
395
+ const tarball = createTarball(tmpDir, {
396
+ ".kntic/lib/orchestrator.py": "# orchestrator\n",
397
+ ".kntic/adrs/ADR-001.md": "# ADR 001\n",
398
+ });
399
+
400
+ // Should not throw
401
+ extractUpdate(tarball, destDir);
402
+
403
+ // Existing weights.json must be preserved when not in archive
404
+ assert.equal(
405
+ fs.readFileSync(path.join(giaDir, "weights.json"), "utf8"),
406
+ '{"existing": true}\n',
407
+ "existing weights.json must be preserved when archive has none"
408
+ );
409
+ });
410
+
411
+ it("preserves files outside .kntic/lib, .kntic/adrs, and .kntic/hooks/gia/internal", () => {
362
412
  const knticDir = path.join(destDir, ".kntic");
363
413
  fs.mkdirSync(knticDir, { recursive: true });
364
414
  fs.writeFileSync(path.join(knticDir, "MEMORY.MD"), "# My memory\n");
@@ -367,7 +417,7 @@ describe("extractUpdate", () => {
367
417
  const tarball = createTarball(tmpDir, {
368
418
  ".kntic/lib/orchestrator.py": "# orchestrator\n",
369
419
  ".kntic/adrs/ADR-001.md": "# ADR 001\n",
370
- ".kntic/hooks/gdr/check.sh": "#!/bin/sh\n",
420
+ ".kntic/hooks/gia/internal/check.sh": "#!/bin/sh\n",
371
421
  ".kntic/MEMORY.MD": "# Archive memory\n",
372
422
  "README.md": "# Archive readme\n",
373
423
  });
@@ -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, .kntic/adrs, and .kntic/hooks/gdr");
13
- console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs and .kntic/hooks/gdr)");
12
+ console.log(" update Download the latest KNTIC bootstrap and update .kntic/lib, .kntic/adrs, .kntic/hooks/gia/internal, and .kntic/gia/weights.json");
13
+ console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs, .kntic/hooks/gia/internal, and .kntic/gia/weights.json)");
14
14
  console.log("");
15
15
  }
16
16