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
@@ -4,29 +4,64 @@
4
4
  // Prices as of August 2026.
5
5
  export const MODEL_CATALOG = [
6
6
  // --- Anthropic (Claude Code) ---
7
- { id: "claude-fable-5", provider: "anthropic", name: "Claude Fable 5", tier: "frontier", inputCostPer1M: 10, outputCostPer1M: 50, nativeAgents: ["claude-code"], current: true, generation: "5" },
8
- { id: "claude-opus-5", provider: "anthropic", name: "Claude Opus 5", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: true, generation: "5" },
9
- { id: "claude-sonnet-5", provider: "anthropic", name: "Claude Sonnet 5", tier: "standard", inputCostPer1M: 3, outputCostPer1M: 15, nativeAgents: ["claude-code"], current: true, generation: "5" },
10
- { id: "claude-opus-4-8", provider: "anthropic", name: "Claude Opus 4.8", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: false, generation: "4" },
11
- { id: "claude-opus-4-6", provider: "anthropic", name: "Claude Opus 4.6", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: false, generation: "4" },
12
- { id: "claude-sonnet-4-6", provider: "anthropic", name: "Claude Sonnet 4.6", tier: "standard", inputCostPer1M: 3, outputCostPer1M: 15, nativeAgents: ["claude-code"], current: false, generation: "4" },
13
- { id: "claude-haiku-4-5", provider: "anthropic", name: "Claude Haiku 4.5", tier: "fast", inputCostPer1M: 1, outputCostPer1M: 5, nativeAgents: ["claude-code"], current: true, generation: "4" },
14
- // --- OpenAI (Codex) — GPT-5.6 family ---
15
- { id: "gpt-5.6-sol", provider: "openai", name: "GPT-5.6 Sol", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 30, nativeAgents: ["codex"], current: true, generation: "5.6" },
16
- { id: "gpt-5.6-terra", provider: "openai", name: "GPT-5.6 Terra", tier: "standard", inputCostPer1M: 2, outputCostPer1M: 12, nativeAgents: ["codex"], current: true, generation: "5.6" },
17
- { id: "gpt-5.6-luna", provider: "openai", name: "GPT-5.6 Luna", tier: "fast", inputCostPer1M: 0.20, outputCostPer1M: 1.20, nativeAgents: ["codex"], current: true, generation: "5.6" },
18
- // --- OpenAI — GPT-5.5 ---
19
- { id: "gpt-5.5", provider: "openai", name: "GPT-5.5", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 30, nativeAgents: ["codex"], current: true, generation: "5.5" },
20
- // --- OpenAI — GPT-5.4 family ---
21
- { id: "gpt-5.4", provider: "openai", name: "GPT-5.4", tier: "standard", inputCostPer1M: 2.50, outputCostPer1M: 15, nativeAgents: ["codex"], current: true, generation: "5.4" },
22
- { id: "gpt-5.4-mini", provider: "openai", name: "GPT-5.4 Mini", tier: "fast", inputCostPer1M: 0.75, outputCostPer1M: 4.50, nativeAgents: ["codex"], current: true, generation: "5.4" },
23
- // --- OpenAI — Codex-specific ---
24
- { id: "gpt-5.1-codex", provider: "openai", name: "GPT-5.1 Codex", tier: "standard", inputCostPer1M: 1.25, outputCostPer1M: 10, nativeAgents: ["codex"], current: true, generation: "5.1" },
25
- // --- OpenAI — o-series reasoning ---
26
- { id: "o3-pro", provider: "openai", name: "o3-pro", tier: "frontier", inputCostPer1M: 20, outputCostPer1M: 80, nativeAgents: ["codex"], current: true, generation: "o3" },
27
- { id: "o3", provider: "openai", name: "o3", tier: "frontier", inputCostPer1M: 2, outputCostPer1M: 8, nativeAgents: ["codex"], current: true, generation: "o3" },
28
- { id: "o4-mini", provider: "openai", name: "o4-mini", tier: "standard", inputCostPer1M: 1.10, outputCostPer1M: 4.40, nativeAgents: ["codex"], current: true, generation: "o4" },
29
- { id: "o3-mini", provider: "openai", name: "o3-mini", tier: "standard", inputCostPer1M: 1.10, outputCostPer1M: 4.40, nativeAgents: ["codex"], current: true, generation: "o3" },
7
+ // Haiku and the 4.x line are deliberately absent: for real coding work the
8
+ // choice is Opus or Sonnet, and offering a weaker tier invites picking it.
9
+ {
10
+ id: "claude-opus-5",
11
+ provider: "anthropic",
12
+ name: "Claude Opus 5",
13
+ tier: "frontier",
14
+ inputCostPer1M: 5,
15
+ outputCostPer1M: 25,
16
+ nativeAgents: ["claude-code"],
17
+ current: true,
18
+ generation: "5",
19
+ },
20
+ {
21
+ id: "claude-sonnet-5",
22
+ provider: "anthropic",
23
+ name: "Claude Sonnet 5",
24
+ tier: "standard",
25
+ inputCostPer1M: 3,
26
+ outputCostPer1M: 15,
27
+ nativeAgents: ["claude-code"],
28
+ current: true,
29
+ generation: "5",
30
+ },
31
+ // --- OpenAI (Codex) — the current GPT-5.6 tiers only ---
32
+ {
33
+ id: "gpt-5.6-sol",
34
+ provider: "openai",
35
+ name: "GPT-5.6 Sol",
36
+ tier: "frontier",
37
+ inputCostPer1M: 5,
38
+ outputCostPer1M: 30,
39
+ nativeAgents: ["codex"],
40
+ current: true,
41
+ generation: "5.6",
42
+ },
43
+ {
44
+ id: "gpt-5.6-terra",
45
+ provider: "openai",
46
+ name: "GPT-5.6 Terra",
47
+ tier: "standard",
48
+ inputCostPer1M: 2,
49
+ outputCostPer1M: 12,
50
+ nativeAgents: ["codex"],
51
+ current: true,
52
+ generation: "5.6",
53
+ },
54
+ {
55
+ id: "gpt-5.6-luna",
56
+ provider: "openai",
57
+ name: "GPT-5.6 Luna",
58
+ tier: "fast",
59
+ inputCostPer1M: 0.2,
60
+ outputCostPer1M: 1.2,
61
+ nativeAgents: ["codex"],
62
+ current: true,
63
+ generation: "5.6",
64
+ },
30
65
  ];
