@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 +1 -1
- package/src/commands/update.js +24 -10
- package/src/commands/update.test.js +74 -24
- package/src/commands/usage.js +2 -2
package/package.json
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -183,12 +183,13 @@ function extractLibOnly(tarball, destDir) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
|
-
* Extract .kntic/lib/**, .kntic/adrs/**, and .kntic/hooks/
|
|
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/
|
|
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
|
|
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(
|
|
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:
|
|
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/
|
|
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/
|
|
225
|
+
`tar xzf "${tarball}" -C "${destDir}" "./.kntic/hooks/gia/internal/"`,
|
|
222
226
|
{ stdio: "pipe" }
|
|
223
227
|
);
|
|
224
228
|
} catch (_) {
|
|
225
|
-
// No .kntic/hooks/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
252
|
+
// hooks/gia/internal files must be extracted
|
|
253
253
|
assert.equal(
|
|
254
|
-
fs.readFileSync(path.join(destDir, ".kntic", "hooks", "
|
|
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/
|
|
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/
|
|
300
|
-
const
|
|
301
|
-
fs.mkdirSync(
|
|
302
|
-
fs.writeFileSync(path.join(
|
|
303
|
-
fs.writeFileSync(path.join(
|
|
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/
|
|
309
|
-
".kntic/hooks/
|
|
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(
|
|
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(
|
|
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(
|
|
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/
|
|
337
|
-
const
|
|
338
|
-
fs.mkdirSync(
|
|
339
|
-
fs.writeFileSync(path.join(
|
|
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
|
|
349
|
+
// Existing gia/internal hook must be preserved
|
|
350
350
|
assert.equal(
|
|
351
|
-
fs.readFileSync(path.join(
|
|
351
|
+
fs.readFileSync(path.join(giaInternalDir, "my-hook.sh"), "utf8"),
|
|
352
352
|
"#!/bin/sh\necho mine\n",
|
|
353
|
-
"existing
|
|
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("
|
|
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/
|
|
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
|
});
|
package/src/commands/usage.js
CHANGED
|
@@ -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/
|
|
13
|
-
console.log(" --lib-only Update only .kntic/lib (skip .kntic/adrs and .kntic/
|
|
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
|
|