loadout-ai 0.6.0 → 0.8.0

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +129 -26
  3. package/catalog/discovered.json +28469 -24524
  4. package/dist/src/cli.js +7 -2
  5. package/dist/src/commands/agents.js +6 -23
  6. package/dist/src/commands/catalog.js +186 -102
  7. package/dist/src/commands/health.js +6 -6
  8. package/dist/src/commands/inventory.js +19 -120
  9. package/dist/src/commands/lifecycle.js +13 -51
  10. package/dist/src/commands/mcp.js +10 -73
  11. package/dist/src/commands/setup.js +5 -5
  12. package/dist/src/commands/sharing.js +11 -108
  13. package/dist/src/commands/support.js +20 -9
  14. package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
  15. package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +48 -6
  16. package/dist/src/core/{codex-mcp.js → agents/codex-mcp.js} +1 -1
  17. package/dist/src/core/{health-score-evidence.js → agents/health-score-evidence.js} +8 -8
  18. package/dist/src/core/{runtime-tools.js → agents/runtime-tools.js} +2 -2
  19. package/dist/src/core/{audit.js → catalog/audit.js} +4 -4
  20. package/dist/src/core/{catalog.js → catalog/catalog.js} +5 -5
  21. package/dist/src/core/{components.js → catalog/components.js} +2 -2
  22. package/dist/src/core/{conformance.js → catalog/conformance.js} +2 -2
  23. package/dist/src/core/{profiles.js → catalog/profiles.js} +2 -2
  24. package/dist/src/core/{provenance.js → catalog/provenance.js} +3 -3
  25. package/dist/src/core/{registry.js → catalog/registry.js} +59 -11
  26. package/dist/src/core/{safety.js → catalog/safety.js} +36 -7
  27. package/dist/src/core/{skill-compare.js → catalog/skill-compare.js} +1 -1
  28. package/dist/src/core/{skill-inventory.js → catalog/skill-inventory.js} +3 -3
  29. package/dist/src/core/{skills.js → catalog/skills.js} +1 -1
  30. package/dist/src/core/{candidate-intelligence.js → discovery/candidate-intelligence.js} +6 -6
  31. package/dist/src/core/{discovery-connector.js → discovery/discovery-connector.js} +2 -2
  32. package/dist/src/core/{evaluate.js → discovery/evaluate.js} +2 -2
  33. package/dist/src/core/{mcp-registry-discovery.js → discovery/mcp-registry-discovery.js} +1 -1
  34. package/dist/src/core/{observations.js → discovery/observations.js} +3 -3
  35. package/dist/src/core/{review-queue.js → discovery/review-queue.js} +2 -2
  36. package/dist/src/core/{skills-sh-discovery.js → discovery/skills-sh-discovery.js} +1 -1
  37. package/dist/src/core/{adopt.js → install/adopt.js} +3 -3
  38. package/dist/src/core/{catalog-install.js → install/catalog-install.js} +11 -11
  39. package/dist/src/core/{file-lock.js → install/file-lock.js} +1 -1
  40. package/dist/src/core/{install.js → install/install.js} +5 -5
  41. package/dist/src/core/{package.js → install/package.js} +3 -3
  42. package/dist/src/core/{reconcile.js → install/reconcile.js} +5 -5
  43. package/dist/src/core/{remove.js → install/remove.js} +4 -4
  44. package/dist/src/core/{snapshot.js → install/snapshot.js} +1 -1
  45. package/dist/src/core/{source.js → install/source.js} +22 -8
  46. package/dist/src/core/{sync.js → install/sync.js} +9 -9
  47. package/dist/src/core/{transaction.js → install/transaction.js} +1 -1
  48. package/dist/src/core/{uninstall.js → install/uninstall.js} +4 -4
  49. package/dist/src/core/{update.js → install/update.js} +4 -4
  50. package/dist/src/core/{cli-guide.js → reporting/cli-guide.js} +29 -45
  51. package/dist/src/core/reporting/completion.js +90 -0
  52. package/dist/src/core/{doctor.js → reporting/doctor.js} +5 -10
  53. package/dist/src/core/{freshness-alerts.js → reporting/freshness-alerts.js} +5 -5
  54. package/dist/src/core/{health.js → reporting/health.js} +6 -6
  55. package/dist/src/core/{loadout-card.js → reporting/loadout-card.js} +1 -1
  56. package/dist/src/core/{readme-claims.js → reporting/readme-claims.js} +1 -1
  57. package/dist/src/core/{readme-facts.js → reporting/readme-facts.js} +2 -2
  58. package/dist/src/core/{share-report.js → reporting/share-report.js} +3 -3
  59. package/dist/src/core/{upgrade.js → reporting/upgrade.js} +7 -7
  60. package/dist/src/core/routing/first-party-skills.js +118 -0
  61. package/dist/src/core/routing/handoff.js +314 -0
  62. package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
  63. package/dist/src/core/routing/policy.js +147 -0
  64. package/dist/src/core/{route.js → routing/route.js} +202 -39
  65. package/dist/src/core/{api.js → runtime/api.js} +4 -4
  66. package/dist/src/core/{canary.js → runtime/canary.js} +1 -1
  67. package/dist/src/core/{github.js → runtime/github.js} +1 -1
  68. package/dist/src/core/{mcp-recipes.js → runtime/mcp-recipes.js} +1 -1
  69. package/dist/src/core/{mcp.js → runtime/mcp.js} +2 -2
  70. package/dist/src/core/{scheduler.js → runtime/scheduler.js} +4 -4
  71. package/dist/src/core/{active-policy.js → workspace/active-policy.js} +2 -2
  72. package/dist/src/core/{active-set.js → workspace/active-set.js} +2 -2
  73. package/dist/src/core/{improve.js → workspace/improve.js} +3 -3
  74. package/dist/src/core/{manifest.js → workspace/manifest.js} +2 -2
  75. package/dist/src/core/{outcomes.js → workspace/outcomes.js} +3 -3
  76. package/dist/src/core/{portable.js → workspace/portable.js} +3 -3
  77. package/dist/src/core/{profile-state.js → workspace/profile-state.js} +1 -1
  78. package/dist/src/core/{recommend.js → workspace/recommend.js} +1 -1
  79. package/dist/src/core/{state.js → workspace/state.js} +3 -3
  80. package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
  81. package/docs/CATALOG.md +1 -1
  82. package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
  83. package/docs/DISCOVERED.md +250 -248
  84. package/docs/FEATURE_TEST_MATRIX.md +7 -260
  85. package/docs/GITHUB_AUTHORIZATION.md +5 -0
  86. package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
  87. package/docs/RELEASE_REVIEW.md +0 -1
  88. package/docs/evidence/readme-claims.json +22 -19
  89. package/package.json +7 -4
  90. package/skills/loadout-curator/SKILL.md +105 -0
  91. package/skills/loadout-router/SKILL.md +85 -0
  92. package/MASTER_PLAN.md +0 -2207
  93. package/dist/src/core/completion.js +0 -143
  94. package/dist/src/core/handoff.js +0 -161
  95. package/docs/ACTIVE_SET.md +0 -53
  96. package/docs/COMPATIBILITY_POLICY.md +0 -22
  97. package/docs/CONVERSION_AND_SANDBOX.md +0 -27
  98. package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
  99. package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
  100. package/docs/PROVIDER_CONFIGURATION.md +0 -45
  101. package/docs/README_RESEARCH.md +0 -36
  102. package/docs/REPOSITORY_STABILIZATION.md +0 -190
  103. package/docs/SAFE_UPDATE_DEMO.md +0 -25
  104. package/docs/SCHEMA_DECISIONS.md +0 -25
  105. package/docs/SUBMISSION_COPY.md +0 -90
  106. package/docs/TEAM_POLICY.md +0 -18
  107. package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
  108. package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
  109. package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
  110. package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
  111. package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
  112. package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
  113. /package/dist/src/core/{agent-health-score.js → agents/agent-health-score.js} +0 -0
  114. /package/dist/src/core/{agent-versions.js → agents/agent-versions.js} +0 -0
  115. /package/dist/src/core/{conversion.js → agents/conversion.js} +0 -0
  116. /package/dist/src/core/{paths.js → agents/paths.js} +0 -0
  117. /package/dist/src/core/{runtime-tool-recipe.js → agents/runtime-tool-recipe.js} +0 -0
  118. /package/dist/src/core/{catalog-coverage.js → catalog/catalog-coverage.js} +0 -0
  119. /package/dist/src/core/{skill-security.js → catalog/skill-security.js} +0 -0
  120. /package/dist/src/core/{community.js → discovery/community.js} +0 -0
  121. /package/dist/src/core/{github-discovery.js → discovery/github-discovery.js} +0 -0
  122. /package/dist/src/core/{private-discovery.js → discovery/private-discovery.js} +0 -0
  123. /package/dist/src/core/{ranking.js → discovery/ranking.js} +0 -0
  124. /package/dist/src/core/{atomic-file.js → install/atomic-file.js} +0 -0
  125. /package/dist/src/core/{diff.js → install/diff.js} +0 -0
  126. /package/dist/src/core/{update-watch.js → install/update-watch.js} +0 -0
  127. /package/dist/src/core/{loadout-badge.js → reporting/loadout-badge.js} +0 -0
  128. /package/dist/src/core/{terminal.js → reporting/terminal.js} +0 -0
  129. /package/dist/src/core/{access.js → routing/access.js} +0 -0
  130. /package/dist/src/core/{credentials.js → routing/credentials.js} +0 -0
  131. /package/dist/src/core/{sandbox.js → runtime/sandbox.js} +0 -0
  132. /package/dist/src/core/{active-limit.js → workspace/active-limit.js} +0 -0
  133. /package/dist/src/core/{target-occupancy.js → workspace/target-occupancy.js} +0 -0
