akm-cli 0.8.0-rc.7 → 0.8.0-rc.8
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/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/dist/cli.js +1 -1
- package/dist/commands/consolidate.js +83 -11
- package/dist/commands/health.js +8 -0
- package/dist/core/config-schema.js +3 -0
- package/dist/core/state-db.js +1 -1
- package/dist/registry/factory.js +1 -1
- package/dist/registry/providers/skills-sh.js +1 -1
- package/dist/scripts/migrate-storage.js +3 -1
- package/dist/sources/providers/git.js +25 -9
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.0] - 2026-05-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Consolidation `delete_failed` on stale index entries** — when consolidation
|
|
14
|
+
successfully deleted a memory file, the index DB was not re-indexed between
|
|
15
|
+
runs. Subsequent runs loaded the stale DB entry into their memory map, the LLM
|
|
16
|
+
re-proposed the deletion, and `deleteAssetFromSource` threw "not found in
|
|
17
|
+
source" — appearing as `delete_failed` in skipReasons. Fix: `loadMemoriesForSource`
|
|
18
|
+
now filters entries whose file no longer exists on disk before building chunks,
|
|
19
|
+
so phantom memories are never sent to the LLM. A secondary catch in the delete
|
|
20
|
+
handler emits `delete_already_gone` instead of `delete_failed` when the file
|
|
21
|
+
is confirmed absent.
|
|
22
|
+
|
|
9
23
|
> **CI / Docker users:** the 0.8.0 storage split moved `akm.lock`, the event
|
|
10
24
|
> database, and the registry cache out of `$XDG_CONFIG_HOME/akm/` into
|
|
11
25
|
> `$XDG_DATA_HOME`, `$XDG_STATE_HOME`, and `$XDG_CACHE_HOME` respectively. If
|
|
@@ -65,6 +79,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
65
79
|
|
|
66
80
|
### Changed
|
|
67
81
|
|
|
82
|
+
- **Rebrand**: the full name "Agent Kit Manager" is now **Agent Knowledge Management** — `akm` stands for Agent Knowledge Management going forward. The binary name, npm package (`akm-cli`), and all APIs remain unchanged.
|
|
83
|
+
|
|
68
84
|
- **Config layer rewrite** — single-source-of-truth Zod schema in
|
|
69
85
|
`src/core/config-schema.ts` replaces the per-field parse switch AND
|
|
70
86
|
the per-shape load-time parser. Adding a new config field is now one
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# akm -- Agent
|
|
1
|
+
# akm -- Agent Knowledge Management
|
|
2
2
|
|
|
3
|
-
> **akm** (Agent
|
|
3
|
+
> **akm** (Agent Knowledge Management) -- A package manager for AI agent skills, commands, tools, and knowledge.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/akm-cli)
|
|
6
6
|
[](https://www.npmjs.com/package/akm-cli)
|
package/dist/cli.js
CHANGED
|
@@ -3175,7 +3175,7 @@ const main = defineCommand({
|
|
|
3175
3175
|
meta: {
|
|
3176
3176
|
name: "akm",
|
|
3177
3177
|
version: pkgVersion,
|
|
3178
|
-
description: "Agent
|
|
3178
|
+
description: "Agent Knowledge Management — search, show, and manage assets from your stash.",
|
|
3179
3179
|
},
|
|
3180
3180
|
args: {
|
|
3181
3181
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)", default: "json" },
|
|
@@ -452,7 +452,11 @@ export function mergePlans(chunks) {
|
|
|
452
452
|
mergeOps.set(op.primary, op);
|
|
453
453
|
}
|
|
454
454
|
else if (op.op === "delete") {
|
|
455
|
-
|
|
455
|
+
// merge and promote both win over delete. A promote is non-destructive
|
|
456
|
+
// (creates a proposal) but the source memory is counted in `promoted`;
|
|
457
|
+
// if a delete also fires, the ref lands in both `promoted` and
|
|
458
|
+
// `skipReasons`, breaking the invariant by +1.
|
|
459
|
+
if (!mergeOps.has(op.ref) && !promoteOps.has(op.ref)) {
|
|
456
460
|
deleteOps.set(op.ref, op);
|
|
457
461
|
}
|
|
458
462
|
}
|
|
@@ -475,6 +479,44 @@ export function mergePlans(chunks) {
|
|
|
475
479
|
}
|
|
476
480
|
}
|
|
477
481
|
}
|
|
482
|
+
// Second pass: enforce merge-wins-over-delete and deduplicate secondaries.
|
|
483
|
+
//
|
|
484
|
+
// 1. Delete/secondary ordering bug: the per-chunk loop removes delete ops
|
|
485
|
+
// for secondaries that were already in deleteOps, but misses the case
|
|
486
|
+
// where the delete chunk came first. A full sweep here fixes both orders.
|
|
487
|
+
//
|
|
488
|
+
// 2. Cross-merge secondary dedup: if ref A is a secondary in two merge ops,
|
|
489
|
+
// only the first (insertion-order) retains it. Without this, a successful
|
|
490
|
+
// merge credits A to mergedSecondaries and a later merge's emitMerge-
|
|
491
|
+
// FailureSkips also charges A to skipReasons — double-counting A while
|
|
492
|
+
// processed has it only once.
|
|
493
|
+
//
|
|
494
|
+
// 3. Primary-as-secondary dedup: if ref A is a primary in one merge op and
|
|
495
|
+
// a secondary in another, remove A from the secondary list. Both merges
|
|
496
|
+
// would otherwise claim A (merged++ for A, then mergedSecondaries++ for A)
|
|
497
|
+
// breaking the invariant the same way.
|
|
498
|
+
// Also remove delete ops for any ref claimed by a promote op (handles the
|
|
499
|
+
// case where the delete chunk appeared before the promote chunk).
|
|
500
|
+
for (const ref of promoteOps.keys()) {
|
|
501
|
+
deleteOps.delete(ref);
|
|
502
|
+
}
|
|
503
|
+
const claimedSecondaries = new Set();
|
|
504
|
+
for (const mergeOp of mergeOps.values()) {
|
|
505
|
+
deleteOps.delete(mergeOp.primary);
|
|
506
|
+
mergeOp.secondaries = mergeOp.secondaries.filter((sec) => {
|
|
507
|
+
if (mergeOps.has(sec)) {
|
|
508
|
+
warnings.push(`Merge: secondary ${sec} is also a merge primary — removing from secondary list to avoid double-count.`);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
if (claimedSecondaries.has(sec)) {
|
|
512
|
+
warnings.push(`Merge: secondary ${sec} appears in multiple merge ops — retaining in first op only.`);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
claimedSecondaries.add(sec);
|
|
516
|
+
deleteOps.delete(sec);
|
|
517
|
+
return true;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
478
520
|
// C-2 / #381: promote ops are ordered BEFORE merge ops so that the
|
|
479
521
|
// human-gated proposal queue entry is created before any destructive merge.
|
|
480
522
|
// Phase B processes ops in array order, so promote executes first.
|
|
@@ -773,6 +815,11 @@ export async function akmConsolidate(opts = {}) {
|
|
|
773
815
|
// health rollup can aggregate without regex-parsing English warning
|
|
774
816
|
// strings. See `/tmp/akm-health-investigations/tuning-reasons-investigation.md` §Q2.
|
|
775
817
|
const skipReasons = [];
|
|
818
|
+
// Tracks refs already emitted to skipReasons. A ref can only occupy one
|
|
819
|
+
// accounting bucket; subsequent skip ops for the same ref are recorded as
|
|
820
|
+
// warnings but must not push a second skipReasons entry (that would inflate
|
|
821
|
+
// Σ(skipReasons) and break the invariant by +1 per duplicate).
|
|
822
|
+
const skipReasonEmittedRefs = new Set();
|
|
776
823
|
const pushSkipReason = (op, ref, reason) => {
|
|
777
824
|
// 2026-05-27 cross-chunk double-count fix: if `ref` already contributed
|
|
778
825
|
// to judgedNoAction in its own chunk (a different chunk proposed an op
|
|
@@ -782,6 +829,13 @@ export async function akmConsolidate(opts = {}) {
|
|
|
782
829
|
// Σ(skipReasons) + failedChunkMemories.
|
|
783
830
|
if (judgedNoActionRefs.delete(ref))
|
|
784
831
|
judgedNoAction--;
|
|
832
|
+
if (skipReasonEmittedRefs.has(ref)) {
|
|
833
|
+
// Already counted once. Record the extra skip for observability but
|
|
834
|
+
// don't push to skipReasons — that would break the accounting invariant.
|
|
835
|
+
warnings.push(`Skip: ${ref} already in skipReasons (${reason} via ${op}); not re-counted.`);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
skipReasonEmittedRefs.add(ref);
|
|
785
839
|
skipReasons.push({ op, ref, reason });
|
|
786
840
|
};
|
|
787
841
|
// judgedNoAction tracks memories the LLM saw inside a chunk but proposed
|
|
@@ -1173,12 +1227,10 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1173
1227
|
const entry = memoryByRef.get(op.ref);
|
|
1174
1228
|
if (!entry) {
|
|
1175
1229
|
warnings.push(`Delete: ${op.ref} not found in loaded memories — skipping.`);
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1180
|
-
// Emit unconditionally for visibility.
|
|
1181
|
-
pushSkipReason("delete", op.ref, "delete_ref_missing");
|
|
1230
|
+
// Phantom ref: not in the batch so not in processed. Pushing to
|
|
1231
|
+
// skipReasons would inflate Σ(skipReasons) without a matching processed
|
|
1232
|
+
// entry, breaking the accounting invariant. Visibility is preserved via
|
|
1233
|
+
// the warnings array above.
|
|
1182
1234
|
continue;
|
|
1183
1235
|
}
|
|
1184
1236
|
// captureMode:hot guard — refuse to delete user-captured memories OR
|
|
@@ -1205,15 +1257,26 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1205
1257
|
deleted++;
|
|
1206
1258
|
}
|
|
1207
1259
|
catch (e) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1260
|
+
// Distinguish "file already absent" from genuine failures. A prior run
|
|
1261
|
+
// may have deleted the file but the DB was not yet re-indexed, so the
|
|
1262
|
+
// ref still appeared in memoryByRef. The delete goal is already met.
|
|
1263
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1264
|
+
if (msg.includes("not found in source")) {
|
|
1265
|
+
warnings.push(`Delete: ${op.ref} — file already absent (stale DB entry); skipping.`);
|
|
1266
|
+
pushSkipReason("delete", op.ref, "delete_already_gone");
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
warnings.push(`Delete: failed for ${op.ref}: ${String(e)}`);
|
|
1270
|
+
pushSkipReason("delete", op.ref, "delete_failed");
|
|
1271
|
+
}
|
|
1210
1272
|
}
|
|
1211
1273
|
}
|
|
1212
1274
|
else if (op.op === "promote") {
|
|
1213
1275
|
const entry = memoryByRef.get(op.ref);
|
|
1214
1276
|
if (!entry) {
|
|
1215
1277
|
warnings.push(`Promote: ${op.ref} not found in loaded memories — skipping.`);
|
|
1216
|
-
|
|
1278
|
+
// Phantom ref: not in processed, so no skipReason (same rationale as
|
|
1279
|
+
// delete_ref_missing above).
|
|
1217
1280
|
continue;
|
|
1218
1281
|
}
|
|
1219
1282
|
// Within-run source-ref dedup: skip if this source memory was already
|
|
@@ -1396,11 +1459,14 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1396
1459
|
const contradictorEntry = memoryByRef.get(op.contradictedByRef);
|
|
1397
1460
|
if (!entry) {
|
|
1398
1461
|
warnings.push(`Contradict: ${op.ref} not found in loaded memories — skipping.`);
|
|
1399
|
-
|
|
1462
|
+
// Phantom ref: not in processed, so no skipReason (same rationale as
|
|
1463
|
+
// delete_ref_missing).
|
|
1400
1464
|
continue;
|
|
1401
1465
|
}
|
|
1402
1466
|
if (!contradictorEntry) {
|
|
1403
1467
|
warnings.push(`Contradict: ${op.contradictedByRef} not found — skipping.`);
|
|
1468
|
+
// op.ref IS in the batch (entry found above) so the skipReason is
|
|
1469
|
+
// correctly charged against a real processed memory.
|
|
1404
1470
|
pushSkipReason("contradict", op.ref, "contradict_target_missing");
|
|
1405
1471
|
continue;
|
|
1406
1472
|
}
|
|
@@ -1769,6 +1835,12 @@ function loadMemoriesForSource(source, stashDir, warnings) {
|
|
|
1769
1835
|
return path.resolve(e.stashDir) === path.resolve(source);
|
|
1770
1836
|
})
|
|
1771
1837
|
.filter((e) => isConsolidationEligibleMemoryName(e.entry.name))
|
|
1838
|
+
// Skip stale DB entries whose file was deleted by a prior run but not yet
|
|
1839
|
+
// re-indexed. Without this guard the deleted file's ref appears in chunks
|
|
1840
|
+
// sent to the LLM, which then proposes a second delete → delete_failed
|
|
1841
|
+
// because the file is already gone. Re-indexing runs on a cron cadence so
|
|
1842
|
+
// several successful deletes can accumulate before the DB catches up.
|
|
1843
|
+
.filter((e) => fs.existsSync(e.filePath))
|
|
1772
1844
|
.map((e) => ({
|
|
1773
1845
|
name: e.entry.name,
|
|
1774
1846
|
filePath: e.filePath,
|
package/dist/commands/health.js
CHANGED
|
@@ -704,6 +704,14 @@ function computeWallTimeStats(durationsMs, byPhase) {
|
|
|
704
704
|
byPhase: phase,
|
|
705
705
|
};
|
|
706
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* NOTE: this reads from task_history, which can produce a count that differs
|
|
709
|
+
* by ±1 from improve_runs (the source for wallTime.byPhase). The discrepancy
|
|
710
|
+
* occurs when a task_history row has no matching improve_runs record (task
|
|
711
|
+
* crashed before recordImproveRun wrote) or vice versa (manual run). The
|
|
712
|
+
* count mismatch is cosmetic — it does not affect median/p95 materially.
|
|
713
|
+
* A full fix requires joining against improve_runs; tracked as a follow-up.
|
|
714
|
+
*/
|
|
707
715
|
function collectImproveWallTimes(db, since, until) {
|
|
708
716
|
const sql = until
|
|
709
717
|
? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL"
|
|
@@ -127,6 +127,9 @@ export const ImproveProcessConfigSchema = z
|
|
|
127
127
|
allowedTypes: z.array(z.string().min(1)).optional(),
|
|
128
128
|
qualityGate: z.object({ enabled: z.boolean().optional() }).strict().optional(),
|
|
129
129
|
contradictionDetection: z.object({ enabled: z.boolean().optional() }).strict().optional(),
|
|
130
|
+
// Extract process config (only meaningful for extract process)
|
|
131
|
+
defaultSince: z.string().min(1).optional(),
|
|
132
|
+
maxTotalChars: positiveInt.optional(),
|
|
130
133
|
})
|
|
131
134
|
.strict();
|
|
132
135
|
const ImproveProfileProcessesSchema = z
|
package/dist/core/state-db.js
CHANGED
|
@@ -1033,7 +1033,7 @@ export function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
|
|
|
1033
1033
|
* created with CREATE TABLE IF NOT EXISTS so it is safe to call inside
|
|
1034
1034
|
* ensureSchema() or as a standalone migration.
|
|
1035
1035
|
*
|
|
1036
|
-
* Purpose: caches the result of resolving and fetching remote registry
|
|
1036
|
+
* Purpose: caches the result of resolving and fetching remote registry stash
|
|
1037
1037
|
* indexes so `akm search` does not hit the network on every invocation.
|
|
1038
1038
|
*
|
|
1039
1039
|
* Indexed (query) columns:
|
package/dist/registry/factory.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Maps registry provider type identifiers (e.g. "static-index", "skills-sh")
|
|
8
8
|
* to factory functions that create RegistryProvider instances.
|
|
9
9
|
*
|
|
10
|
-
* "Registry" here refers to the
|
|
10
|
+
* "Registry" here refers to the stash discovery registries (static index files,
|
|
11
11
|
* skills.sh API) — not to be confused with the source provider factory map in
|
|
12
12
|
* `sources/provider-factory.ts` or the installed-source operations in
|
|
13
13
|
* `installed-stashes.ts`.
|
|
@@ -64,7 +64,7 @@ class SkillsShProvider {
|
|
|
64
64
|
/**
|
|
65
65
|
* skills.sh has no `getKit` API — every entry corresponds to a GitHub
|
|
66
66
|
* repository whose metadata we already include in the search result. We
|
|
67
|
-
* synthesize a manifest from the search hit when the caller knows the
|
|
67
|
+
* synthesize a manifest from the search hit when the caller knows the stash
|
|
68
68
|
* id; if not present in the most recent results, return null.
|
|
69
69
|
*/
|
|
70
70
|
async getKit(id) {
|
|
@@ -14436,7 +14436,9 @@ var init_config_schema = __esm(() => {
|
|
|
14436
14436
|
timeoutMs: exports_external.union([positiveInt, exports_external.null()]).optional(),
|
|
14437
14437
|
allowedTypes: exports_external.array(exports_external.string().min(1)).optional(),
|
|
14438
14438
|
qualityGate: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
|
|
14439
|
-
contradictionDetection: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional()
|
|
14439
|
+
contradictionDetection: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
|
|
14440
|
+
defaultSince: exports_external.string().min(1).optional(),
|
|
14441
|
+
maxTotalChars: positiveInt.optional()
|
|
14440
14442
|
}).strict();
|
|
14441
14443
|
ImproveProfileProcessesSchema = exports_external.object({
|
|
14442
14444
|
reflect: ImproveProcessConfigSchema.optional(),
|
|
@@ -18,6 +18,13 @@ import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, det
|
|
|
18
18
|
const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
19
19
|
/** Maximum stale age allowed when refresh fails (7 days). */
|
|
20
20
|
const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
21
|
+
function runGit(args, options) {
|
|
22
|
+
return spawnSync("git", args, {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
...options,
|
|
25
|
+
env: { ...process.env, ...options?.env, GIT_TERMINAL_PROMPT: "0" },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
21
28
|
/**
|
|
22
29
|
* Git source provider — clones (and re-pulls) a remote repo into a local
|
|
23
30
|
* cache directory. Implements the v1 {@link SourceProvider} interface (spec
|
|
@@ -221,7 +228,7 @@ async function doSyncGit(parsed, options) {
|
|
|
221
228
|
cloneArgs.push("--branch", parsed.requestedRef);
|
|
222
229
|
}
|
|
223
230
|
cloneArgs.push(parsed.url, cloneDir);
|
|
224
|
-
const cloneResult =
|
|
231
|
+
const cloneResult = runGit(cloneArgs, { timeout: 120_000 });
|
|
225
232
|
if (cloneResult.status !== 0) {
|
|
226
233
|
throw new Error(classifyCloneFailure(parsed.url, cloneResult.stderr, cloneResult.error));
|
|
227
234
|
}
|
|
@@ -268,7 +275,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
|
268
275
|
if (ref)
|
|
269
276
|
args.push("--branch", ref);
|
|
270
277
|
args.push(cloneUrl, tmpDir);
|
|
271
|
-
const result =
|
|
278
|
+
const result = runGit(args, { timeout: 120_000 });
|
|
272
279
|
if (result.status !== 0) {
|
|
273
280
|
// Clean up the (possibly partial) temp dir but leave destDir untouched.
|
|
274
281
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -293,8 +300,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
function pullRepo(repoDir) {
|
|
296
|
-
const result =
|
|
297
|
-
encoding: "utf8",
|
|
303
|
+
const result = runGit(["-C", repoDir, "pull", "--ff-only"], {
|
|
298
304
|
timeout: 120_000,
|
|
299
305
|
});
|
|
300
306
|
if (result.status !== 0) {
|
|
@@ -431,7 +437,7 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
431
437
|
return { committed: false, pushed: false, skipped: true, reason: "not a git repository", output: "" };
|
|
432
438
|
}
|
|
433
439
|
// Nothing to commit?
|
|
434
|
-
const statusResult =
|
|
440
|
+
const statusResult = runGit(["-C", repoDir, "status", "--porcelain"]);
|
|
435
441
|
if (statusResult.error || statusResult.status !== 0) {
|
|
436
442
|
throw new Error(`git status failed: ${statusResult.error?.message || statusResult.stderr?.trim() || "unknown error"}`);
|
|
437
443
|
}
|
|
@@ -456,16 +462,26 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
456
462
|
// Stage and commit — supply fallback identity so fresh environments without
|
|
457
463
|
// user.name/user.email configured can always commit to the default stash.
|
|
458
464
|
// `add -A` is safe here because nonAkmDirty was just verified empty.
|
|
459
|
-
const addResult =
|
|
465
|
+
const addResult = runGit(["-C", repoDir, "add", "-A"]);
|
|
460
466
|
if (addResult.status !== 0) {
|
|
461
467
|
throw new Error(`git add failed: ${addResult.stderr?.trim() || "unknown error"}`);
|
|
462
468
|
}
|
|
463
|
-
const commitResult =
|
|
469
|
+
const commitResult = runGit([
|
|
470
|
+
"-C",
|
|
471
|
+
repoDir,
|
|
472
|
+
"-c",
|
|
473
|
+
"user.name=akm",
|
|
474
|
+
"-c",
|
|
475
|
+
"user.email=akm@local",
|
|
476
|
+
"commit",
|
|
477
|
+
"-m",
|
|
478
|
+
commitMessage,
|
|
479
|
+
]);
|
|
464
480
|
if (commitResult.status !== 0) {
|
|
465
481
|
throw new Error(`git commit failed: ${commitResult.stderr?.trim() || "unknown error"}`);
|
|
466
482
|
}
|
|
467
483
|
// Push only when there is a remote AND the stash is marked writable
|
|
468
|
-
const remoteResult =
|
|
484
|
+
const remoteResult = runGit(["-C", repoDir, "remote"]);
|
|
469
485
|
if (remoteResult.status !== 0) {
|
|
470
486
|
throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
|
|
471
487
|
}
|
|
@@ -473,7 +489,7 @@ export function saveGitStash(name, message, writableOverride) {
|
|
|
473
489
|
if (!hasRemote || !writable) {
|
|
474
490
|
return { committed: true, pushed: false, skipped: false, output: commitResult.stdout.trim() };
|
|
475
491
|
}
|
|
476
|
-
const pushResult =
|
|
492
|
+
const pushResult = runGit(["-C", repoDir, "push"], { timeout: 120_000 });
|
|
477
493
|
if (pushResult.status !== 0) {
|
|
478
494
|
throw new Error(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`);
|
|
479
495
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.8.0-rc.
|
|
3
|
+
"version": "0.8.0-rc.8",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "akm (Agent
|
|
5
|
+
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"akm",
|
|
8
|
+
"agent-knowledge-management",
|
|
8
9
|
"agent-kit-manager",
|
|
9
10
|
"akm-cli",
|
|
10
11
|
"ai-agent",
|
|
@@ -53,9 +54,9 @@
|
|
|
53
54
|
"build": "rm -rf dist && bun run tsc --project ./tsconfig.build.json && bun scripts/copy-assets.ts",
|
|
54
55
|
"check": "bun run lint && bunx tsc --noEmit && bun run test:unit && bun run test:integration",
|
|
55
56
|
"check:changed": "bun test tests/output-baseline.test.ts tests/integration/e2e.test.ts tests/stash-search.test.ts && bun run lint && bunx tsc --noEmit",
|
|
56
|
-
"test": "bun test --parallel=12 --timeout=
|
|
57
|
-
"test:unit": "bun test --parallel=12 --timeout=
|
|
58
|
-
"test:integration": "bun test --parallel=12 --timeout=
|
|
57
|
+
"test": "bun test --parallel=12 --timeout=30000 ./tests",
|
|
58
|
+
"test:unit": "bun test --parallel=12 --timeout=30000 ./tests --path-ignore-patterns=tests/integration",
|
|
59
|
+
"test:integration": "bun test --parallel=12 --timeout=30000 ./tests/integration ./tests/commands ./tests/workflows",
|
|
59
60
|
"test:sharded": "bun test ./tests --shard=1/4 & bun test ./tests --shard=2/4 & bun test ./tests --shard=3/4 & bun test ./tests --shard=4/4 & wait",
|
|
60
61
|
"test:time": "bun scripts/test-timing-report.ts",
|
|
61
62
|
"lint:isolation": "bun scripts/lint-tests-isolation.ts",
|