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.
- package/CHANGELOG.md +148 -1
- package/README.md +160 -315
- package/SECURITY.md +21 -1
- package/catalog/discovered.json +31269 -26003
- package/catalog/packages.json +4 -4
- package/dist/src/cli.js +13 -0
- package/dist/src/commands/agents.js +3 -1
- package/dist/src/commands/catalog-candidate.js +150 -0
- package/dist/src/commands/catalog-workflows.js +147 -0
- package/dist/src/commands/catalog.js +9 -367
- package/dist/src/commands/coordinate.js +586 -0
- package/dist/src/commands/coordination-discussions.js +194 -0
- package/dist/src/commands/coordination-sessions.js +197 -0
- package/dist/src/commands/inventory.js +4 -2
- package/dist/src/core/agents/agent-inspection.js +26 -4
- package/dist/src/core/{routing → agents}/model-config.js +7 -2
- package/dist/src/core/catalog/catalog.js +1 -0
- package/dist/src/core/catalog/registry.js +44 -10
- package/dist/src/core/catalog/safety.js +36 -7
- package/dist/src/core/coordination/adapters/claude-code.js +154 -0
- package/dist/src/core/coordination/adapters/codex.js +137 -0
- package/dist/src/core/coordination/adapters/types.js +64 -0
- package/dist/src/core/coordination/auth.js +59 -0
- package/dist/src/core/coordination/bridge-lease.js +79 -0
- package/dist/src/core/coordination/conflict-preview.js +167 -0
- package/dist/src/core/coordination/contract-diff.js +106 -0
- package/dist/src/core/coordination/coordinator.js +552 -0
- package/dist/src/core/coordination/crash-recovery.js +169 -0
- package/dist/src/core/coordination/daemon.js +667 -0
- package/dist/src/core/coordination/discussion.js +340 -0
- package/dist/src/core/coordination/events.js +161 -0
- package/dist/src/core/coordination/http-api.js +118 -0
- package/dist/src/core/coordination/interrupt-policy.js +89 -0
- package/dist/src/core/coordination/lock.js +110 -0
- package/dist/src/core/coordination/mcp-server.js +424 -0
- package/dist/src/core/coordination/redaction.js +85 -0
- package/dist/src/core/coordination/replay.js +188 -0
- package/dist/src/core/coordination/retention.js +128 -0
- package/dist/src/core/coordination/runtime.js +21 -0
- package/dist/src/core/coordination/session-manager.js +366 -0
- package/dist/src/core/coordination/watcher.js +128 -0
- package/dist/src/core/{routing → delegation}/first-party-skills.js +25 -13
- package/dist/src/core/{routing → delegation}/handoff.js +130 -63
- package/dist/src/core/discovery/candidate-intelligence-evidence.js +108 -0
- package/dist/src/core/discovery/candidate-intelligence-types.js +1 -0
- package/dist/src/core/discovery/candidate-intelligence-validation.js +108 -0
- package/dist/src/core/discovery/candidate-intelligence.js +3 -214
- package/dist/src/core/discovery/community.js +12 -4
- package/dist/src/core/discovery/github-discovery.js +6 -2
- package/dist/src/core/discovery/private-discovery.js +6 -2
- package/dist/src/core/install/reconcile.js +1 -1
- package/dist/src/core/install/source.js +21 -7
- package/dist/src/core/install/uninstall.js +39 -3
- package/dist/src/core/reporting/cli-guide.js +10 -6
- package/dist/src/core/reporting/completion.js +61 -109
- package/dist/src/core/reporting/doctor.js +4 -6
- package/dist/src/core/runtime/bounded-json.js +65 -0
- package/dist/src/core/runtime/github.js +30 -16
- package/dist/src/core/runtime/mcp-recipes.js +1 -1
- package/dist/src/core/workspace/active-policy.js +1 -1
- package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
- package/docs/CATALOG.md +1 -1
- package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
- package/docs/DEMO_SCRIPT.md +19 -23
- package/docs/DISCOVERED.md +251 -250
- package/docs/FEATURE_TEST_MATRIX.md +10 -263
- package/docs/GITHUB_AUTHORIZATION.md +5 -0
- package/docs/LIVE_COLLABORATION.md +234 -0
- package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
- package/docs/REFERENCE.md +163 -0
- package/docs/RELEASE_REVIEW.md +1 -2
- package/docs/TESTING.md +17 -1
- package/docs/USER_TEST_GUIDE.md +138 -3
- package/docs/assets/loadout-discover-activate.webp +0 -0
- package/docs/assets/loadout-handoff-coordinate.webp +0 -0
- package/docs/assets/loadout-social-preview.png +0 -0
- package/docs/decisions/001-coordination-jsonl-locking.md +35 -0
- package/docs/decisions/002-local-daemon-authentication.md +32 -0
- package/docs/decisions/003-bounded-agent-discussions.md +91 -0
- package/docs/specs/BOUNDED_AGENT_DISCUSSIONS.md +169 -0
- package/docs/superpowers/plans/2026-09-03-coordination-hardening.md +231 -0
- package/docs/superpowers/plans/2026-09-03-release-readiness.md +232 -0
- package/docs/superpowers/plans/2026-09-04-bounded-agent-discussions.md +121 -0
- package/package.json +18 -7
- package/skills/loadout-handoff/SKILL.md +238 -0
- package/MASTER_PLAN.md +0 -2207
- package/dist/src/core/routing/route.js +0 -539
- package/docs/ACTIVE_SET.md +0 -53
- package/docs/COMPATIBILITY_POLICY.md +0 -22
- package/docs/CONVERSION_AND_SANDBOX.md +0 -27
- package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
- package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
- package/docs/PROVIDER_CONFIGURATION.md +0 -45
- package/docs/README_RESEARCH.md +0 -36
- package/docs/REPOSITORY_STABILIZATION.md +0 -190
- package/docs/SAFE_UPDATE_DEMO.md +0 -25
- package/docs/SCHEMA_DECISIONS.md +0 -25
- package/docs/SUBMISSION_COPY.md +0 -90
- package/docs/TEAM_POLICY.md +0 -18
- package/docs/assets/loadout-workflow.png +0 -0
- package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
- package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
- package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
- package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
- package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
- package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
- package/skills/loadout-router/SKILL.md +0 -120
- /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
|
+
}
|