@@ -1,20 +1,18 @@
1
- import { loadEffectiveCatalog, } from "../core/catalog.js";
2
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
1
+ import { loadEffectiveCatalog, } from "../core/catalog/catalog.js";
2
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { resolve } from "node:path";
5
- import { buildSkillPlan, applySkillInstall, installedAgents, } from "../core/install.js";
6
- import { listSnapshotIds, readSnapshot, restoreSnapshot, summarizeSnapshot, } from "../core/snapshot.js";
7
- import { fetchRepositorySnapshot } from "../core/source.js";
8
- import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, selectSafeAutomaticUpdates, } from "../core/update.js";
9
- import { startApiServer } from "../core/api.js";
10
- import { readInstallState } from "../core/state.js";
11
- import { evaluateInstalledProfile, formatInstalledProfileStatus, } from "../core/profile-state.js";
12
- import { resolveCatalogProfile } from "../core/profiles.js";
13
- import { compileConversion, } from "../core/conversion.js";
14
- import { writeFileAtomically } from "../core/atomic-file.js";
15
- import { formatCanaryResult, runCanary } from "../core/canary.js";
16
- import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/catalog-install.js";
17
- import { withMutationLock } from "../core/transaction.js";
5
+ import { buildSkillPlan, applySkillInstall, installedAgents, } from "../core/install/install.js";
6
+ import { listSnapshotIds, readSnapshot, restoreSnapshot, summarizeSnapshot, } from "../core/install/snapshot.js";
7
+ import { fetchRepositorySnapshot } from "../core/install/source.js";
8
+ import { applyPackageUpdate, buildUpdatePlan, formatUpdatePlan, selectSafeAutomaticUpdates, } from "../core/install/update.js";
9
+ import { readInstallState } from "../core/workspace/state.js";
10
+ import { evaluateInstalledProfile, formatInstalledProfileStatus, } from "../core/workspace/profile-state.js";
11
+ import { resolveCatalogProfile } from "../core/catalog/profiles.js";
12
+ import { compileConversion, } from "../core/agents/conversion.js";
13
+ import { writeFileAtomically } from "../core/install/atomic-file.js";
14
+ import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/install/catalog-install.js";
15
+ import { withMutationLock } from "../core/install/transaction.js";
18
16
  import { setupSelection, printSetupProgress } from "./support.js";
