loadout-ai 0.7.0 → 0.9.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 (108) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/README.md +160 -315
  3. package/SECURITY.md +21 -1
  4. package/catalog/discovered.json +31269 -26003
  5. package/catalog/packages.json +4 -4
  6. package/dist/src/cli.js +13 -0
  7. package/dist/src/commands/agents.js +3 -1
  8. package/dist/src/commands/catalog-candidate.js +150 -0
  9. package/dist/src/commands/catalog-workflows.js +147 -0
  10. package/dist/src/commands/catalog.js +9 -367
  11. package/dist/src/commands/coordinate.js +586 -0
  12. package/dist/src/commands/coordination-discussions.js +194 -0
  13. package/dist/src/commands/coordination-sessions.js +197 -0
  14. package/dist/src/commands/inventory.js +4 -2
  15. package/dist/src/core/agents/agent-inspection.js +26 -4
  16. package/dist/src/core/{routing → agents}/model-config.js +7 -2
  17. package/dist/src/core/catalog/catalog.js +1 -0
  18. package/dist/src/core/catalog/registry.js +44 -10
  19. package/dist/src/core/catalog/safety.js +36 -7
  20. package/dist/src/core/coordination/adapters/claude-code.js +154 -0
  21. package/dist/src/core/coordination/adapters/codex.js +137 -0
  22. package/dist/src/core/coordination/adapters/types.js +64 -0
  23. package/dist/src/core/coordination/auth.js +59 -0
  24. package/dist/src/core/coordination/bridge-lease.js +79 -0
  25. package/dist/src/core/coordination/conflict-preview.js +167 -0
  26. package/dist/src/core/coordination/contract-diff.js +106 -0
  27. package/dist/src/core/coordination/coordinator.js +552 -0
  28. package/dist/src/core/coordination/crash-recovery.js +169 -0
  29. package/dist/src/core/coordination/daemon.js +667 -0
  30. package/dist/src/core/coordination/discussion.js +340 -0
  31. package/dist/src/core/coordination/events.js +161 -0
  32. package/dist/src/core/coordination/http-api.js +118 -0
  33. package/dist/src/core/coordination/interrupt-policy.js +89 -0
  34. package/dist/src/core/coordination/lock.js +110 -0
  35. package/dist/src/core/coordination/mcp-server.js +424 -0
  36. package/dist/src/core/coordination/redaction.js +85 -0
  37. package/dist/src/core/coordination/replay.js +188 -0
  38. package/dist/src/core/coordination/retention.js +128 -0
  39. package/dist/src/core/coordination/runtime.js +21 -0
  40. package/dist/src/core/coordination/session-manager.js +366 -0
  41. package/dist/src/core/coordination/watcher.js +128 -0
  42. package/dist/src/core/{routing → delegation}/first-party-skills.js +25 -13
  43. package/dist/src/core/{routing → delegation}/handoff.js +130 -63
  44. package/dist/src/core/discovery/candidate-intelligence-evidence.js +108 -0
  45. package/dist/src/core/discovery/candidate-intelligence-types.js +1 -0
  46. package/dist/src/core/discovery/candidate-intelligence-validation.js +108 -0
  47. package/dist/src/core/discovery/candidate-intelligence.js +3 -214
  48. package/dist/src/core/discovery/community.js +12 -4
  49. package/dist/src/core/discovery/github-discovery.js +6 -2
  50. package/dist/src/core/discovery/private-discovery.js +6 -2
  51. package/dist/src/core/install/reconcile.js +1 -1
  52. package/dist/src/core/install/source.js +21 -7
  53. package/dist/src/core/install/uninstall.js +39 -3
  54. package/dist/src/core/reporting/cli-guide.js +10 -6
  55. package/dist/src/core/reporting/completion.js +61 -109
  56. package/dist/src/core/reporting/doctor.js +4 -6
  57. package/dist/src/core/runtime/bounded-json.js +65 -0
  58. package/dist/src/core/runtime/github.js +30 -16
  59. package/dist/src/core/runtime/mcp-recipes.js +1 -1
  60. package/dist/src/core/workspace/active-policy.js +1 -1
  61. package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
  62. package/docs/CATALOG.md +1 -1
  63. package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
  64. package/docs/DEMO_SCRIPT.md +19 -23
  65. package/docs/DISCOVERED.md +251 -250
  66. package/docs/FEATURE_TEST_MATRIX.md +10 -263
  67. package/docs/GITHUB_AUTHORIZATION.md +5 -0
  68. package/docs/LIVE_COLLABORATION.md +234 -0
  69. package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
  70. package/docs/REFERENCE.md +163 -0
  71. package/docs/RELEASE_REVIEW.md +1 -2
  72. package/docs/TESTING.md +17 -1
  73. package/docs/USER_TEST_GUIDE.md +138 -3
  74. package/docs/assets/loadout-discover-activate.webp +0 -0
  75. package/docs/assets/loadout-handoff-coordinate.webp +0 -0
  76. package/docs/assets/loadout-social-preview.png +0 -0
  77. package/docs/decisions/001-coordination-jsonl-locking.md +35 -0
  78. package/docs/decisions/002-local-daemon-authentication.md +32 -0
  79. package/docs/decisions/003-bounded-agent-discussions.md +91 -0
  80. package/docs/specs/BOUNDED_AGENT_DISCUSSIONS.md +169 -0
  81. package/docs/superpowers/plans/2026-09-03-coordination-hardening.md +231 -0
  82. package/docs/superpowers/plans/2026-09-03-release-readiness.md +232 -0
  83. package/docs/superpowers/plans/2026-09-04-bounded-agent-discussions.md +121 -0
  84. package/package.json +18 -7
  85. package/skills/loadout-handoff/SKILL.md +238 -0
  86. package/MASTER_PLAN.md +0 -2207
  87. package/dist/src/core/routing/route.js +0 -539
  88. package/docs/ACTIVE_SET.md +0 -53
  89. package/docs/COMPATIBILITY_POLICY.md +0 -22
  90. package/docs/CONVERSION_AND_SANDBOX.md +0 -27
  91. package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
  92. package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
  93. package/docs/PROVIDER_CONFIGURATION.md +0 -45
  94. package/docs/README_RESEARCH.md +0 -36
  95. package/docs/REPOSITORY_STABILIZATION.md +0 -190
  96. package/docs/SAFE_UPDATE_DEMO.md +0 -25
  97. package/docs/SCHEMA_DECISIONS.md +0 -25
  98. package/docs/SUBMISSION_COPY.md +0 -90
  99. package/docs/TEAM_POLICY.md +0 -18
  100. package/docs/assets/loadout-workflow.png +0 -0
  101. package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
  102. package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
  103. package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
  104. package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
  105. package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
  106. package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
  107. package/skills/loadout-router/SKILL.md +0 -120
  108. /package/dist/src/core/{routing → agents}/credentials.js +0 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Conflict preview — detect and show what another agent changed in files
