akm-cli 0.7.4 → 0.8.0-rc.3
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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +86 -0
- package/dist/cli.js +1223 -650
- package/dist/commands/agent-dispatch.js +107 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +812 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +224 -39
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1161 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +291 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +145 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/vault-key-rules.js +67 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +106 -43
- package/dist/commands/reflect.js +167 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +135 -55
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +173 -87
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +100 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +240 -127
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +0 -6
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +731 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +418 -59
- package/dist/indexer/ensure-index.js +133 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +480 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/matchers.js +124 -160
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +196 -197
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/builders.js +109 -0
- package/dist/integrations/agent/config.js +203 -3
- package/dist/integrations/agent/index.js +5 -2
- package/dist/integrations/agent/model-aliases.js +63 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +158 -34
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -309
- package/dist/output/renderers.js +226 -257
- package/dist/output/shapes.js +109 -96
- package/dist/output/text.js +274 -36
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/registry/resolve.js +8 -16
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/filesystem.js +16 -23
- package/dist/sources/providers/git.js +45 -4
- package/dist/sources/providers/website.js +15 -22
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +5 -2
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
package/dist/output/text.js
CHANGED
|
@@ -38,6 +38,13 @@ export function formatPlain(command, result, detail) {
|
|
|
38
38
|
case "index": {
|
|
39
39
|
const indexResult = result;
|
|
40
40
|
let out = `Indexed ${indexResult.totalEntries ?? 0} entries from ${indexResult.directoriesScanned ?? 0} directories (mode: ${indexResult.mode ?? "unknown"})`;
|
|
41
|
+
const graphQuality = indexResult.graphQuality;
|
|
42
|
+
if (graphQuality) {
|
|
43
|
+
const coverage = typeof graphQuality.extractionCoverage === "number"
|
|
44
|
+
? `${Math.round(graphQuality.extractionCoverage * 100)}%`
|
|
45
|
+
: "n/a";
|
|
46
|
+
out += `\nGraph quality: entities ${graphQuality.entityCount ?? 0}, relations ${graphQuality.relationCount ?? 0}, coverage ${coverage}, density ${graphQuality.density ?? 0}`;
|
|
47
|
+
}
|
|
41
48
|
const warnings = indexResult.warnings;
|
|
42
49
|
if (Array.isArray(warnings) && warnings.length > 0) {
|
|
43
50
|
out += `\nWarnings (${warnings.length}):`;
|
|
@@ -221,6 +228,31 @@ export function formatPlain(command, result, detail) {
|
|
|
221
228
|
case "distill": {
|
|
222
229
|
return formatDistillPlain(r);
|
|
223
230
|
}
|
|
231
|
+
case "graph-summary":
|
|
232
|
+
return formatGraphSummaryPlain(r);
|
|
233
|
+
case "graph-entities":
|
|
234
|
+
return formatGraphEntitiesPlain(r);
|
|
235
|
+
case "graph-relations":
|
|
236
|
+
return formatGraphRelationsPlain(r);
|
|
237
|
+
case "graph-related":
|
|
238
|
+
return formatGraphRelatedPlain(r);
|
|
239
|
+
case "graph-export":
|
|
240
|
+
return formatGraphExportPlain(r);
|
|
241
|
+
case "improve": {
|
|
242
|
+
return formatImprovePlain(r);
|
|
243
|
+
}
|
|
244
|
+
case "consolidate": {
|
|
245
|
+
return formatConsolidatePlain(r);
|
|
246
|
+
}
|
|
247
|
+
// Output shape registration for `akm agent <profile>` (#agent-dispatch).
|
|
248
|
+
// In interactive mode stdout/stderr are empty (they went to the TTY), so
|
|
249
|
+
// we print only the profile name and exit status. In captured mode we
|
|
250
|
+
// emit stdout first, then stderr, then the exit code summary.
|
|
251
|
+
case "agent-result": {
|
|
252
|
+
return formatAgentResultPlain(r);
|
|
253
|
+
}
|
|
254
|
+
case "health":
|
|
255
|
+
return formatHealthPlain(r);
|
|
224
256
|
case "info":
|
|
225
257
|
return formatInfoPlain(r);
|
|
226
258
|
case "config":
|
|
@@ -249,7 +281,7 @@ export function formatPlain(command, result, detail) {
|
|
|
249
281
|
case "vault-list":
|
|
250
282
|
return formatVaultListPlain(r);
|
|
251
283
|
case "vault-create":
|
|
252
|
-
return `Created vault ${String(r.ref ?? "?")}
|
|
284
|
+
return `Created vault ${String(r.ref ?? "?")}`;
|
|
253
285
|
case "vault-set":
|
|
254
286
|
return `Set ${String(r.key ?? "?")} in ${String(r.ref ?? "?")} (value not displayed)`;
|
|
255
287
|
case "vault-unset": {
|
|
@@ -299,6 +331,50 @@ export function formatInfoPlain(r) {
|
|
|
299
331
|
return JSON.stringify(r, null, 2);
|
|
300
332
|
return lines.join("\n");
|
|
301
333
|
}
|
|
334
|
+
export function formatHealthPlain(r) {
|
|
335
|
+
const lines = [];
|
|
336
|
+
lines.push(`health: ${String(r.status ?? "unknown")}`);
|
|
337
|
+
if (typeof r.since === "string")
|
|
338
|
+
lines.push(`since: ${r.since}`);
|
|
339
|
+
const metrics = typeof r.metrics === "object" && r.metrics !== null ? r.metrics : undefined;
|
|
340
|
+
if (metrics) {
|
|
341
|
+
lines.push("metrics:");
|
|
342
|
+
lines.push(` taskFailRate: ${String(metrics.taskFailRate ?? 0)}`);
|
|
343
|
+
lines.push(` agentFailureRate: ${String(metrics.agentFailureRate ?? 0)}`);
|
|
344
|
+
lines.push(` stuckActiveRuns: ${String(metrics.stuckActiveRuns ?? 0)}`);
|
|
345
|
+
lines.push(` logBackingRate: ${String(metrics.logBackingRate ?? 0)}`);
|
|
346
|
+
lines.push(` probeRoundTripMs: ${String(metrics.probeRoundTripMs ?? "null")}`);
|
|
347
|
+
}
|
|
348
|
+
const improve = typeof r.improve === "object" && r.improve !== null ? r.improve : undefined;
|
|
349
|
+
if (improve) {
|
|
350
|
+
const actions = typeof improve.actions === "object" && improve.actions !== null
|
|
351
|
+
? improve.actions
|
|
352
|
+
: {};
|
|
353
|
+
lines.push("improve:");
|
|
354
|
+
lines.push(` invoked: ${String(improve.invoked ?? 0)}`);
|
|
355
|
+
lines.push(` completed: ${String(improve.completed ?? 0)}`);
|
|
356
|
+
lines.push(` skipped: ${String(improve.skipped ?? 0)}`);
|
|
357
|
+
lines.push(` plannedRefs: ${String(improve.plannedRefs ?? 0)}`);
|
|
358
|
+
lines.push(` actions: reflect=${String(actions.reflect ?? 0)} distill=${String(actions.distill ?? 0)} distillSkipped=${String(actions.distillSkipped ?? 0)} memoryPrune=${String(actions.memoryPrune ?? 0)} memoryInference=${String(actions.memoryInference ?? 0)} graphExtraction=${String(actions.graphExtraction ?? 0)} error=${String(actions.error ?? 0)}`);
|
|
359
|
+
lines.push(` coverageGapCount: ${String(improve.coverageGapCount ?? 0)}`);
|
|
360
|
+
lines.push(` executionLogCandidateCount: ${String(improve.executionLogCandidateCount ?? 0)}`);
|
|
361
|
+
lines.push(` deadUrlCount: ${String(improve.deadUrlCount ?? 0)}`);
|
|
362
|
+
}
|
|
363
|
+
const sections = [
|
|
364
|
+
["hardChecks", r.hardChecks],
|
|
365
|
+
["advisories", r.advisories],
|
|
366
|
+
];
|
|
367
|
+
for (const [label, value] of sections) {
|
|
368
|
+
const checks = Array.isArray(value) ? value : [];
|
|
369
|
+
if (checks.length === 0)
|
|
370
|
+
continue;
|
|
371
|
+
lines.push(`${label}:`);
|
|
372
|
+
for (const check of checks) {
|
|
373
|
+
lines.push(` - [${String(check.status ?? "unknown")}] ${String(check.name ?? "check")}: ${String(check.message ?? "")}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return lines.join("\n");
|
|
377
|
+
}
|
|
302
378
|
export function formatConfigPlain(r) {
|
|
303
379
|
// Recursive flattener: prints `key=value` lines, and nested objects as
|
|
304
380
|
// `parent.child=value`. Arrays render as JSON for compactness.
|
|
@@ -404,22 +480,7 @@ export function formatRegistryBuildIndexPlain(r) {
|
|
|
404
480
|
return `Wrote registry index ${version} (${total} kits) → ${outPath}`.replace(/\s+/g, " ").trim();
|
|
405
481
|
}
|
|
406
482
|
export function formatVaultListPlain(r) {
|
|
407
|
-
//
|
|
408
|
-
if (typeof r.ref === "string" && Array.isArray(r.entries)) {
|
|
409
|
-
const ref = r.ref;
|
|
410
|
-
const entries = r.entries;
|
|
411
|
-
if (entries.length === 0) {
|
|
412
|
-
return `No keys in ${ref}. Set one with \`akm vault set ${ref} KEY=VALUE\`.`;
|
|
413
|
-
}
|
|
414
|
-
const lines = [ref];
|
|
415
|
-
for (const e of entries) {
|
|
416
|
-
const key = String(e.key ?? "?");
|
|
417
|
-
const comment = typeof e.comment === "string" && e.comment ? ` # ${e.comment}` : "";
|
|
418
|
-
lines.push(` ${key}${comment}`);
|
|
419
|
-
}
|
|
420
|
-
return lines.join("\n");
|
|
421
|
-
}
|
|
422
|
-
// Multi-vault listing: { vaults: [{ ref, path, keyCount }, ...] }
|
|
483
|
+
// Multi-vault listing: { vaults: [{ ref, path, keys }, ...] }
|
|
423
484
|
const vaults = Array.isArray(r.vaults) ? r.vaults : [];
|
|
424
485
|
if (vaults.length === 0) {
|
|
425
486
|
return "No vaults. Create one with `akm vault create <name>` then `akm vault set vault:<name> KEY=VALUE`.";
|
|
@@ -427,8 +488,17 @@ export function formatVaultListPlain(r) {
|
|
|
427
488
|
const lines = [];
|
|
428
489
|
for (const v of vaults) {
|
|
429
490
|
const ref = String(v.ref ?? "?");
|
|
430
|
-
const
|
|
431
|
-
lines.
|
|
491
|
+
const keys = Array.isArray(v.keys) ? v.keys.map(String) : [];
|
|
492
|
+
if (lines.length > 0)
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push(`## ${ref}`);
|
|
495
|
+
if (keys.length === 0) {
|
|
496
|
+
lines.push("- (no keys)");
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
for (const key of keys) {
|
|
500
|
+
lines.push(`- ${key}`);
|
|
501
|
+
}
|
|
432
502
|
}
|
|
433
503
|
return lines.join("\n");
|
|
434
504
|
}
|
|
@@ -446,6 +516,57 @@ export function formatWorkflowValidatePlain(r) {
|
|
|
446
516
|
const stepCount = typeof r.stepCount === "number" ? r.stepCount : 0;
|
|
447
517
|
return `workflow validate: ok — ${title || pathValue} (${stepCount} step(s))`;
|
|
448
518
|
}
|
|
519
|
+
export function formatGraphSummaryPlain(r) {
|
|
520
|
+
const lines = [
|
|
521
|
+
`Graph: ${String(r.graphPath ?? "?")}`,
|
|
522
|
+
`Generated: ${String(r.generatedAt ?? "?")}`,
|
|
523
|
+
`Files: ${String(r.fileCount ?? 0)} Entities: ${String(r.entityCount ?? 0)} Relations: ${String(r.relationCount ?? 0)}`,
|
|
524
|
+
];
|
|
525
|
+
const quality = r.quality;
|
|
526
|
+
if (quality) {
|
|
527
|
+
const coverage = typeof quality.extractionCoverage === "number" ? `${Math.round(quality.extractionCoverage * 100)}%` : "n/a";
|
|
528
|
+
lines.push(`Coverage: ${coverage} Density: ${String(quality.density ?? 0)}`);
|
|
529
|
+
}
|
|
530
|
+
return lines.join("\n");
|
|
531
|
+
}
|
|
532
|
+
export function formatGraphEntitiesPlain(r) {
|
|
533
|
+
const entities = Array.isArray(r.entities) ? r.entities : [];
|
|
534
|
+
if (entities.length === 0)
|
|
535
|
+
return "No entities found in graph.";
|
|
536
|
+
const lines = [`Entities (${String(r.total ?? entities.length)} total):`];
|
|
537
|
+
for (const entity of entities) {
|
|
538
|
+
lines.push(`- ${String(entity.name ?? "?")} (${String(entity.fileCount ?? 0)} files)`);
|
|
539
|
+
}
|
|
540
|
+
return lines.join("\n");
|
|
541
|
+
}
|
|
542
|
+
export function formatGraphRelationsPlain(r) {
|
|
543
|
+
const relations = Array.isArray(r.relations) ? r.relations : [];
|
|
544
|
+
if (relations.length === 0)
|
|
545
|
+
return "No relations found in graph.";
|
|
546
|
+
const lines = [`Relations (${String(r.total ?? relations.length)} total):`];
|
|
547
|
+
for (const relation of relations) {
|
|
548
|
+
const type = relation.type ? ` [${String(relation.type)}]` : "";
|
|
549
|
+
lines.push(`- ${String(relation.from ?? "?")} -> ${String(relation.to ?? "?")}${type} x${String(relation.count ?? 0)}`);
|
|
550
|
+
}
|
|
551
|
+
return lines.join("\n");
|
|
552
|
+
}
|
|
553
|
+
export function formatGraphRelatedPlain(r) {
|
|
554
|
+
const related = Array.isArray(r.related) ? r.related : [];
|
|
555
|
+
if (related.length === 0)
|
|
556
|
+
return String(r.tip ?? "No related graph neighbors were found.");
|
|
557
|
+
const lines = [`Related (${String(r.total ?? related.length)} total) for ${String(r.ref ?? "?")}:`];
|
|
558
|
+
for (const hit of related) {
|
|
559
|
+
const shared = Array.isArray(hit.sharedEntities) ? hit.sharedEntities.map(String).join(", ") : "";
|
|
560
|
+
lines.push(`- ${String(hit.type ?? "?")}: ${formatRelatedLabel(hit)}`);
|
|
561
|
+
if (shared)
|
|
562
|
+
lines.push(` shared: ${shared}`);
|
|
563
|
+
lines.push(` relationCount: ${String(hit.relationCount ?? 0)}`);
|
|
564
|
+
}
|
|
565
|
+
return lines.join("\n");
|
|
566
|
+
}
|
|
567
|
+
export function formatGraphExportPlain(r) {
|
|
568
|
+
return `Exported graph (${String(r.format ?? "json")}, ${String(r.bytes ?? 0)} bytes) to ${String(r.outPath ?? "?")}`;
|
|
569
|
+
}
|
|
449
570
|
export function formatProposalProducerPlain(command, r) {
|
|
450
571
|
if (r.ok === false) {
|
|
451
572
|
const reason = String(r.reason ?? "unknown");
|
|
@@ -470,7 +591,7 @@ export function formatProposalListPlain(r) {
|
|
|
470
591
|
const proposals = Array.isArray(r.proposals) ? r.proposals : [];
|
|
471
592
|
const total = typeof r.totalCount === "number" ? r.totalCount : proposals.length;
|
|
472
593
|
if (proposals.length === 0) {
|
|
473
|
-
return `${total} proposal(s).\nNo proposals.\nGenerate one with \`akm
|
|
594
|
+
return `${total} proposal(s).\nNo proposals.\nGenerate one with \`akm improve\`, \`akm propose <type> <name> --task ...\`, or \`akm improve <ref>\`.`;
|
|
474
595
|
}
|
|
475
596
|
const lines = [`${total} proposal(s)`, ""];
|
|
476
597
|
for (const p of proposals) {
|
|
@@ -535,7 +656,7 @@ export function formatDistillPlain(r) {
|
|
|
535
656
|
const lessonRef = String(r.lessonRef ?? "?");
|
|
536
657
|
if (outcome === "queued") {
|
|
537
658
|
const id = String(r.proposalId ?? "?");
|
|
538
|
-
return `Distilled ${inputRef} → proposal ${id} (${lessonRef}). Run \`akm proposal
|
|
659
|
+
return `Distilled ${inputRef} → proposal ${id} (${lessonRef}). Run \`akm show proposal ${id}\` to review.`;
|
|
539
660
|
}
|
|
540
661
|
if (outcome === "validation_failed") {
|
|
541
662
|
const findings = Array.isArray(r.findings) ? r.findings : [];
|
|
@@ -549,6 +670,50 @@ export function formatDistillPlain(r) {
|
|
|
549
670
|
const message = typeof r.message === "string" ? r.message : "feature disabled or LLM unavailable";
|
|
550
671
|
return `Distill skipped for ${inputRef}: ${message}`;
|
|
551
672
|
}
|
|
673
|
+
export function formatConsolidatePlain(r) {
|
|
674
|
+
const processed = typeof r.processed === "number" ? r.processed : 0;
|
|
675
|
+
const merged = typeof r.merged === "number" ? r.merged : 0;
|
|
676
|
+
const deleted = typeof r.deleted === "number" ? r.deleted : 0;
|
|
677
|
+
const promoted = Array.isArray(r.promoted) ? r.promoted.length : 0;
|
|
678
|
+
const warnings = Array.isArray(r.warnings) ? r.warnings : [];
|
|
679
|
+
const lines = [];
|
|
680
|
+
if (r.dryRun === true) {
|
|
681
|
+
lines.push(`[consolidate] dry-run: ${processed} memories found, no AI call`);
|
|
682
|
+
}
|
|
683
|
+
else if (r.previewOnly === true) {
|
|
684
|
+
lines.push(`[consolidate] preview: processed=${processed}`);
|
|
685
|
+
const planned = Array.isArray(r.planned) ? r.planned : [];
|
|
686
|
+
for (const op of planned) {
|
|
687
|
+
if (op.op === "merge") {
|
|
688
|
+
lines.push(` merge: ${String(op.primary)} ← ${String(Array.isArray(op.secondaries) ? op.secondaries.join(", ") : "")}`);
|
|
689
|
+
}
|
|
690
|
+
else if (op.op === "delete") {
|
|
691
|
+
lines.push(` delete: ${String(op.ref)} (${String(op.reason ?? "")})`);
|
|
692
|
+
}
|
|
693
|
+
else if (op.op === "promote") {
|
|
694
|
+
lines.push(` promote: ${String(op.ref)} → ${String(op.knowledgeRef)}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
lines.push(`[consolidate] processed=${processed} merged=${merged} deleted=${deleted} promoted=${promoted}`);
|
|
700
|
+
}
|
|
701
|
+
for (const w of warnings) {
|
|
702
|
+
lines.push(` warning: ${w}`);
|
|
703
|
+
}
|
|
704
|
+
return lines.join("\n");
|
|
705
|
+
}
|
|
706
|
+
export function formatImprovePlain(r) {
|
|
707
|
+
const scope = r.scope ?? {};
|
|
708
|
+
const mode = String(scope.mode ?? "all");
|
|
709
|
+
const value = typeof scope.value === "string" ? ` ${scope.value}` : "";
|
|
710
|
+
const plannedRefs = Array.isArray(r.plannedRefs) ? r.plannedRefs.length : 0;
|
|
711
|
+
if (r.dryRun === true) {
|
|
712
|
+
return `Improve dry-run:${mode === "all" ? " all assets" : value} (${plannedRefs} planned ref(s))`;
|
|
713
|
+
}
|
|
714
|
+
const actions = Array.isArray(r.actions) ? r.actions.length : 0;
|
|
715
|
+
return `Improve:${mode === "all" ? " all assets" : value} queued ${actions} action(s) across ${plannedRefs} ref(s)`;
|
|
716
|
+
}
|
|
552
717
|
export function formatProposalDiffPlain(r) {
|
|
553
718
|
const header = r.isNew
|
|
554
719
|
? `# proposal ${String(r.id ?? "?")} (new asset: ${String(r.ref ?? "?")})`
|
|
@@ -558,18 +723,25 @@ export function formatProposalDiffPlain(r) {
|
|
|
558
723
|
return `${header}\n(no changes)`;
|
|
559
724
|
return `${header}\n${unified}`;
|
|
560
725
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Build the summary header line shared by formatEventsPlain and formatHistoryPlain.
|
|
728
|
+
* Accumulates ref/type/since label parts then appends the count label.
|
|
729
|
+
*/
|
|
730
|
+
function buildEventHeader(r, countLabel, totalCount) {
|
|
731
|
+
const parts = [];
|
|
564
732
|
if (typeof r.ref === "string" && r.ref)
|
|
565
|
-
|
|
733
|
+
parts.push(`ref: ${r.ref}`);
|
|
566
734
|
if (typeof r.type === "string" && r.type)
|
|
567
|
-
|
|
735
|
+
parts.push(`type: ${r.type}`);
|
|
568
736
|
if (typeof r.since === "string" && r.since)
|
|
569
|
-
|
|
737
|
+
parts.push(`since: ${r.since}`);
|
|
738
|
+
parts.push(`${totalCount} ${countLabel}`);
|
|
739
|
+
return parts.join(" ");
|
|
740
|
+
}
|
|
741
|
+
export function formatEventsPlain(r) {
|
|
742
|
+
const events = Array.isArray(r.events) ? r.events : [];
|
|
570
743
|
const totalCount = typeof r.totalCount === "number" ? r.totalCount : events.length;
|
|
571
|
-
|
|
572
|
-
const header = headerParts.join(" ");
|
|
744
|
+
const header = buildEventHeader(r, "event(s)", totalCount);
|
|
573
745
|
if (events.length === 0) {
|
|
574
746
|
return `${header}\nNo events.`;
|
|
575
747
|
}
|
|
@@ -592,13 +764,8 @@ export function formatEventLine(event) {
|
|
|
592
764
|
}
|
|
593
765
|
export function formatHistoryPlain(r) {
|
|
594
766
|
const entries = Array.isArray(r.entries) ? r.entries : [];
|
|
595
|
-
const headerParts = [];
|
|
596
|
-
if (typeof r.ref === "string" && r.ref)
|
|
597
|
-
headerParts.push(`ref: ${r.ref}`);
|
|
598
|
-
if (typeof r.since === "string" && r.since)
|
|
599
|
-
headerParts.push(`since: ${r.since}`);
|
|
600
767
|
const totalCount = typeof r.totalCount === "number" ? r.totalCount : entries.length;
|
|
601
|
-
headerParts
|
|
768
|
+
const headerParts = [buildEventHeader(r, "event(s)", totalCount)];
|
|
602
769
|
// Show active event sources so operators know which streams were consulted.
|
|
603
770
|
if (Array.isArray(r.sources) && r.sources.length > 0) {
|
|
604
771
|
headerParts.push(`sources: ${r.sources.join(", ")}`);
|
|
@@ -676,6 +843,19 @@ function formatShowPlain(r, detail) {
|
|
|
676
843
|
if (r.schemaVersion !== undefined)
|
|
677
844
|
lines.push(`schemaVersion: ${String(r.schemaVersion)}`);
|
|
678
845
|
}
|
|
846
|
+
const related = typeof r.related === "object" && r.related !== null ? r.related : undefined;
|
|
847
|
+
const relatedHits = related && Array.isArray(related.hits) ? related.hits : [];
|
|
848
|
+
if (related) {
|
|
849
|
+
lines.push("");
|
|
850
|
+
lines.push(`related: ${String(related.total ?? relatedHits.length)}`);
|
|
851
|
+
for (const hit of relatedHits) {
|
|
852
|
+
lines.push(` - ${String(hit.type ?? "?")}: ${formatRelatedLabel(hit)}`);
|
|
853
|
+
const shared = Array.isArray(hit.sharedEntities) ? hit.sharedEntities.map(String) : [];
|
|
854
|
+
if (shared.length > 0)
|
|
855
|
+
lines.push(` shared: ${shared.join(", ")}`);
|
|
856
|
+
lines.push(` relationCount: ${String(hit.relationCount ?? 0)}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
679
859
|
const payloads = [r.content, r.template, r.prompt].filter((value) => value != null).map(String);
|
|
680
860
|
if (Array.isArray(r.steps) && r.steps.length > 0) {
|
|
681
861
|
if (lines.length > 0)
|
|
@@ -792,10 +972,17 @@ function isCommandOutputSkill(lines) {
|
|
|
792
972
|
const yamlCount = codeLines.filter((l) => yamlPattern.test(l)).length;
|
|
793
973
|
return cliCount > yamlCount && cliCount > 0;
|
|
794
974
|
}
|
|
975
|
+
function formatRelatedLabel(hit) {
|
|
976
|
+
const ref = typeof hit.ref === "string" ? hit.ref : undefined;
|
|
977
|
+
if (ref)
|
|
978
|
+
return ref;
|
|
979
|
+
const pathValue = typeof hit.path === "string" ? hit.path : "?";
|
|
980
|
+
return pathValue.split("/").pop() ?? pathValue;
|
|
981
|
+
}
|
|
795
982
|
export function formatWorkflowListPlain(result) {
|
|
796
983
|
const runs = Array.isArray(result.runs) ? result.runs : [];
|
|
797
984
|
if (runs.length === 0) {
|
|
798
|
-
return "No workflow runs. Start one with `akm workflow next workflow:<name>` or author one with `akm workflow create <name>`.";
|
|
985
|
+
return "No workflow runs in the current working scope. Start one with `akm workflow next workflow:<name>` or author one with `akm workflow create <name>`.";
|
|
799
986
|
}
|
|
800
987
|
return runs
|
|
801
988
|
.map((run) => {
|
|
@@ -906,6 +1093,8 @@ export function formatSearchPlain(r, detail) {
|
|
|
906
1093
|
lines.push(` ref: ${String(hit.ref)}`);
|
|
907
1094
|
if (hit.origin !== undefined)
|
|
908
1095
|
lines.push(` origin: ${String(hit.origin)}`);
|
|
1096
|
+
if (Array.isArray(hit.keys) && hit.keys.length > 0)
|
|
1097
|
+
lines.push(` keys: ${hit.keys.join(", ")}`);
|
|
909
1098
|
if (hit.size)
|
|
910
1099
|
lines.push(` size: ${String(hit.size)}`);
|
|
911
1100
|
if (hit.action)
|
|
@@ -922,6 +1111,24 @@ export function formatSearchPlain(r, detail) {
|
|
|
922
1111
|
if (Array.isArray(hit.warnings) && hit.warnings.length > 0) {
|
|
923
1112
|
lines.push(` warnings: ${hit.warnings.join("; ")}`);
|
|
924
1113
|
}
|
|
1114
|
+
const graph = typeof hit.graph === "object" && hit.graph !== null ? hit.graph : undefined;
|
|
1115
|
+
if (graph) {
|
|
1116
|
+
const entities = Array.isArray(graph.entities) ? graph.entities : [];
|
|
1117
|
+
if (entities.length > 0) {
|
|
1118
|
+
const matched = entities
|
|
1119
|
+
.filter((entity) => String(entity.kind ?? "") === "matched")
|
|
1120
|
+
.map((entity) => String(entity.name ?? "?"));
|
|
1121
|
+
const neighbors = entities
|
|
1122
|
+
.filter((entity) => String(entity.kind ?? "") !== "matched")
|
|
1123
|
+
.map((entity) => String(entity.name ?? "?"));
|
|
1124
|
+
lines.push(` graph: ${[
|
|
1125
|
+
matched.length > 0 ? `query match=${matched.join(", ")}` : undefined,
|
|
1126
|
+
neighbors.length > 0 ? `neighbors=${neighbors.join(", ")}` : undefined,
|
|
1127
|
+
]
|
|
1128
|
+
.filter(Boolean)
|
|
1129
|
+
.join("; ")}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
925
1132
|
if (detail === "full") {
|
|
926
1133
|
if (hit.path)
|
|
927
1134
|
lines.push(` path: ${String(hit.path)}`);
|
|
@@ -1088,6 +1295,9 @@ export function formatCuratePlain(r, detail) {
|
|
|
1088
1295
|
lines.push(` ref: ${String(item.ref)}`);
|
|
1089
1296
|
if (item.id)
|
|
1090
1297
|
lines.push(` id: ${String(item.id)}`);
|
|
1298
|
+
if (Array.isArray(item.keys) && item.keys.length > 0) {
|
|
1299
|
+
lines.push(` keys: ${item.keys.join(", ")}`);
|
|
1300
|
+
}
|
|
1091
1301
|
if (Array.isArray(item.parameters) && item.parameters.length > 0) {
|
|
1092
1302
|
lines.push(` parameters: ${item.parameters.join(", ")}`);
|
|
1093
1303
|
}
|
|
@@ -1114,3 +1324,31 @@ export function formatCuratePlain(r, detail) {
|
|
|
1114
1324
|
lines.push("To search further: akm search '<query>'");
|
|
1115
1325
|
return lines.join("\n");
|
|
1116
1326
|
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Render the result of `akm agent <profile>`.
|
|
1329
|
+
*
|
|
1330
|
+
* Interactive mode: stdout and stderr are empty (output went to the TTY).
|
|
1331
|
+
* Print only the profile name and exit code so the caller knows the agent
|
|
1332
|
+
* finished.
|
|
1333
|
+
*
|
|
1334
|
+
* Captured mode: emit stdout (if non-empty), then stderr (if non-empty),
|
|
1335
|
+
* then the profile/exit-code summary line.
|
|
1336
|
+
*/
|
|
1337
|
+
export function formatAgentResultPlain(r) {
|
|
1338
|
+
const profile = String(r.profileName ?? "agent");
|
|
1339
|
+
const exitCode = r.exitCode !== undefined && r.exitCode !== null ? Number(r.exitCode) : 0;
|
|
1340
|
+
const stdout = typeof r.stdout === "string" ? r.stdout : "";
|
|
1341
|
+
const stderr = typeof r.stderr === "string" ? r.stderr : "";
|
|
1342
|
+
// Interactive mode: both streams are empty.
|
|
1343
|
+
if (!stdout && !stderr) {
|
|
1344
|
+
return `[${profile}] agent exited with code ${exitCode}`;
|
|
1345
|
+
}
|
|
1346
|
+
// Captured mode: stream content + summary.
|
|
1347
|
+
const parts = [];
|
|
1348
|
+
if (stdout)
|
|
1349
|
+
parts.push(stdout.trimEnd());
|
|
1350
|
+
if (stderr)
|
|
1351
|
+
parts.push(stderr.trimEnd());
|
|
1352
|
+
parts.push(`[${profile}] agent exited with code ${exitCode}`);
|
|
1353
|
+
return parts.join("\n");
|
|
1354
|
+
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { fetchWithRetry } from "../../core/common";
|
|
4
|
-
import {
|
|
2
|
+
import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
|
|
5
3
|
import { registerProvider } from "../factory";
|
|
6
4
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
7
5
|
/** Per-query cache TTL in milliseconds (15 minutes). */
|
|
8
6
|
const QUERY_CACHE_TTL_MS = 15 * 60 * 1000;
|
|
9
|
-
/** Maximum age before query cache is considered stale but still usable (1 day). */
|
|
10
|
-
const QUERY_CACHE_STALE_MS = 24 * 60 * 60 * 1000;
|
|
11
7
|
// ── Provider class ──────────────────────────────────────────────────────────
|
|
12
8
|
class SkillsShProvider {
|
|
13
9
|
type = "skills-sh";
|
|
@@ -89,13 +85,33 @@ class SkillsShProvider {
|
|
|
89
85
|
return ref.source === "github";
|
|
90
86
|
}
|
|
91
87
|
async fetchSkills(query, limit) {
|
|
92
|
-
//
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
// Build a stable DB cache key for this query
|
|
89
|
+
const dbCacheKey = this.queryDbCacheKey(query, limit);
|
|
90
|
+
// ── Step 1: Try DB cache (index.db) ───────────────────────────────────
|
|
91
|
+
let db;
|
|
92
|
+
let dbCacheResult;
|
|
93
|
+
try {
|
|
94
|
+
db = openDatabase();
|
|
95
|
+
dbCacheResult = getRegistryIndexCache(db, dbCacheKey, QUERY_CACHE_TTL_MS);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// index.db not available yet (pre-migration install or test env) — fall through
|
|
99
|
+
}
|
|
100
|
+
if (dbCacheResult) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(dbCacheResult.indexJson);
|
|
103
|
+
if (Array.isArray(parsed)) {
|
|
104
|
+
const entries = parsed.filter(isValidSkillsEntry);
|
|
105
|
+
if (db)
|
|
106
|
+
closeDatabase(db);
|
|
107
|
+
return entries;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* corrupt DB entry — fall through */
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
|
-
// Fetch from API
|
|
114
|
+
// ── Step 2: Fetch from API ─────────────────────────────────────────────
|
|
99
115
|
const baseUrl = this.config.url.replace(/\/+$/, "");
|
|
100
116
|
const url = `${baseUrl}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
101
117
|
try {
|
|
@@ -105,13 +121,40 @@ class SkillsShProvider {
|
|
|
105
121
|
}
|
|
106
122
|
const data = (await response.json());
|
|
107
123
|
const entries = parseSkillsResponse(data);
|
|
108
|
-
|
|
124
|
+
// Write to DB cache (primary)
|
|
125
|
+
if (db) {
|
|
126
|
+
try {
|
|
127
|
+
upsertRegistryIndexCache(db, dbCacheKey, JSON.stringify(entries));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* best-effort */
|
|
131
|
+
}
|
|
132
|
+
closeDatabase(db);
|
|
133
|
+
}
|
|
109
134
|
return entries;
|
|
110
135
|
}
|
|
111
136
|
catch (err) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
if (db) {
|
|
138
|
+
try {
|
|
139
|
+
closeDatabase(db);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* ignore */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Fetch failed — use stale DB cache if available
|
|
146
|
+
if (dbCacheResult) {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(dbCacheResult.indexJson);
|
|
149
|
+
if (Array.isArray(parsed)) {
|
|
150
|
+
const entries = parsed.filter(isValidSkillsEntry);
|
|
151
|
+
if (entries.length > 0)
|
|
152
|
+
return entries;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* ignore */
|
|
157
|
+
}
|
|
115
158
|
}
|
|
116
159
|
throw err;
|
|
117
160
|
}
|
|
@@ -167,9 +210,8 @@ class SkillsShProvider {
|
|
|
167
210
|
});
|
|
168
211
|
return hits.length > 0 ? hits : undefined;
|
|
169
212
|
}
|
|
170
|
-
// ──
|
|
171
|
-
|
|
172
|
-
const cacheDir = getRegistryIndexCacheDir();
|
|
213
|
+
// ── DB cache key ────────────────────────────────────────────────────────
|
|
214
|
+
queryDbCacheKey(query, limit) {
|
|
173
215
|
const hasher = new Bun.CryptoHasher("md5");
|
|
174
216
|
hasher.update(this.config.url);
|
|
175
217
|
hasher.update("\0");
|
|
@@ -177,33 +219,7 @@ class SkillsShProvider {
|
|
|
177
219
|
hasher.update("\0");
|
|
178
220
|
hasher.update(String(limit));
|
|
179
221
|
const hash = hasher.digest("hex");
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
readQueryCache(cachePath) {
|
|
183
|
-
try {
|
|
184
|
-
const stat = fs.statSync(cachePath);
|
|
185
|
-
const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
186
|
-
if (!Array.isArray(raw))
|
|
187
|
-
return null;
|
|
188
|
-
const entries = raw.filter(isValidSkillsEntry);
|
|
189
|
-
return { entries, mtime: stat.mtimeMs };
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
writeQueryCache(cachePath, entries) {
|
|
196
|
-
try {
|
|
197
|
-
const dir = path.dirname(cachePath);
|
|
198
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
199
|
-
const tmpPath = `${cachePath}.tmp.${process.pid}`;
|
|
200
|
-
// 0o600: owner read/write only — cache may contain search terms tied to API keys
|
|
201
|
-
fs.writeFileSync(tmpPath, JSON.stringify(entries), { encoding: "utf8", mode: 0o600 });
|
|
202
|
-
fs.renameSync(tmpPath, cachePath);
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
// Best-effort caching
|
|
206
|
-
}
|
|
222
|
+
return `skills-sh:${hash}`;
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
// ── Self-register ───────────────────────────────────────────────────────────
|
|
@@ -226,7 +242,3 @@ function isValidSkillsEntry(entry) {
|
|
|
226
242
|
typeof obj.installs === "number" &&
|
|
227
243
|
typeof obj.source === "string");
|
|
228
244
|
}
|
|
229
|
-
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
230
|
-
function isExpired(mtimeMs, ttlMs) {
|
|
231
|
-
return Date.now() - mtimeMs > ttlMs;
|
|
232
|
-
}
|