19
17
  export function registerLifecycle(program) {
20
18
  program
@@ -372,40 +370,4 @@ export function registerLifecycle(program) {
372
370
  ? "Manual approval is required before using this artifact."
373
371
  : "No additional approval is required."));
374
372
  });
375
- program
376
- .command("canary")
377
- .description("Run a static canary policy gate for a candidate without installing it")
378
- .requiredOption("--source <directory>", "candidate package directory")
379
- .requiredOption("--package <id>", "candidate package id")
380
- .option("--repository <owner/repo>", "candidate repository")
381
- .option("--commit <sha>", "candidate immutable commit")
382
- .option("--approve", "approve promotion if a promotion callback is supplied")
383
- .option("--allow-unready", "allow review findings in the static gate")
384
- .option("--json", "emit machine-readable JSON")
385
- .action(async (options) => {
386
- const result = await runCanary({
387
- packageId: options.package,
388
- root: options.source,
389
- ...(options.repository ? { repository: options.repository } : {}),
390
- ...(options.commit ? { commit: options.commit } : {}),
391
- }, { enabled: true, requireStaticReady: !options.allowUnready }, { approve: options.approve });
392
- console.log(options.json
393
- ? JSON.stringify(result, null, 2)
394
- : formatCanaryResult(result));
395
- if (result.status === "blocked")
396
- process.exitCode = 1;
397
- });
398
- program
399
- .command("serve")
400
- .description("Start a loopback-only read-only API for status, health, catalog, and updates")
401
- .option("--port <port>", "TCP port (0 selects an available port)", "0")
402
- .action(async (options) => {
403
- const handle = await startApiServer({ port: Number(options.port) });
404
- console.log(`Loadout API listening at http://${handle.host}:${handle.port}`);
405
- await new Promise((resolve) => {
406
- process.once("SIGINT", resolve);
407
- process.once("SIGTERM", resolve);
408
- });
409
- await handle.close();
410
- });
411
373
  }
@@ -1,16 +1,15 @@
1
- import { userHome } from "../core/paths.js";
1
+ import { userHome } from "../core/agents/paths.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { fetchRepositorySnapshot } from "../core/source.js";
5
- import { discoverMcpManifests, summarizeMcpManifest, planMcpConfig, summarizeMcpConfigPlan, applyMcpConfigPlan, } from "../core/mcp.js";
6
- import { REVIEWED_MCP_RECIPES, buildMcpRecipeServer, findMcpRecipe, formatMcpRecipePlan, planMcpRecipe, verifyMcpRecipe, verifyMcpRecipeConnection, } from "../core/mcp-recipes.js";
7
- import { inspectPackage, formatPackageInspection } from "../core/package.js";
8
- import { installStatePath, recordInstallTransaction, recordMcpInstall, } from "../core/state.js";
9
- import { applyCodexMcpConfigPlan, codexMcpServerFingerprint, defaultCodexMcpConfigPath, planCodexMcpConfig, } from "../core/codex-mcp.js";
10
- import { evaluatePackage, formatPackageEvaluation } from "../core/evaluate.js";
11
- import { checkForUpdates, startUpdateWatcher } from "../core/update-watch.js";
12
- import { runDisposableSandbox } from "../core/sandbox.js";
13
- import { applyNativeScheduler, formatNativeScheduler, planNativeScheduler, } from "../core/scheduler.js";
4
+ import { fetchRepositorySnapshot } from "../core/install/source.js";
5
+ import { discoverMcpManifests, summarizeMcpManifest, planMcpConfig, summarizeMcpConfigPlan, applyMcpConfigPlan, } from "../core/runtime/mcp.js";
6
+ import { REVIEWED_MCP_RECIPES, buildMcpRecipeServer, findMcpRecipe, formatMcpRecipePlan, planMcpRecipe, verifyMcpRecipe, verifyMcpRecipeConnection, } from "../core/runtime/mcp-recipes.js";
7
+ import { inspectPackage, formatPackageInspection, } from "../core/install/package.js";
8
+ import { installStatePath, recordInstallTransaction, recordMcpInstall, } from "../core/workspace/state.js";
9
+ import { applyCodexMcpConfigPlan, codexMcpServerFingerprint, defaultCodexMcpConfigPath, planCodexMcpConfig, } from "../core/agents/codex-mcp.js";
10
+ import { evaluatePackage, formatPackageEvaluation, } from "../core/discovery/evaluate.js";
11
+ import { checkForUpdates, startUpdateWatcher, } from "../core/install/update-watch.js";
12
+ import { applyNativeScheduler, formatNativeScheduler, planNativeScheduler, } from "../core/runtime/scheduler.js";
14
13
  import { collectOption, parseMcpCredentialMappings, durableSchedulerLauncher, } from "./support.js";