31
66
  const TIER_LABELS = {
32
67
  frontier: "Frontier (deep reasoning)",
@@ -74,12 +109,70 @@ const PHASE_CONFIG = {
74
109
  },
75
110
  };
76
111
  const PHASE_KEYWORDS = {
77
- plan: ["plan", "design", "architect", "architecture", "rfc", "spec", "decompose", "strategy", "system design"],
78
- implement: ["implement", "build", "code", "create", "add", "feature", "write", "develop", "make"],
79
- review: ["review", "audit", "check", "inspect", "critique", "pr", "pull request", "diff"],
80
- test: ["test", "spec", "coverage", "unit", "integration", "e2e", "assert", "vitest", "jest"],
81
- debug: ["debug", "fix", "bug", "error", "crash", "broken", "investigate", "troubleshoot", "failing"],
82
- document: ["document", "docs", "readme", "comment", "jsdoc", "changelog", "explain", "docstring"],
112
+ plan: [
113
+ "plan",
114
+ "design",
115
+ "architect",
116
+ "architecture",
117
+ "rfc",
118
+ "spec",
119
+ "decompose",
120
+ "strategy",
121
+ "system design",
122
+ ],
123
+ implement: [
124
+ "implement",
125
+ "build",
126
+ "code",
127
+ "create",
128
+ "add",
129
+ "feature",
130
+ "write",
131
+ "develop",
132
+ "make",
133
+ ],
134
+ review: [
135
+ "review",
136
+ "audit",
137
+ "check",
138
+ "inspect",
139
+ "critique",
140
+ "pr",
141
+ "pull request",
142
+ "diff",
143
+ ],
144
+ test: [
145
+ "test",
146
+ "spec",
147
+ "coverage",
148
+ "unit",
149
+ "integration",
150
+ "e2e",
151
+ "assert",
152
+ "vitest",
153
+ "jest",
154
+ ],
155
+ debug: [
156
+ "debug",
157
+ "fix",
158
+ "bug",
159
+ "error",
160
+ "crash",
161
+ "broken",
162
+ "investigate",
163
+ "troubleshoot",
164
+ "failing",
165
+ ],
166
+ document: [
167
+ "document",
168
+ "docs",
169
+ "readme",
170
+ "comment",
171
+ "jsdoc",
172
+ "changelog",
173
+ "explain",
174
+ "docstring",
175
+ ],
83
176
  };