3
+ * you're about to write, before you write them.
4
+ *
5
+ * Uses the ownership map + git diff to surface inter-agent conflicts
6
+ * before they become merge problems.
7
+ */
8
+ import { execFile } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ import { getOwnership } from "./coordinator.js";
11
+ const exec = promisify(execFile);
12
+ /**
13
+ * Preview conflicts before writing to files.
14
+ *
15
+ * Checks the ownership map and git status to show what another agent
16
+ * has changed in files you want to write. Call this before claiming
17
+ * ownership or writing.
18
+ */
19
+ export async function previewConflicts(projectRoot, agent, paths) {
20
+ const ownership = await getOwnership(projectRoot);
21
+ const conflicts = [];
22
+ const safe = [];
23
+ for (const path of paths) {
24
+ // Check if any ownership claim covers this path
25
+ let conflicting = null;
26
+ for (const claim of ownership.values()) {
27
+ if (claim.agent === agent)
28
+ continue; // Skip own claims
29
+ for (const ownedPath of claim.paths) {
30
+ if (pathOverlaps(path, ownedPath)) {
31
+ conflicting = { agent: claim.agent, mode: claim.mode };
32
+ break;
33
+ }
34
+ }
35
+ if (conflicting)
36
+ break;
37
+ }
38
+ if (!conflicting) {
39
+ safe.push(path);
40
+ continue;
41
+ }
42
+ // Get git diff for what the other agent changed
43
+ const { diff, linesChanged, status } = await getGitDiff(projectRoot, path);
44
+ conflicts.push({
45
+ path,
46
+ owner: conflicting.agent,
47
+ ownerMode: conflicting.mode,
48
+ diff,
49
+ linesChanged,
50
+ status,
51
+ });
52
+ }
53
+ return {
54
+ agent,
55
+ requestedPaths: paths,
56
+ conflicts,
57
+ safe,
58
+ timestamp: new Date().toISOString(),
59
+ };
60
+ }
61
+ /**
62
+ * Format conflict preview for terminal display.
63
+ */
64
+ export function formatConflictPreview(preview) {
65
+ const lines = [];
66
+ if (preview.conflicts.length === 0) {
67
+ lines.push(`\x1b[32m✓\x1b[0m No conflicts — ${preview.safe.length} path(s) safe to write.`);
68
+ return lines.join("\n");
69
+ }
70
+ lines.push(`\x1b[33m⚠ ${preview.conflicts.length} conflict(s) detected\x1b[0m`);
71
+ lines.push("");
72
+ for (const c of preview.conflicts) {
73
+ const modeIcon = c.ownerMode === "exclusive" ? "🔒" : "🔓";
74
+ lines.push(`${modeIcon} \x1b[1m${c.path}\x1b[0m — owned by \x1b[36m${c.owner}\x1b[0m (${c.ownerMode})`);
75
+ if (c.status !== "unchanged") {
76
+ lines.push(` ${c.status === "added" ? "➕" : c.status === "deleted" ? "➖" : "✏️"} ${c.linesChanged} line(s) ${c.status}`);
77
+ }
78
+ if (c.diff) {
79
+ // Show first 20 lines of diff
80
+ const diffLines = c.diff.split("\n").slice(0, 20);
81
+ for (const dl of diffLines) {
82
+ if (dl.startsWith("+") && !dl.startsWith("+++")) {
83
+ lines.push(` \x1b[32m${dl}\x1b[0m`);
84
+ }
85
+ else if (dl.startsWith("-") && !dl.startsWith("---")) {
86
+ lines.push(` \x1b[31m${dl}\x1b[0m`);
87
+ }
88
+ else if (dl.startsWith("@@")) {
89
+ lines.push(` \x1b[36m${dl}\x1b[0m`);
90
+ }
91
+ }
92
+ if (c.diff.split("\n").length > 20) {
93
+ lines.push(` ... (${c.diff.split("\n").length - 20} more lines)`);
94
+ }
95
+ }
96
+ lines.push("");
97
+ }
98
+ if (preview.safe.length > 0) {
99
+ lines.push(`\x1b[32m✓\x1b[0m ${preview.safe.length} path(s) safe: ${preview.safe.join(", ")}`);
100
+ }
101
+ return lines.join("\n");
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Helpers
105
+ // ---------------------------------------------------------------------------
106
+ function pathOverlaps(requested, owned) {
107
+ // Normalize trailing slashes
108
+ const r = requested.replace(/\/$/, "");
109
+ const o = owned.replace(/\/$/, "");
110
+ // Exact match
111
+ if (r === o)
112
+ return true;
113
+ // Owned is a directory containing the requested path
114
+ if (r.startsWith(o + "/"))
115
+ return true;
116
+ // Requested is a directory containing the owned path
117
+ if (o.startsWith(r + "/"))
118
+ return true;
119
+ return false;
120
+ }
121
+ async function getGitDiff(projectRoot, path) {
122
+ try {
123
+ // Check if path has uncommitted changes
124
+ const { stdout: statusOut } = await exec("git", ["status", "--porcelain", "--", path], { cwd: projectRoot, timeout: 5000 });
125
+ if (!statusOut.trim()) {
126
+ // Check for committed changes vs main
127
+ try {
128
+ const { stdout: diffOut } = await exec("git", ["diff", "HEAD~5..HEAD", "--", path], { cwd: projectRoot, timeout: 5000 });
129
+ if (diffOut.trim()) {
130
+ const added = (diffOut.match(/^\+[^+]/gm) ?? []).length;
131
+ const removed = (diffOut.match(/^-[^-]/gm) ?? []).length;
132
+ return {
133
+ diff: diffOut,
134
+ linesChanged: added + removed,
135
+ status: "modified",
136
+ };
137
+ }
138
+ }
139
+ catch {
140
+ // Not enough history
141
+ }
142
+ return { diff: "", linesChanged: 0, status: "unchanged" };
143
+ }
144
+ const statusCode = statusOut.trim().slice(0, 2);
145
+ if (statusCode.includes("A") || statusCode === "??") {
146
+ return { diff: "", linesChanged: 0, status: "added" };
147
+ }
148
+ if (statusCode.includes("D")) {
149
+ return { diff: "", linesChanged: 0, status: "deleted" };
150
+ }
151
+ // Get the actual diff
152
+ const { stdout: diffOut } = await exec("git", ["diff", "--", path], {
153
+ cwd: projectRoot,
154
+ timeout: 5000,
155
+ });
156
+ const added = (diffOut.match(/^\+[^+]/gm) ?? []).length;
157
+ const removed = (diffOut.match(/^-[^-]/gm) ?? []).length;
158
+ return {
159
+ diff: diffOut,
160
+ linesChanged: added + removed,
161
+ status: "modified",
162
+ };
163
+ }
164
+ catch {
165
+ return { diff: "", linesChanged: 0, status: "unchanged" };
166
+ }
167
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Contract diffing — when a contract revision bumps, auto-generate
3
+ * a structured delta showing exactly what changed between revisions.
4
+ */
5
+ import { readCoordLog } from "./coordinator.js";
6
+ /**
7
+ * Get all revisions of a named contract from the log.
8
+ */
9
+ export async function getContractHistory(projectRoot, name) {
10
+ const log = await readCoordLog(projectRoot);
11
+ const revisions = [];
12
+ for (const event of log.events) {
13
+ if (event.type !== "contract" || !event.payload)
14
+ continue;
15
+ const payload = event.payload;
16
+ if (payload.name !== name)
17
+ continue;
18
+ revisions.push({
19
+ name: payload.name,
20
+ revision: payload.revision,
21
+ body: payload.body,
22
+ format: payload.format,
23
+ publisher: event.from,
24
+ eventId: event.id,
25
+ seq: event.seq,
26
+ timestamp: event.timestamp,
27
+ });
28
+ }
29
+ return revisions.sort((a, b) => a.revision - b.revision);
30
+ }
31
+ /**
32
+ * Diff two contract revisions and produce a structured delta.
33
+ */
34
+ export function diffContracts(from, to) {
35
+ const fromLines = from.body.split("\n");
36
+ const toLines = to.body.split("\n");
37
+ const fromSet = new Set(fromLines);
38
+ const toSet = new Set(toLines);
39
+ const added = toLines.filter((l) => !fromSet.has(l) && l.trim());
40
+ const removed = fromLines.filter((l) => !toSet.has(l) && l.trim());
41
+ // Line-by-line comparison for changed lines at same positions
42
+ const changed = [];
43
+ const maxLines = Math.min(fromLines.length, toLines.length);
44
+ for (let i = 0; i < maxLines; i++) {
45
+ if (fromLines[i] !== toLines[i]) {
46
+ changed.push({ line: i + 1, from: fromLines[i], to: toLines[i] });
47
+ }
48
+ }
49
+ // Summary
50
+ const parts = [];
51
+ if (added.length)
52
+ parts.push(`${added.length} added`);
53
+ if (removed.length)
54
+ parts.push(`${removed.length} removed`);
55
+ if (changed.length)
56
+ parts.push(`${changed.length} changed`);
57
+ const summary = parts.length
58
+ ? `${parts.join(", ")} line(s)`
59
+ : "No differences";
60
+ return {
61
+ name: from.name,
62
+ fromRevision: from.revision,
63
+ toRevision: to.revision,
64
+ fromPublisher: from.publisher,
65
+ toPublisher: to.publisher,
66
+ added,
67
+ removed,
68
+ changed,
69
+ summary,
70
+ };
71
+ }
72
+ /**
73
+ * Diff the latest two revisions of a named contract.
74
+ */
75
+ export async function diffLatestContract(projectRoot, name) {
76
+ const history = await getContractHistory(projectRoot, name);
77
+ if (history.length < 2)
78
+ return null;
79
+ return diffContracts(history[history.length - 2], history[history.length - 1]);
80
+ }
81
+ /**
82
+ * Format a contract delta for terminal display.
83
+ */
84
+ export function formatContractDelta(delta) {
85
+ const lines = [];
86
+ lines.push(`\x1b[1m${delta.name}\x1b[0m rev${delta.fromRevision} → rev${delta.toRevision}`);
87
+ lines.push(` by ${delta.fromPublisher} → ${delta.toPublisher} · ${delta.summary}`);
88
+ lines.push("");
89
+ if (delta.removed.length) {
90
+ for (const r of delta.removed.slice(0, 30)) {
91
+ lines.push(`\x1b[31m- ${r}\x1b[0m`);
92
+ }
93
+ if (delta.removed.length > 30) {
94
+ lines.push(` ... ${delta.removed.length - 30} more removed`);
95
+ }
96
+ }
97
+ if (delta.added.length) {
98
+ for (const a of delta.added.slice(0, 30)) {
99
+ lines.push(`\x1b[32m+ ${a}\x1b[0m`);
100
+ }
101
+ if (delta.added.length > 30) {
102
+ lines.push(` ... ${delta.added.length - 30} more added`);
103
+ }
104
+ }
105
+ return lines.join("\n");
106
+ }