15
14
  export function registerMcp(program) {
16
15
  program
@@ -318,29 +317,6 @@ export function registerMcp(program) {
318
317
  : `${formatNativeScheduler(plan)}\nApplied. Snapshot: ${snapshotId}`);
319
318
  });
320
319
  }
321
- program
322
- .command("sandbox-run")
323
- .description("Run an explicitly approved command in a disposable networkless Docker sandbox")
324
- .requiredOption("--source <directory>", "read-only source directory")
325
- .requiredOption("--image <image>", "reviewed/pinned Docker image reference")
326
- .requiredOption("--command <argument>", "command argument (repeatable; first is executable)", (value, previous = []) => [...previous, value], [])
327
- .requiredOption("--approve-risk", "explicitly approve sandbox execution")
328
- .option("--timeout <milliseconds>", "execution timeout", "120000")
329
- .option("--json", "emit result JSON")
330
- .action(async (options) => {
331
- const result = await runDisposableSandbox({
332
- sourceDirectory: options.source,
333
- image: options.image,
334
- command: options.command,
335
- approveRisk: options.approveRisk,
336
- timeoutMs: Number(options.timeout),
337
- });
338
- console.log(options.json
339
- ? JSON.stringify(result, null, 2)
340
- : `Sandbox exited ${result.exitCode}\n${result.stdout}${result.stderr ? `\n${result.stderr}` : ""}`);
341
- if (result.exitCode !== 0)
342
- process.exitCode = result.exitCode;
343
- });
344
320
  program
345
321
  .command("mcp-config")
346
322
  .description("Plan or apply a safe MCP server configuration change (dry-run by default)")
@@ -382,43 +358,4 @@ export function registerMcp(program) {
382
358
  const snapshot = await applyMcpConfigPlan(plan);
383
359
  console.log(`Applied successfully. Snapshot: ${snapshot.rollbackSnapshotId}`);
384
360
  });
385
- program
386
- .command("codex-mcp-config")
387
- .description("Plan or add a Codex TOML MCP server (dry-run by default)")
388
- .option("--config <path>", "Codex config.toml path", defaultCodexMcpConfigPath())
389
- .requiredOption("--name <name>", "server name")
390
- .option("--command <command>", "local server command")
391
- .option("--url <url>", "remote MCP server URL")
392
- .option("--arg <value>", "server argument (repeatable)", (value, previous = []) => [...previous, value], [])
393
- .option("--env <NAME=VALUE>", "environment variable (repeatable; values are never printed)", (value, previous = []) => [...previous, value], [])
394
- .option("--yes", "apply the change; without this flag only a plan is shown")
395
- .action(async (options) => {
396
- if ((options.command ? 1 : 0) + (options.url ? 1 : 0) !== 1)
397
- throw new Error("Provide exactly one of --command or --url");
398
- const env = {};
399
- for (const item of options.env) {
400
- const separator = item.indexOf("=");
401
- if (separator <= 0)
402
- throw new Error(`Invalid --env '${item}'; expected NAME=VALUE`);
403
- const key = item.slice(0, separator);
404
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
405
- throw new Error(`Invalid environment variable name '${key}'`);
406
- env[key] = item.slice(separator + 1);
407
- }
408
- const server = {
409
- name: options.name,
410
- command: options.command,
411
- url: options.url,
412
- args: options.arg,
413
- env,
414
- sourcePath: options.config,
415
- warnings: [],
416
- };
417
- const plan = await planCodexMcpConfig(options.config, server);
418
- console.log(`Codex config: ${plan.path}\n - ${plan.summary}`);
419
- if (!options.yes)
420
- return console.log("Dry run only. Re-run with --yes to add this Codex MCP server.");
421
- const snapshot = await applyCodexMcpConfigPlan(plan);
422
- console.log(`Applied successfully. Snapshot: ${snapshot.rollbackSnapshotId}`);
423
- });
424
361
  }
@@ -1,8 +1,8 @@
1
- import { parseAgentSelection } from "../core/paths.js";
2
- import { formatHealthReport } from "../core/health.js";
3
- import { applyUpgrade, formatUpgradePlan, planUpgrade, summarizeUpgradePlan, } from "../core/upgrade.js";
4
- import { parseModelApiAccess } from "../core/access.js";
5
- import { ADVANCED_GUIDE } from "../core/cli-guide.js";
1
+ import { parseAgentSelection } from "../core/agents/paths.js";
2
+ import { formatHealthReport } from "../core/reporting/health.js";
3
+ import { applyUpgrade, formatUpgradePlan, planUpgrade, summarizeUpgradePlan, } from "../core/reporting/upgrade.js";
4
+ import { parseModelApiAccess } from "../core/routing/access.js";
5
+ import { ADVANCED_GUIDE } from "../core/reporting/cli-guide.js";
6
6
  import { collectOption, setupSelection, printSetupProgress, runSetup, printBeginnerGuide, } from "./support.js";