84
177
  // ---------------------------------------------------------------------------
85
178
  // Classification
@@ -114,6 +207,25 @@ export function cheapestInTier(tier) {
114
207
  const models = modelsForTier(tier);
115
208
  return models.sort((a, b) => a.inputCostPer1M - b.inputCostPer1M)[0];
116
209
  }
210
+ /**
211
+ * Narrow a recommendation to the agents actually present on this machine. A
212
+ * recommendation that names an agent the user has not installed is noise, so
213
+ * the resolved list drives both the printed advice and the handoff command.
214
+ */
215
+ export function resolveAvailableAgents(suggested, installed) {
216
+ const present = new Set(installed);
217
+ return {
218
+ available: suggested.filter((id) => present.has(id)),
219
+ missing: suggested.filter((id) => !present.has(id)),
220
+ };
221
+ }
222
+ /** Models in the recommended tier that the given agents can actually run. */
223
+ export function modelsRunnableBy(models, agents) {
224
+ if (!agents.length)
225
+ return models;
226
+ const wanted = new Set(agents);
227
+ return models.filter((m) => m.nativeAgents.some((id) => wanted.has(id)));
228
+ }
117
229
  // ---------------------------------------------------------------------------
118
230
  // Routing
119
231
  // ---------------------------------------------------------------------------
@@ -166,23 +278,60 @@ export function estimateCostSavings() {
166
278
  const conserveTotal = conserveEstimates.reduce((s, c) => s + c, 0);
167
279
  return { estimates, normalTotal, conserveTotal };
168
280
  }
169
- // ---------------------------------------------------------------------------
170
- // Formatters
171
- // ---------------------------------------------------------------------------
172
- export function formatRouteRecommendation(rec) {
173
- const modelNames = rec.models.slice(0, 4).map((m) => `${m.name} ($${m.inputCostPer1M}/$${m.outputCostPer1M})`);
281
+ /** Shell-quote a task description for the suggested handoff command. */
282
+ function shellQuote(value) {
283
+ return `'${value.replace(/'/g, `'\\''`)}'`;
284
+ }
285
+ export function formatRouteRecommendation(rec, context = {}) {
286
+ // `undefined` means detection never ran, so every suggested agent stands. An
287
+ // empty array means detection ran and found nothing, which must not be
288
+ // silently upgraded back to the full suggestion list.
289
+ const checked = context.installedAgents !== undefined;
290
+ const { available, missing } = checked
291
+ ? resolveAvailableAgents(rec.suggestedAgents, context.installedAgents)
292
+ : { available: rec.suggestedAgents, missing: [] };
293
+ // Only recommend models an available agent can actually run. Not every
294
+ // provider offers every tier — Claude Code has no fast tier — so when the
295
+ // recommended tier holds nothing the user can reach, step up to the nearest
296
+ // tier that does rather than naming a model they cannot run.
297
+ const runnable = checked
298
+ ? modelsRunnableBy(rec.models, available)
299
+ : rec.models;
300
+ const stepUp = {
301
+ fast: "standard",
302
+ standard: "frontier",
303
+ };
304
+ let effective = runnable;
305
+ let tier = rec.tier;
306
+ let substituted;
307
+ while (checked && available.length && !effective.length && stepUp[tier]) {
308
+ const next = stepUp[tier];
309
+ effective = modelsRunnableBy(modelsForTier(next), available);
310
+ if (effective.length)
311
+ substituted = { from: rec.tier, to: next };
312
+ tier = next;
313
+ }
314
+ const shown = (effective.length ? effective : rec.models).slice(0, 4);
315
+ const modelNames = shown.map((m) => `${m.name} ($${m.inputCostPer1M}/$${m.outputCostPer1M})`);
174
316
  const lines = [
175
317
  `Phase: ${rec.phase}`,
176
318
  `Tier: ${rec.tierLabel}`,
177
319
  `Models: ${modelNames.join("\n ")}`,
178
- `Agents: ${rec.suggestedAgents.join(", ")}`,
320
+ `Agents: ${available.length ? available.join(", ") : "none detected"}${missing.length ? ` (not installed: ${missing.join(", ")})` : ""}`,
179
321
  `Why: ${rec.reason}`,
180
322
  ];
323
+ if (substituted)
324
+ lines.push(`Note: your agents have no ${substituted.from}-tier model, so this shows`, ` the ${substituted.to} tier instead.`);
181
325
  if (rec.conserveAlternative) {
182
326
  const alt = rec.conserveAlternative;
183
327
  const altCheapest = cheapestInTier(alt.tier);
184
328
  lines.push(``, `Conserve: drop to ${alt.tier} tier (${altCheapest?.name ?? "—"} at $${altCheapest?.inputCostPer1M ?? "?"}/$${altCheapest?.outputCostPer1M ?? "?"})`, ` ${alt.tradeoff}`);
185
329
  }
330
+ // Actionable next step: hand this task to a detected agent.
331
+ if (context.description && available.length) {
332
+ const target = available[0];
333
+ lines.push(``, `Hand off:`, ` loadout handoff ${target} ${shellQuote(context.description)}`);
334
+ }
186
335
  return lines.join("\n");
187
336
  }
188
337
  export function formatRoutingTable(conserve = false) {
@@ -200,8 +349,13 @@ export function formatRoutingTable(conserve = false) {
200
349
  return `${phase} ${tier} ${model} ${inCost} ${outCost} ${agents}`;
201
350
  });
202
351
  return [
203
- conserve ? "ROUTING TABLE (conserve mode — usage-saving tier for each phase)" : "ROUTING TABLE",
204
- "", header, divider, ...rows,
352
+ conserve
353
+ ? "ROUTING TABLE (conserve mode — usage-saving tier for each phase)"
354
+ : "ROUTING TABLE",
355
+ "",
356
+ header,
357
+ divider,
358
+ ...rows,
205
359
  ].join("\n");
206
360
  }
207
361
  export function formatCostTable() {
@@ -216,7 +370,10 @@ export function formatCostTable() {
216
370
  });
217
371
  const savings = Math.round((1 - conserveTotal / normalTotal) * 100);
218
372
  return [
219
- header, divider, ...rows, divider,
373
+ header,
374
+ divider,
375
+ ...rows,
376
+ divider,
220
377
  `Normal total: $${normalTotal.toFixed(2)}/M input across 6 phases`,
221
378
  `Conserve total: $${conserveTotal.toFixed(2)}/M input (${savings}% cheaper)`,
222
379
  "",
@@ -244,5 +401,11 @@ export function formatModelCatalog(filter) {
244
401
  const agents = m.nativeAgents.slice(0, 3).join(", ");
245
402
  return `${name} ${provider} ${tier} ${inCost} ${outCost} ${gen} ${agents}`;
246
403
  });
247
- return [`${models.length} models${filter?.current === false ? " (including legacy)" : ""}`, "", header, divider, ...rows].join("\n");
404
+ return [
405
+ `${models.length} models${filter?.current === false ? " (including legacy)" : ""}`,
406
+ "",
407
+ header,
408
+ divider,
409
+ ...rows,
410
+ ].join("\n");
248
411
  }
@@ -1,8 +1,8 @@
1
1
  import { createServer, } from "node:http";
2
- import { loadEffectiveCatalog, rankCatalog } from "./catalog.js";
3
- import { detectAgents } from "./paths.js";
4
- import { buildUpdatePlan } from "./update.js";
5
- import { buildHealthReport } from "./health.js";
2
+ import { loadEffectiveCatalog, rankCatalog } from "../catalog/catalog.js";
3
+ import { detectAgents } from "../agents/paths.js";
4
+ import { buildUpdatePlan } from "../install/update.js";
5
+ import { buildHealthReport } from "../reporting/health.js";
6
6
  function json(response, statusCode, body) {
7
7
  const payload = JSON.stringify(body);
8
8
  response.writeHead(statusCode, {
@@ -1,4 +1,4 @@
1
- import { evaluatePackage } from "./evaluate.js";
1
+ import { evaluatePackage, } from "../discovery/evaluate.js";
2
2
  /**
3
3
  * Run the policy gate for a candidate without mutating agent configuration.
4
4
  * Promotion is deliberately injected so callers can connect it to their
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { join } from "node:path";
4
- import { loadoutHome } from "./paths.js";
4
+ import { loadoutHome } from "../agents/paths.js";
5
5
  function normalizeRepository(input) {
6
6
  const value = input
7
7
  .trim()
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
- import { createCredentialResolver } from "./credentials.js";
4
+ import { createCredentialResolver } from "../routing/credentials.js";
5
5
  import { planMcpConfig } from "./mcp.js";
6
6
  /**
7
7
  * Small, source-linked recipes. These configure a connection only; authorizing
@@ -1,8 +1,8 @@
1
1
  import { readFile, readdir, mkdir, rename, writeFile, rm, } from "node:fs/promises";
2
2
  import { join, resolve, dirname, basename } from "node:path";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
- import { loadoutHome } from "./paths.js";
5
- import { runMutationTransaction, withMutationLock } from "./transaction.js";
4
+ import { loadoutHome } from "../agents/paths.js";
5
+ import { runMutationTransaction, withMutationLock, } from "../install/transaction.js";
6
6
  const missingConfigMarker = "loadout:mcp-config:missing";
7
7
  async function configFingerprint(path) {
8
8
  let content;
@@ -2,10 +2,10 @@ import { execFile } from "node:child_process";
2
2
  import { rm } from "node:fs/promises";
3
3
  import { dirname, win32, join } from "node:path";
4
4
  import { promisify } from "node:util";
5
- import { writeFileAtomically } from "./atomic-file.js";
6
- import { ensureDirectory, loadoutHome, userHome } from "./paths.js";
7
- import { createSnapshot, recordSnapshotPostMutationState } from "./snapshot.js";
8
- import { beginTransaction, completeTransaction, markTransactionCommitting, recoverPendingTransactions, rollbackTransaction, } from "./transaction.js";
5
+ import { writeFileAtomically } from "../install/atomic-file.js";
6
+ import { ensureDirectory, loadoutHome, userHome } from "../agents/paths.js";
7
+ import { createSnapshot, recordSnapshotPostMutationState, } from "../install/snapshot.js";
8
+ import { beginTransaction, completeTransaction, markTransactionCommitting, recoverPendingTransactions, rollbackTransaction, } from "../install/transaction.js";
9
9
  const execFileAsync = promisify(execFile);
10
10
  function parseTime(value) {
11
11
  const match = /^(\d{2}):(\d{2})$/.exec(value);
@@ -1,8 +1,8 @@
1
1
  import { applyActivationChange, formatActivationPlan, planActivationChange, } from "./active-set.js";
2
2
  import { DEFAULT_ACTIVE_SKILL_LIMIT } from "./active-limit.js";
3
- import { detectAgents } from "./paths.js";
3
+ import { detectAgents } from "../agents/paths.js";
4
4
  import { formatDetectedSignals, scanProject } from "./recommend.js";
5
- import { scanInstalledSkills } from "./skill-inventory.js";
5
+ import { scanInstalledSkills } from "../catalog/skill-inventory.js";
6
6
  import { readInstallState } from "./state.js";
7
7
  import { outcomeAdjustment, projectTaskFamilies, readLocalOutcomes, } from "./outcomes.js";
8
8
  const FOUNDATION = new Set([
@@ -1,10 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { cp, lstat, mkdir, readFile, readdir, rm } from "node:fs/promises";
3
3
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
- import { agentSkillsDirectory, ensureDirectory } from "./paths.js";
4
+ import { agentSkillsDirectory, ensureDirectory } from "../agents/paths.js";
5
5
  import { activationLibraryPath, installStatePath, readInstallState, writeInstallState, } from "./state.js";
6
6
  import { inspectTargetOccupancy } from "./target-occupancy.js";
7
- import { runMutationTransaction } from "./transaction.js";
7
+ import { runMutationTransaction } from "../install/transaction.js";
8
8
  /** Resolve a managed file to its active location or disabled library copy. */
9
9
  export function managedFileReadPath(packageId, filePath, activations) {
10
10
  const disabled = activations.find((activation) => activation.packageId === packageId &&
@@ -1,9 +1,9 @@
1
- import { buildHealthReport } from "./health.js";
1
+ import { buildHealthReport } from "../reporting/health.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
4
4
  import { join, resolve } from "node:path";
5
- import { loadoutHome } from "./paths.js";
6
- import { detectSecretKinds } from "./safety.js";
5
+ import { loadoutHome } from "../agents/paths.js";
6
+ import { detectSecretKinds } from "../catalog/safety.js";
7
7
  export function proposeImprovements(report) {
8
8
  const proposals = [];
9
9
  if (report.driftedFiles)
@@ -1,8 +1,8 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
- import { formatSchemaError, loadoutManifestSchema } from "../shared/schemas.js";
3
+ import { formatSchemaError, loadoutManifestSchema, } from "../../shared/schemas.js";
4
4
  import { readInstallState } from "./state.js";
5
- import { writeFileAtomically } from "./atomic-file.js";
5
+ import { writeFileAtomically } from "../install/atomic-file.js";
6
6
  const AGENTS = new Set([
7
7
  "claude-code",
8
8
  "codex",
@@ -1,9 +1,9 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { dirname, join } from "node:path";
4
- import { writeFileAtomically } from "./atomic-file.js";
5
- import { ensureDirectory, loadoutHome } from "./paths.js";
6
- import { withFileLock } from "./file-lock.js";
4
+ import { writeFileAtomically } from "../install/atomic-file.js";
5
+ import { ensureDirectory, loadoutHome } from "../agents/paths.js";
6
+ import { withFileLock } from "../install/file-lock.js";
7
7
  const outcomePath = () => join(loadoutHome(), "outcomes.json");
8
8
  const outcomeLockPath = () => join(loadoutHome(), "outcomes.lock");
9
9
  const RESULTS = new Set([
@@ -1,9 +1,9 @@
1
1
  import { lstat, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
- import { parseLockfile, readLockfile } from "./audit.js";
3
+ import { parseLockfile, readLockfile } from "../catalog/audit.js";
4
4
  import { parseManifest, readManifest } from "./manifest.js";
5
- import { runMutationTransaction } from "./transaction.js";
6
- import { detectSecretKinds } from "./safety.js";
5
+ import { runMutationTransaction } from "../install/transaction.js";
6
+ import { detectSecretKinds } from "../catalog/safety.js";
7
7
  export function parsePortableLoadout(value) {
8
8
  if (!value || typeof value !== "object" || Array.isArray(value))
9
9
  throw new Error("Portable Loadout must be an object");
@@ -1,4 +1,4 @@
1
- import { resolveCatalogProfile } from "./profiles.js";
1
+ import { resolveCatalogProfile } from "../catalog/profiles.js";
2
2
  import { readInstallState, writeInstallState } from "./state.js";
3
3
  export async function recordInstalledProfile(prepared) {
4
4
  const profile = {
@@ -1,6 +1,6 @@
1
1
  import { readFile, readdir } from "node:fs/promises";
2
2
  import { basename, resolve } from "node:path";
3
- import { POWER_SKILL_ALLOWLIST, STABLE_BOOST_PACKAGE_IDS } from "./profiles.js";
3
+ import { POWER_SKILL_ALLOWLIST, STABLE_BOOST_PACKAGE_IDS, } from "../catalog/profiles.js";
4
4
  import { packageOutcomeAdjustment, projectTaskFamilies, } from "./outcomes.js";
5
5
  const SIGNAL_FILES = new Set([
6
6
  "package.json",
@@ -1,9 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile, readdir, stat } from "node:fs/promises";
3
3
  import { basename, join, relative, sep } from "node:path";
4
- import { formatSchemaError, installStateSchema } from "../shared/schemas.js";
5
- import { ensureDirectory, loadoutHome } from "./paths.js";
6
- import { writeFileAtomically } from "./atomic-file.js";
4
+ import { formatSchemaError, installStateSchema } from "../../shared/schemas.js";
5
+ import { ensureDirectory, loadoutHome } from "../agents/paths.js";
6
+ import { writeFileAtomically } from "../install/atomic-file.js";
7
7
  const stateFile = () => join(loadoutHome(), "state.json");
8
8
  export const installStatePath = () => stateFile();
9
9
  export async function writeInstallState(state) {
@@ -93,10 +93,16 @@ powers, platform behavior, category, and catalog policy.
93
93
 
94
94
  ## Distribute a trusted catalog release
95
95
 
96
- Maintainers sign a technically screened full catalog array with an Ed25519 key kept outside the
96
+ > **Not implemented.** The signing flow below is a design sketch. `loadout keygen`,
97
+ > `loadout catalog-sign`, and `loadout catalog-update` do not exist in the CLI
98
+ > today; the catalog ships in the package and is verified by the evidence gate
99
+ > instead. Kept here as the intended shape, not as instructions.
100
+
101
+ Maintainers would sign a technically screened full catalog array with an Ed25519 key kept outside the
97
102
  repository:
98
103
 
99
104
  ```bash
105
+ # Design sketch — these commands do not exist yet.
100
106
  loadout keygen --private-key /secure/catalog-private.pem \
101
107
  --public-key ./catalog-public.pem
102
108
  loadout catalog-sign --catalog ./catalog/packages.json \
@@ -104,9 +110,10 @@ loadout catalog-sign --catalog ./catalog/packages.json \
104
110
  --output ./catalog.signed.json
105
111
  ```
106
112
 
107
- Users preview the verified release before trusting it:
113
+ Users would preview the verified release before trusting it:
108
114
 
109
115
  ```bash
116
+ # Design sketch — these commands do not exist yet.
110
117
  loadout catalog-update --source ./catalog.signed.json \
111
118
  --public-key ./catalog-public.pem
112
119
  loadout catalog-update --source ./catalog.signed.json \
package/docs/CATALOG.md CHANGED
@@ -12,7 +12,7 @@ Thank you to every maintainer and contributor whose work appears here. Loadout d
12
12
  - **License** is the SPDX identifier recorded in `catalog/packages.json` from GitHub metadata. **Review required** corresponds to `NOASSERTION`: no SPDX identifier was reported, so users must inspect the upstream terms instead of assuming permission.
13
13
  - **Reviewed revision** links to the immutable commit inspected for catalog admission. It is not necessarily the newest upstream commit.
14
14
 
15
- The machine-readable catalog remains the source of truth. Run `loadout catalog --coverage --json` to inspect coverage and `loadout capabilities` to inspect what each local agent adapter can actually manage.
15
+ The machine-readable catalog remains the source of truth. Run `loadout catalog --coverage --json` to inspect coverage and `loadout doctor --verbose` to inspect what each local agent adapter can actually manage.
16
16
 
17
17
  ## All 53 credited repositories
18
18
 
@@ -26,6 +26,6 @@ verification restores the snapshot and quarantines the candidate commit. A user
26
26
  revoke a policy at any time; revocation prevents future mutations but never deletes
27
27
  existing snapshots.
28
28
 
29
- The local `loadout canary` command implements the non-mutating static gate. A
29
+ Update safety runs as a non-mutating static gate during `loadout upgrade` previews. A
30
30
  transaction layer must provide verification and promotion callbacks before a
31
31
  candidate can be promoted; the command itself never installs a candidate.