7
7
  export function registerSetup(program) {
8
8
  program
@@ -1,18 +1,15 @@
1
- import { loadEffectiveCatalog } from "../core/catalog.js";
2
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
3
- import { readFile } from "node:fs/promises";
1
+ import { loadEffectiveCatalog } from "../core/catalog/catalog.js";
2
+ import { parseAgentSelection } from "../core/agents/paths.js";
4
3
  import { resolve } from "node:path";
5
- import { addManifestPackage, initManifest, readManifest, removeManifestPackage, writeLockfile, } from "../core/manifest.js";
6
- import { readInstallState } from "../core/state.js";
7
- import { createPackage, packPackage, publishLocalPackage, publishRemotePackage, searchLocalRegistry, } from "../core/registry.js";
8
- import { auditLoadout, formatAuditReport } from "../core/audit.js";
9
- import { applyPortableImport, exportPortableLoadout, planPortableImport, } from "../core/portable.js";
10
- import { writeFileAtomically } from "../core/atomic-file.js";
11
- import { buildLibraryStateReport, formatLibrarySummary, formatLibraryStateReport, } from "../core/active-set.js";
12
- import { buildPrivacySafeReport, formatPrivacySafeReport, parsePrivacySafeLoadoutReport, writePrivacySafeReport, } from "../core/share-report.js";
13
- import { readLocalOutcomes, recordLocalOutcome, } from "../core/outcomes.js";
14
- import { createCredentialResolver } from "../core/credentials.js";
15
- import { buildLoadoutCard, compareLoadoutReports, formatLoadoutCard, formatLoadoutComparison, } from "../core/loadout-card.js";
4
+ import { addManifestPackage, initManifest, readManifest, removeManifestPackage, writeLockfile, } from "../core/workspace/manifest.js";
5
+ import { readInstallState } from "../core/workspace/state.js";
6
+ import { createPackage, searchLocalRegistry, } from "../core/catalog/registry.js";
7
+ import { auditLoadout, formatAuditReport } from "../core/catalog/audit.js";
8
+ import { applyPortableImport, exportPortableLoadout, planPortableImport, } from "../core/workspace/portable.js";
9
+ import { writeFileAtomically } from "../core/install/atomic-file.js";
10
+ import { buildLibraryStateReport, formatLibrarySummary, formatLibraryStateReport, } from "../core/workspace/active-set.js";
11
+ import { buildPrivacySafeReport, formatPrivacySafeReport, writePrivacySafeReport, } from "../core/reporting/share-report.js";
12
+ import { buildLoadoutCard, formatLoadoutCard, } from "../core/reporting/loadout-card.js";
16
13
  export function registerSharing(program) {
17
14
  program
18
15
  .command("init")
@@ -89,54 +86,6 @@ export function registerSharing(program) {
89
86
  const descriptor = await createPackage(directory, options);
90
87
  console.log(`Created ${descriptor.name}@${descriptor.version} in ${directory}.`);
91
88
  });
92
- program
93
- .command("pack")
94
- .description("Validate a package and print its deterministic inventory digest")
95
- .argument("[directory]", "package directory", ".")
96
- .option("--json", "emit machine-readable JSON")
97
- .action(async (directory, options) => {
98
- const packed = await packPackage(directory);
99
- console.log(options.json
100
- ? JSON.stringify(packed, null, 2)
101
- : `${packed.descriptor.name}@${packed.descriptor.version} — ${packed.files.length} file(s) — sha256:${packed.digest}`);
102
- });
103
- program
104
- .command("publish")
105
- .description("Publish an immutable package version to a local or remote Loadout registry")
106
- .argument("[directory]", "package directory", ".")
107
- .option("--local", "publish to the local registry")
108
- .option("--registry-url <url>", "remote registry base URL")
109
- .option("--credential-keychain <service>", "resolve the remote registry token from the OS credential store")
110
- .option("--credential-account <account>", "OS credential account")
111
- .option("--approve-risk", "explicitly approve publishing scripts, hooks, or binaries")
112
- .action(async (directory, options) => {
113
- if (Number(Boolean(options.local)) +
114
- Number(Boolean(options.registryUrl)) !==
115
- 1)
116
- throw new Error("Choose exactly one destination: --local or --registry-url");
117
- if (options.local) {
118
- if (options.credentialKeychain)
119
- throw new Error("Local publishing does not require a credential");
120
- const packed = await publishLocalPackage(directory, {
121
- approveRisk: options.approveRisk,
122
- });
123
- console.log(`Published ${packed.descriptor.name}@${packed.descriptor.version} with digest ${packed.digest}.`);
124
- return;
125
- }
126
- const token = await createCredentialResolver()(options.credentialKeychain
127
- ? {
128
- kind: "os-keychain",
129
- service: options.credentialKeychain,
130
- ...(options.credentialAccount
131
- ? { account: options.credentialAccount }
132
- : {}),
133
- }
134
- : { kind: "environment", name: "LOADOUT_REGISTRY_TOKEN" });
135
- if (!token)
136
- throw new Error("Remote publishing credential did not resolve; set LOADOUT_REGISTRY_TOKEN or use --credential-keychain");
137
- const published = await publishRemotePackage(directory, options.registryUrl, token, { approveRisk: options.approveRisk });
138
- console.log(`Published ${published.name}@${published.version} with digest ${published.digest}.`);
139
- });
140
89
  program
141
90
  .command("search")
142
91
  .description("Search the bundled catalog and local registry")
@@ -283,39 +232,6 @@ export function registerSharing(program) {
283
232
  ? JSON.stringify(report, null, 2)
284
233
  : formatPrivacySafeReport(report));
285
234
  });
286
- program
287
- .command("outcomes")
288
- .description("Show privacy-safe local outcome signals; never uploads project or prompt data")
289
- .option("--json", "emit machine-readable JSON")
290
- .action(async (options) => {
291
- const store = await readLocalOutcomes();
292
- console.log(options.json
293
- ? JSON.stringify(store, null, 2)
294
- : [
295
- `Local outcomes: ${store.events.length}`,
296
- ...store.events.map((event) => `${event.recordedAt} — ${event.selector} — ${event.agent}/${event.taskFamily} — ${event.result}`),
297
- "Privacy: local only; no project names, paths, prompts, code, filenames, or secrets.",
298
- ].join("\n"));
299
- });
300
- program
301
- .command("outcome")
302
- .description("Record an explicit local, agent/task-scoped skill outcome")
303
- .argument("<selector>", "exact package/skill selector")
304
- .requiredOption("--agent <id>", "agent id")
305
- .requiredOption("--task <family>", "general, frontend, testing, javascript, python, backend, security, or documentation")
306
- .requiredOption("--result <value>", "accept, reject, success, failure, activation, disable, or rollback")
307
- .action(async (selector, options) => {
308
- const knownAgents = new Set((await detectAgents()).map((agent) => agent.id));
309
- if (!knownAgents.has(options.agent))
310
- throw new Error(`Unknown agent id: ${options.agent}`);
311
- const event = await recordLocalOutcome({
312
- selector,
313
- agent: options.agent,
314
- taskFamily: options.task,
315
- result: options.result,
316
- });
317
- console.log(`Recorded local outcome ${event.id}. No project, prompt, or source data was stored.`);
318
- });
319
235
  program
320
236
  .command("share")
321
237
  .description("Write the privacy-safe Loadout report to a JSON artifact")
@@ -342,17 +258,4 @@ export function registerSharing(program) {
342
258
  }
343
259
  console.log(options.json ? JSON.stringify(card, null, 2) : markdown);
344
260
  });
345
- program
346
- .command("compare-loadouts")
347
- .description("Compare aggregate counts from two explicit privacy-safe Loadout reports")
348
- .argument("<left>", "earlier privacy-safe report JSON")
349
- .argument("<right>", "later privacy-safe report JSON")
350
- .option("--json", "emit machine-readable count deltas")
351
- .action(async (left, right, options) => {
352
- const [before, after] = await Promise.all([left, right].map(async (path) => parsePrivacySafeLoadoutReport(JSON.parse(await readFile(resolve(path), "utf8")))));
353
- const comparison = compareLoadoutReports(before, after);
354
- console.log(options.json
355
- ? JSON.stringify(comparison, null, 2)
356
- : formatLoadoutComparison(comparison));
357
- });
358
261
  }
@@ -1,12 +1,12 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { createRequire } from "node:module";
3
- import { detectAgents, parseAgentSelection } from "../core/paths.js";
3
+ import { detectAgents, parseAgentSelection } from "../core/agents/paths.js";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/catalog-install.js";
7
- import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/skill-inventory.js";
8
- import { interactiveModelApiAccess, parseModelApiAccess, } from "../core/access.js";
9
- import { BEGINNER_GUIDE } from "../core/cli-guide.js";
6
+ import { applyPreparedCatalogInstall, formatCatalogApplyGuidance, formatPreparedCatalogInstall, prepareCatalogInstall, } from "../core/install/catalog-install.js";
7
+ import { formatInstalledSkillInventory, scanInstalledSkills, } from "../core/catalog/skill-inventory.js";
8
+ import { interactiveModelApiAccess, parseModelApiAccess, } from "../core/routing/access.js";
9
+ import { BEGINNER_GUIDE } from "../core/reporting/cli-guide.js";
10
10
  export const collectOption = (value, previous = []) => [...previous, value];
11
11
  export function parseMcpCredentialMappings(recipe, mappings, account) {
12
12
  const references = {};
@@ -105,7 +105,16 @@ export async function runSetup(options) {
105
105
  try {
106
106
  if (!mode) {
107
107
  if (!interactive) {
108
- console.log("Pick a loadout to preview: `loadout setup --mode stable` (recommended), `--mode power` (broader), or `--mode maximum` (full library, activate per project). Nothing installs until you add --yes.");
108
+ console.log([
109
+ "Pick a loadout to preview:",
110
+ " --mode stable 4 reviewed sources, 30 skills, all active (recommended)",
111
+ " --mode power 8 sources, 56 skills, all active",
112
+ " --mode maximum every reviewed source, downloaded but DISABLED; a project",
113
+ " activates what it needs with `loadout optimize --project .`",
114
+ " --mode custom --package <id>, one or more",
115
+ "",
116
+ "Nothing installs until you add --yes.",
117
+ ].join("\n"));
109
118
  return;
110
119
  }
111
120
  reader = createInterface({
@@ -114,9 +123,11 @@ export async function runSetup(options) {
114
123
  });
115
124
  const answer = (await reader.question([
116
125
  "Choose a loadout:",
117
- " [1] Stable — a small, reviewed set of everyday skills (recommended)",
118
- " [2] Power — a broader daily-driver set",
119
- " [3] Maximum — download the full reviewed library, activate per project later",
126
+ " [1] Stable — 4 reviewed sources, 30 skills, all active (recommended)",
127
+ " [2] Power — 8 sources, 56 skills, all active",
128
+ " [3] Maximum — download every reviewed source, but leave it DISABLED.",
129
+ " Nothing reaches your prompts until a project activates",
130
+ " it with `loadout optimize --project .`. Costs disk, not context.",
120
131
  " [4] Custom — pick exact package ids",
121
132
  "> ",
122
133
  ].join("\n"))).trim();
@@ -1,6 +1,6 @@
1
1
  import { dirname, join } from "node:path";
2
- import { planSkillInstall } from "./skills.js";
3
- import { formatTerminalTable } from "./terminal.js";
2
+ import { planSkillInstall } from "../catalog/skills.js";
3
+ import { formatTerminalTable } from "../reporting/terminal.js";
4
4
  /**
5
5
  * Rules shared by the CLI, planner, and documentation. A capability only
6
6
  * changes state when it is native or has this explicitly tested adaptation.
@@ -136,15 +136,57 @@ export async function inspectAgent(agent) {
136
136
  export async function inspectAgents(agents) {
137
137
  return Promise.all(agents.map(inspectAgent));
138
138
  }
139
- export function formatAgentInventory(inventory) {
139
+ /**
140
+ * Count installable units rather than raw filesystem entries. A skill is one
141
+ * directory containing SKILL.md, so counting every nested file reports a number
142
+ * many times larger than the number of skills the user actually has.
143
+ */
144
+ export function countComponentUnits(component) {
145
+ if (!component.scanned || !component.directoryExists)
146
+ return 0;
147
+ const skillRoots = new Set();
148
+ let looseFiles = 0;
149
+ for (const entry of component.entries) {
150
+ if (entry.kind !== "file")
151
+ continue;
152
+ const segments = entry.path.split("/");
153
+ const name = segments[segments.length - 1];
154
+ if (name === "SKILL.md") {
155
+ // Depth 1 means SKILL.md sits at the component root, which is one unit.
156
+ skillRoots.add(segments.slice(0, -1).join("/") || ".");
157
+ }
158
+ else if (segments.length === 1)
159
+ looseFiles += 1;
160
+ }
161
+ return skillRoots.size || looseFiles;
162
+ }
163
+ /**
164
+ * One agent's inventory. The default is a two-line summary: what is installed
165
+ * and which component kinds the adapter supports. Adapter internals — the
166
+ * per-kind compatibility notes and the paths that do not exist yet — are only
167
+ * useful when diagnosing, so they stay behind `details`.
168
+ */
169
+ export function formatAgentInventory(inventory, options = {}) {
140
170
  const lines = [
141
171
  `${inventory.agent.installed ? "✓" : "○"} ${inventory.agent.displayName}`,
142
172
  ];
143
- for (const component of inventory.components) {
144
- const state = component.scanned
145
- ? `${component.directoryExists ? `${component.entries.length} filesystem item(s)` : "not created"} — ${component.directory}`
146
- : (component.note ?? "not inspected");
147
- lines.push(` ${component.type}: ${component.compatibility}; ${state}`);
173
+ if (options.details) {
174
+ for (const component of inventory.components) {
175
+ const state = component.scanned
176
+ ? `${component.directoryExists ? `${component.entries.length} filesystem item(s)` : "not created"} — ${component.directory}`
177
+ : (component.note ?? "not inspected");
178
+ lines.push(` ${component.type}: ${component.compatibility}; ${state}`);
179
+ }
180
+ }
181
+ else {
182
+ const items = inventory.components.reduce((total, component) => total + countComponentUnits(component), 0);
183
+ const supported = inventory.components
184
+ .filter((component) => component.compatibility !== "unsupported")
185
+ .map((component) => component.type);
186
+ const skills = inventory.components.find((component) => component.type === "skill");
187
+ if (skills?.directory)
188
+ lines.push(` ${skills.directory}`);
189
+ lines.push(` ${items} skills | supports: ${supported.join(", ")}`);
148
190
  }
149
191
  for (const warning of inventory.warnings)
150
192
  lines.push(` ! ${warning}`);
@@ -2,7 +2,7 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { loadoutHome, userHome } from "./paths.js";
5
- import { runMutationTransaction } from "./transaction.js";
5
+ import { runMutationTransaction } from "../install/transaction.js";
6
6
  function safeName(name) {
7
7
  if (!/^[A-Za-z0-9._-]{1,128}$/.test(name))
8
8
  throw new Error("MCP server name may contain only letters, numbers, ., _, and -");
@@ -1,16 +1,16 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { buildAgentHealthScore, } from "./agent-health-score.js";
4
- import { DEFAULT_ACTIVE_SKILL_LIMIT } from "./active-limit.js";
5
- import { managedFileReadPath } from "./active-set.js";
4
+ import { DEFAULT_ACTIVE_SKILL_LIMIT } from "../workspace/active-limit.js";
5
+ import { managedFileReadPath } from "../workspace/active-set.js";
6
6
  import { adapterCapabilities } from "./adapters.js";
7
- import { loadEffectiveCatalog } from "./catalog.js";
7
+ import { loadEffectiveCatalog } from "../catalog/catalog.js";
8
8
  import { detectAgents } from "./paths.js";
9
- import { readLocalOutcomes } from "./outcomes.js";
10
- import { readSnapshot } from "./snapshot.js";
11
- import { scanInstalledSkills } from "./skill-inventory.js";
12
- import { readInstallState } from "./state.js";
13
- import { transactionRoot } from "./transaction.js";
9
+ import { readLocalOutcomes } from "../workspace/outcomes.js";
10
+ import { readSnapshot } from "../install/snapshot.js";
11
+ import { scanInstalledSkills } from "../catalog/skill-inventory.js";
12
+ import { readInstallState } from "../workspace/state.js";
13
+ import { transactionRoot } from "../install/transaction.js";
14
14
  function freshness(pkg, asOf) {
15
15
  const observed = pkg?.pushedAt ?? pkg?.lastUpdatedAt;
16
16
  if (!observed)
@@ -2,10 +2,10 @@ import { execFile } from "node:child_process";
2
2
  import { access, readFile, readdir, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { promisify } from "node:util";
5
- import { writeFileAtomically } from "./atomic-file.js";
5
+ import { writeFileAtomically } from "../install/atomic-file.js";
6
6
  import { detectAgents, loadoutHome, userHome } from "./paths.js";
7
7
  import { parseRuntimeToolRecipe, renderRuntimeRecipeValue, resolveRuntimeRecipePath, } from "./runtime-tool-recipe.js";
8
- import { createSnapshot, readSnapshot, recordSnapshotPostMutationState, restoreSnapshot, } from "./snapshot.js";
8
+ import { createSnapshot, readSnapshot, recordSnapshotPostMutationState, restoreSnapshot, } from "../install/snapshot.js";
9
9
  const execFileAsync = promisify(execFile);
10
10
  function deepFreeze(value) {
11
11
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1,10 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
- import { formatSchemaError, loadoutLockfileSchema } from "../shared/schemas.js";
5
- import { readManifest } from "./manifest.js";
6
- import { readInstallState } from "./state.js";
7
- import { managedFileReadPath } from "./active-set.js";
4
+ import { formatSchemaError, loadoutLockfileSchema, } from "../../shared/schemas.js";
5
+ import { readManifest } from "../workspace/manifest.js";
6
+ import { readInstallState } from "../workspace/state.js";
7
+ import { managedFileReadPath } from "../workspace/active-set.js";
8
8
  export function parseLockfile(value) {
9
9
  const result = loadoutLockfileSchema.safeParse(value);
10
10
  if (!result.success)
@@ -2,12 +2,12 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { catalogSchema, formatSchemaError } from "../shared/schemas.js";
6
- import { fetchGitHubMetadata } from "./github.js";
7
- import { loadoutHome } from "./paths.js";
8
- import { compareCatalogPackages, explainCatalogScore } from "./ranking.js";
5
+ import { catalogSchema, formatSchemaError } from "../../shared/schemas.js";
6
+ import { fetchGitHubMetadata, } from "../runtime/github.js";
7
+ import { loadoutHome } from "../agents/paths.js";
8
+ import { compareCatalogPackages, explainCatalogScore, } from "../discovery/ranking.js";
9
9
  import { resolveCatalogProfile } from "./profiles.js";
10
- import { recordCatalogObservations } from "./observations.js";
10
+ import { recordCatalogObservations } from "../discovery/observations.js";
11
11
  const cachedCatalogPath = () => join(loadoutHome(), "catalog.json");
12
12
  function bundledCatalogPath() {
13
13
  const moduleDirectory = dirname(fileURLToPath(import.meta.url));
@@ -1,7 +1,7 @@
1
1
  import { lstat, readdir } from "node:fs/promises";
2
2
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
- import { buildSkillPlan } from "./install.js";
4
- import { adapterCapabilities, agentComponentDirectory } from "./adapters.js";
3
+ import { buildSkillPlan } from "../install/install.js";
4
+ import { adapterCapabilities, agentComponentDirectory, } from "../agents/adapters.js";
5
5
  const RESOURCE_DIRECTORIES = new Map([
6
6
  ["rules", "rule"],
7
7
  ["commands", "command"],
@@ -1,5 +1,5 @@
1
- import { ADAPTER_CAPABILITIES, agentComponentDirectory } from "./adapters.js";
2
- import { AGENT_DEFINITIONS, agentSkillsDirectory } from "./paths.js";
1
+ import { ADAPTER_CAPABILITIES, agentComponentDirectory, } from "../agents/adapters.js";
2
+ import { AGENT_DEFINITIONS, agentSkillsDirectory } from "../agents/paths.js";
3
3
  const PLATFORM_BY_RUNNER = {
4
4
  "ubuntu-latest": "linux",
5
5
  "macos-latest": "macos",
@@ -1,4 +1,4 @@
1
- import { compareCatalogPackages } from "./ranking.js";
1
+ import { compareCatalogPackages } from "../discovery/ranking.js";
2
2
  /**
3
3
  * Stable is Loadout's bounded policy-selected daily driver: broad enough to improve normal
4
4
  * engineering work immediately, but bounded at skill granularity and restricted
@@ -247,7 +247,7 @@ export function resolveCatalogProfile(packages, selection, families = CATALOG_CO
247
247
  warnings.push(`Custom selection retains the soft overlap in ${family.label}. Review ${names} before installation.`);
248
248
  }
249
249
  else {
250
- warnings.push(`${selection.mode === "power" ? "Power Boost" : "Maximum Library"} retains the soft overlap in ${family.label}. Loadout policy orders ${primary.displayName} first; review each package before installation.`);
250
+ warnings.push(`${selection.mode === "power" ? "Power" : "Maximum"} retains the soft overlap in ${family.label}. Loadout policy orders ${primary.displayName} first; review each package before installation.`);
251
251
  }
252
252
  }
253
253
  return {
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { loadEffectiveCatalog } from "./catalog.js";
5
- import { ensureDirectory, loadoutHome } from "./paths.js";
5
+ import { ensureDirectory, loadoutHome } from "../agents/paths.js";
6
6
  import { discoverSkillDirectories } from "./skills.js";
7
- import { fetchRepositorySnapshot, } from "./source.js";
8
- import { writeFileAtomically } from "./atomic-file.js";
7
+ import { fetchRepositorySnapshot, } from "../install/source.js";
8
+ import { writeFileAtomically } from "../install/atomic-file.js";
9
9
  const catalogSkillIndexPath = () => join(loadoutHome(), "provenance", "catalog-skills.json");
10
10
  function immutableCatalogDigest(catalog) {
11
11
  const evidence = catalog