infernoflow 0.33.0 → 0.34.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/README.md +208 -120
- package/dist/bin/infernoflow.mjs +271 -85
- package/dist/lib/adopters/angular.mjs +128 -1
- package/dist/lib/adopters/css.mjs +111 -1
- package/dist/lib/adopters/react.mjs +104 -1
- package/dist/lib/ai/ideDetection.mjs +31 -1
- package/dist/lib/ai/localProvider.mjs +88 -1
- package/dist/lib/ai/providerRouter.mjs +295 -2
- package/dist/lib/commands/adopt.mjs +869 -20
- package/dist/lib/commands/adoptWizard.mjs +320 -9
- package/dist/lib/commands/agent.mjs +191 -5
- package/dist/lib/commands/ai.mjs +407 -2
- package/dist/lib/commands/ask.mjs +299 -0
- package/dist/lib/commands/audit.mjs +300 -13
- package/dist/lib/commands/changelog.mjs +594 -26
- package/dist/lib/commands/check.mjs +184 -3
- package/dist/lib/commands/ci.mjs +208 -3
- package/dist/lib/commands/claudeMd.mjs +139 -28
- package/dist/lib/commands/cloud.mjs +521 -5
- package/dist/lib/commands/context.mjs +346 -34
- package/dist/lib/commands/coverage.mjs +282 -2
- package/dist/lib/commands/dashboard.mjs +635 -123
- package/dist/lib/commands/demo.mjs +465 -8
- package/dist/lib/commands/diff.mjs +274 -5
- package/dist/lib/commands/docGate.mjs +81 -2
- package/dist/lib/commands/doctor.mjs +321 -3
- package/dist/lib/commands/explain.mjs +438 -8
- package/dist/lib/commands/export.mjs +239 -10
- package/dist/lib/commands/generateSkills.mjs +163 -38
- package/dist/lib/commands/graph.mjs +378 -11
- package/dist/lib/commands/health.mjs +309 -2
- package/dist/lib/commands/impact.mjs +325 -2
- package/dist/lib/commands/implement.mjs +103 -7
- package/dist/lib/commands/init.mjs +545 -23
- package/dist/lib/commands/installCursorHooks.mjs +36 -1
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +37 -1
- package/dist/lib/commands/link.mjs +342 -2
- package/dist/lib/commands/log.mjs +164 -16
- package/dist/lib/commands/monorepo.mjs +428 -4
- package/dist/lib/commands/notify.mjs +258 -4
- package/dist/lib/commands/onboard.mjs +296 -4
- package/dist/lib/commands/prComment.mjs +361 -2
- package/dist/lib/commands/prImpact.mjs +157 -2
- package/dist/lib/commands/publish.mjs +316 -15
- package/dist/lib/commands/recap.mjs +359 -0
- package/dist/lib/commands/report.mjs +272 -28
- package/dist/lib/commands/review.mjs +223 -9
- package/dist/lib/commands/run.mjs +336 -8
- package/dist/lib/commands/scaffold.mjs +419 -54
- package/dist/lib/commands/scan.mjs +1118 -5
- package/dist/lib/commands/scout.mjs +291 -2
- package/dist/lib/commands/setup.mjs +310 -5
- package/dist/lib/commands/share.mjs +196 -13
- package/dist/lib/commands/snapshot.mjs +383 -3
- package/dist/lib/commands/stability.mjs +293 -2
- package/dist/lib/commands/stats.mjs +402 -0
- package/dist/lib/commands/status.mjs +172 -4
- package/dist/lib/commands/suggest.mjs +563 -21
- package/dist/lib/commands/switch.mjs +310 -0
- package/dist/lib/commands/syncAuto.mjs +96 -1
- package/dist/lib/commands/synthesize.mjs +228 -10
- package/dist/lib/commands/teamSync.mjs +388 -2
- package/dist/lib/commands/test.mjs +363 -6
- package/dist/lib/commands/theme.mjs +195 -18
- package/dist/lib/commands/upgrade.mjs +153 -0
- package/dist/lib/commands/version.mjs +282 -2
- package/dist/lib/commands/vibe.mjs +357 -7
- package/dist/lib/commands/watch.mjs +203 -4
- package/dist/lib/commands/why.mjs +358 -4
- package/dist/lib/cursorHooksInstall.mjs +60 -1
- package/dist/lib/draftToolingInstall.mjs +68 -7
- package/dist/lib/git/detect-drift.mjs +208 -4
- package/dist/lib/learning/adapt.mjs +101 -6
- package/dist/lib/learning/observe.mjs +119 -1
- package/dist/lib/learning/patternDetector.mjs +298 -1
- package/dist/lib/learning/profile.mjs +279 -2
- package/dist/lib/learning/skillSynthesizer.mjs +145 -24
- package/dist/lib/templates/index.mjs +131 -1
- package/dist/lib/theme/scanner.mjs +343 -4
- package/dist/lib/ui/errors.mjs +142 -1
- package/dist/lib/ui/output.mjs +72 -6
- package/dist/lib/ui/prompts.mjs +147 -6
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +42 -1
- package/package.json +1 -1
|
@@ -1,2 +1,388 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* infernoflow team-sync
|
|
3
|
+
*
|
|
4
|
+
* Shared capability contract sync across a team.
|
|
5
|
+
* Uses a dedicated git branch (`inferno-contracts`) as the source of truth.
|
|
6
|
+
*
|
|
7
|
+
* Sub-commands:
|
|
8
|
+
* infernoflow team-sync push — push local contract to shared branch
|
|
9
|
+
* infernoflow team-sync pull — pull shared contract, detect conflicts
|
|
10
|
+
* infernoflow team-sync status — show diff between local and shared
|
|
11
|
+
* infernoflow team-sync init — create the shared branch if it doesn't exist
|
|
12
|
+
*
|
|
13
|
+
* Flags:
|
|
14
|
+
* --branch <name> Shared branch name (default: inferno-contracts)
|
|
15
|
+
* --remote <name> Git remote (default: origin)
|
|
16
|
+
* --json Machine-readable output
|
|
17
|
+
* --force Overwrite conflicts without prompting
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from "node:fs";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { execSync } from "node:child_process";
|
|
23
|
+
import { header, ok, warn, info, done, bold, cyan, gray, green, red, yellow } from "../ui/output.mjs";
|
|
24
|
+
|
|
25
|
+
// ── git helpers ───────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
function capture(cmd, cwd) {
|
|
28
|
+
try {
|
|
29
|
+
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
30
|
+
} catch { return null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function run(cmd, cwd) {
|
|
34
|
+
execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function currentBranch(cwd) {
|
|
38
|
+
return capture("git rev-parse --abbrev-ref HEAD", cwd) || "HEAD";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function currentUser(cwd) {
|
|
42
|
+
return capture("git config user.name", cwd) || capture("git config user.email", cwd) || "unknown";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasRemote(remote, cwd) {
|
|
46
|
+
return !!capture(`git remote get-url ${remote}`, cwd);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function branchExistsRemote(remote, branch, cwd) {
|
|
50
|
+
return !!capture(`git ls-remote --heads ${remote} refs/heads/${branch}`, cwd);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── capability helpers ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function parseCaps(jsonText) {
|
|
56
|
+
if (!jsonText) return [];
|
|
57
|
+
try {
|
|
58
|
+
const obj = JSON.parse(jsonText);
|
|
59
|
+
const raw = obj.capabilities || [];
|
|
60
|
+
return raw.map(c => typeof c === "string" ? { id: c, title: c } : c);
|
|
61
|
+
} catch { return []; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function capsToMap(caps) {
|
|
65
|
+
return new Map(caps.map(c => [c.id, c]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function detectConflicts(local, shared, base) {
|
|
69
|
+
// A conflict occurs when BOTH local and shared changed the same capability
|
|
70
|
+
// since the last sync (base).
|
|
71
|
+
const localMap = capsToMap(local);
|
|
72
|
+
const sharedMap = capsToMap(shared);
|
|
73
|
+
const baseMap = capsToMap(base);
|
|
74
|
+
|
|
75
|
+
const conflicts = [];
|
|
76
|
+
const localOnly = [];
|
|
77
|
+
const sharedOnly = [];
|
|
78
|
+
|
|
79
|
+
const allIds = new Set([...localMap.keys(), ...sharedMap.keys(), ...baseMap.keys()]);
|
|
80
|
+
|
|
81
|
+
for (const id of allIds) {
|
|
82
|
+
const localCap = localMap.get(id);
|
|
83
|
+
const sharedCap = sharedMap.get(id);
|
|
84
|
+
const baseCap = baseMap.get(id);
|
|
85
|
+
|
|
86
|
+
const localChanged = JSON.stringify(localCap) !== JSON.stringify(baseCap);
|
|
87
|
+
const sharedChanged = JSON.stringify(sharedCap) !== JSON.stringify(baseCap);
|
|
88
|
+
|
|
89
|
+
if (localChanged && sharedChanged && JSON.stringify(localCap) !== JSON.stringify(sharedCap)) {
|
|
90
|
+
conflicts.push({ id, local: localCap, shared: sharedCap, base: baseCap });
|
|
91
|
+
} else if (localCap && !sharedCap && !baseCap) {
|
|
92
|
+
localOnly.push(localCap); // added locally, not in shared yet
|
|
93
|
+
} else if (!localCap && sharedCap && !baseCap) {
|
|
94
|
+
sharedOnly.push(sharedCap); // added in shared, not locally yet
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { conflicts, localOnly, sharedOnly };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── shared branch operations ──────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
function readContractFromBranch(remote, branch, cwd) {
|
|
104
|
+
// Fetch the branch first
|
|
105
|
+
try { run(`git fetch ${remote} ${branch} --quiet`, cwd); } catch {}
|
|
106
|
+
|
|
107
|
+
const content = capture(`git show ${remote}/${branch}:inferno/contract.json`, cwd);
|
|
108
|
+
if (!content) return null;
|
|
109
|
+
try { return JSON.parse(content); } catch { return null; }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readLastSyncBase(infernoDir) {
|
|
113
|
+
const basePath = path.join(infernoDir, ".team-sync-base.json");
|
|
114
|
+
if (!fs.existsSync(basePath)) return null;
|
|
115
|
+
try { return JSON.parse(fs.readFileSync(basePath, "utf8")); } catch { return null; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeLastSyncBase(infernoDir, contract) {
|
|
119
|
+
const basePath = path.join(infernoDir, ".team-sync-base.json");
|
|
120
|
+
fs.writeFileSync(basePath, JSON.stringify(contract, null, 2), "utf8");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── sub-commands ──────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
function initSharedBranch(cwd, remote, branch, infernoDir, asJson) {
|
|
126
|
+
if (!hasRemote(remote, cwd)) {
|
|
127
|
+
const msg = `Remote "${remote}" not found. Add it first: git remote add ${remote} <url>`;
|
|
128
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
129
|
+
warn(msg); process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (branchExistsRemote(remote, branch, cwd)) {
|
|
133
|
+
if (asJson) { console.log(JSON.stringify({ ok: true, action: "already_exists", branch })); }
|
|
134
|
+
else { ok(`Shared branch ${bold(branch)} already exists on ${remote}`); }
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create orphan branch with just the contract
|
|
139
|
+
const contractPath = path.join(infernoDir, "contract.json");
|
|
140
|
+
if (!fs.existsSync(contractPath)) {
|
|
141
|
+
const msg = "inferno/contract.json not found — run: infernoflow init";
|
|
142
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
143
|
+
warn(msg); process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Use a temp worktree approach: push contract.json to the branch
|
|
147
|
+
const tmpDir = path.join(infernoDir, ".team-sync-tmp");
|
|
148
|
+
try {
|
|
149
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
150
|
+
const contractContent = fs.readFileSync(contractPath, "utf8");
|
|
151
|
+
fs.writeFileSync(path.join(tmpDir, "contract.json"), contractContent);
|
|
152
|
+
|
|
153
|
+
// Create an empty tree commit on the shared branch
|
|
154
|
+
run(`git checkout --orphan ${branch}`, cwd);
|
|
155
|
+
run(`git rm -rf . --quiet 2>/dev/null || true`, cwd);
|
|
156
|
+
run(`git checkout ${currentBranch(cwd)} -- inferno/contract.json`, cwd);
|
|
157
|
+
run(`git add inferno/contract.json`, cwd);
|
|
158
|
+
run(`git commit -m "infernoflow: initialize shared contract branch"`, cwd);
|
|
159
|
+
run(`git push ${remote} ${branch}`, cwd);
|
|
160
|
+
run(`git checkout -`, cwd); // back to previous branch
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// Restore original branch on failure
|
|
163
|
+
try { run(`git checkout -`, cwd); } catch {}
|
|
164
|
+
const msg = `Failed to create shared branch: ${err.message}`;
|
|
165
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
166
|
+
warn(msg); process.exit(1);
|
|
167
|
+
} finally {
|
|
168
|
+
try { fs.rmSync(tmpDir, { recursive: true }); } catch {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (asJson) { console.log(JSON.stringify({ ok: true, action: "created", branch, remote })); }
|
|
172
|
+
else { done(`Shared branch ${bold(branch)} created on ${bold(remote)}`); }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function pushToShared(cwd, remote, branch, infernoDir, asJson, force) {
|
|
176
|
+
const contractPath = path.join(infernoDir, "contract.json");
|
|
177
|
+
if (!fs.existsSync(contractPath)) {
|
|
178
|
+
const msg = "inferno/contract.json not found";
|
|
179
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
180
|
+
warn(msg); process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const localContract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
|
184
|
+
const user = currentUser(cwd);
|
|
185
|
+
|
|
186
|
+
// Stamp the push metadata
|
|
187
|
+
localContract._teamSync = {
|
|
188
|
+
pushedBy: user,
|
|
189
|
+
pushedAt: new Date().toISOString(),
|
|
190
|
+
fromBranch: currentBranch(cwd),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Write updated contract
|
|
194
|
+
fs.writeFileSync(contractPath, JSON.stringify(localContract, null, 2), "utf8");
|
|
195
|
+
|
|
196
|
+
// Commit + push to shared branch
|
|
197
|
+
try {
|
|
198
|
+
run(`git fetch ${remote} ${branch} --quiet`, cwd);
|
|
199
|
+
// Use a temporary stash-push approach: push just the contract file
|
|
200
|
+
capture(`git stash --quiet`, cwd);
|
|
201
|
+
try {
|
|
202
|
+
run(`git checkout ${remote}/${branch} -- inferno/contract.json 2>/dev/null || git checkout ${remote}/${branch} inferno/contract.json`, cwd);
|
|
203
|
+
} catch {}
|
|
204
|
+
capture(`git stash pop --quiet`, cwd);
|
|
205
|
+
|
|
206
|
+
// Write the updated content
|
|
207
|
+
fs.writeFileSync(contractPath, JSON.stringify(localContract, null, 2), "utf8");
|
|
208
|
+
run(`git add inferno/contract.json`, cwd);
|
|
209
|
+
run(`git commit -m "infernoflow team-sync: push by ${user}"`, cwd);
|
|
210
|
+
run(`git push ${remote} HEAD:${branch}`, cwd);
|
|
211
|
+
|
|
212
|
+
// Save base snapshot
|
|
213
|
+
writeLastSyncBase(infernoDir, localContract);
|
|
214
|
+
|
|
215
|
+
if (asJson) { console.log(JSON.stringify({ ok: true, action: "pushed", remote, branch, user })); }
|
|
216
|
+
else { done(`Contract pushed to ${bold(remote + "/" + branch)} by ${bold(user)}`); }
|
|
217
|
+
} catch (err) {
|
|
218
|
+
const msg = `Push failed: ${err.message}`;
|
|
219
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
220
|
+
warn(msg);
|
|
221
|
+
info(`Try: git push ${remote} HEAD:${branch} --force (use --force flag)`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function pullFromShared(cwd, remote, branch, infernoDir, asJson, force) {
|
|
227
|
+
const contractPath = path.join(infernoDir, "contract.json");
|
|
228
|
+
|
|
229
|
+
// Fetch remote contract
|
|
230
|
+
const sharedContract = readContractFromBranch(remote, branch, cwd);
|
|
231
|
+
if (!sharedContract) {
|
|
232
|
+
const msg = `Could not read contract from ${remote}/${branch}. Run: infernoflow team-sync init`;
|
|
233
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
234
|
+
warn(msg); process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const localContract = fs.existsSync(contractPath)
|
|
238
|
+
? JSON.parse(fs.readFileSync(contractPath, "utf8")) : { capabilities: [] };
|
|
239
|
+
|
|
240
|
+
const baseContract = readLastSyncBase(infernoDir) || { capabilities: [] };
|
|
241
|
+
|
|
242
|
+
const localCaps = parseCaps(JSON.stringify(localContract));
|
|
243
|
+
const sharedCaps = parseCaps(JSON.stringify(sharedContract));
|
|
244
|
+
const baseCaps = parseCaps(JSON.stringify(baseContract));
|
|
245
|
+
|
|
246
|
+
const { conflicts, localOnly, sharedOnly } = detectConflicts(localCaps, sharedCaps, baseCaps);
|
|
247
|
+
|
|
248
|
+
if (conflicts.length > 0 && !force) {
|
|
249
|
+
if (asJson) {
|
|
250
|
+
console.log(JSON.stringify({ ok: false, error: "conflicts_detected", conflicts, hint: "Use --force to overwrite with remote version" }));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
warn(`${conflicts.length} capability conflict${conflicts.length !== 1 ? "s" : ""} detected:\n`);
|
|
254
|
+
for (const c of conflicts) {
|
|
255
|
+
console.log(` ${red("✗")} ${bold(c.id)}`);
|
|
256
|
+
console.log(` local: ${gray(c.local?.title || "(removed)")}`);
|
|
257
|
+
console.log(` shared: ${gray(c.shared?.title || "(removed)")}`);
|
|
258
|
+
}
|
|
259
|
+
console.log();
|
|
260
|
+
warn("Resolve conflicts manually or use --force to take the shared version");
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Merge: take shared as base, apply localOnly additions
|
|
265
|
+
const merged = { ...sharedContract };
|
|
266
|
+
const mergedCaps = [...sharedCaps];
|
|
267
|
+
for (const cap of localOnly) mergedCaps.push(cap);
|
|
268
|
+
merged.capabilities = mergedCaps;
|
|
269
|
+
delete merged._teamSync;
|
|
270
|
+
|
|
271
|
+
fs.writeFileSync(contractPath, JSON.stringify(merged, null, 2), "utf8");
|
|
272
|
+
writeLastSyncBase(infernoDir, merged);
|
|
273
|
+
|
|
274
|
+
if (asJson) {
|
|
275
|
+
console.log(JSON.stringify({
|
|
276
|
+
ok: true, action: "pulled", remote, branch,
|
|
277
|
+
conflicts: conflicts.length,
|
|
278
|
+
localOnly: localOnly.length,
|
|
279
|
+
sharedOnly: sharedOnly.length,
|
|
280
|
+
}));
|
|
281
|
+
} else {
|
|
282
|
+
console.log();
|
|
283
|
+
ok("Contract updated from shared branch");
|
|
284
|
+
if (conflicts.length > 0) warn(`${conflicts.length} conflict(s) resolved with --force (shared version wins)`);
|
|
285
|
+
if (localOnly.length > 0) ok(`${localOnly.length} local capability(-ies) preserved`);
|
|
286
|
+
if (sharedOnly.length > 0) ok(`${sharedOnly.length} new capability(-ies) pulled from shared`);
|
|
287
|
+
if (conflicts.length === 0 && localOnly.length === 0 && sharedOnly.length === 0) {
|
|
288
|
+
info("Already in sync — no changes");
|
|
289
|
+
}
|
|
290
|
+
console.log();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function showStatus(cwd, remote, branch, infernoDir, asJson) {
|
|
295
|
+
const contractPath = path.join(infernoDir, "contract.json");
|
|
296
|
+
|
|
297
|
+
try { run(`git fetch ${remote} ${branch} --quiet`, cwd); } catch {}
|
|
298
|
+
|
|
299
|
+
const sharedContract = readContractFromBranch(remote, branch, cwd);
|
|
300
|
+
if (!sharedContract) {
|
|
301
|
+
const msg = `Shared branch ${remote}/${branch} not found. Run: infernoflow team-sync init`;
|
|
302
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
303
|
+
warn(msg); process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const localContract = fs.existsSync(contractPath)
|
|
307
|
+
? JSON.parse(fs.readFileSync(contractPath, "utf8")) : { capabilities: [] };
|
|
308
|
+
|
|
309
|
+
const localCaps = parseCaps(JSON.stringify(localContract));
|
|
310
|
+
const sharedCaps = parseCaps(JSON.stringify(sharedContract));
|
|
311
|
+
|
|
312
|
+
const localMap = capsToMap(localCaps);
|
|
313
|
+
const sharedMap = capsToMap(sharedCaps);
|
|
314
|
+
|
|
315
|
+
const onlyLocal = localCaps.filter(c => !sharedMap.has(c.id));
|
|
316
|
+
const onlyShared = sharedCaps.filter(c => !localMap.has(c.id));
|
|
317
|
+
const inSync = onlyLocal.length === 0 && onlyShared.length === 0;
|
|
318
|
+
const pushedBy = sharedContract._teamSync?.pushedBy || "unknown";
|
|
319
|
+
const pushedAt = sharedContract._teamSync?.pushedAt || "unknown";
|
|
320
|
+
|
|
321
|
+
if (asJson) {
|
|
322
|
+
console.log(JSON.stringify({
|
|
323
|
+
ok: true, inSync,
|
|
324
|
+
local: localCaps.length, shared: sharedCaps.length,
|
|
325
|
+
onlyLocal: onlyLocal.map(c => c.id),
|
|
326
|
+
onlyShared: onlyShared.map(c => c.id),
|
|
327
|
+
lastPush: { by: pushedBy, at: pushedAt },
|
|
328
|
+
}));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
console.log();
|
|
333
|
+
console.log(` Shared branch ${bold(cyan(remote + "/" + branch))}`);
|
|
334
|
+
console.log(` Last push ${bold(pushedBy)} ${gray(pushedAt.slice(0, 19).replace("T", " "))}`);
|
|
335
|
+
console.log();
|
|
336
|
+
|
|
337
|
+
if (inSync) {
|
|
338
|
+
ok("Local and shared contracts are in sync");
|
|
339
|
+
} else {
|
|
340
|
+
if (onlyLocal.length) {
|
|
341
|
+
console.log(` ${yellow("→")} ${bold(onlyLocal.length)} local capability(-ies) not yet pushed:`);
|
|
342
|
+
for (const c of onlyLocal) console.log(` ${yellow("+")} ${c.id} ${gray(c.title)}`);
|
|
343
|
+
}
|
|
344
|
+
if (onlyShared.length) {
|
|
345
|
+
console.log(` ${cyan("←")} ${bold(onlyShared.length)} shared capability(-ies) not yet pulled:`);
|
|
346
|
+
for (const c of onlyShared) console.log(` ${cyan("+")} ${c.id} ${gray(c.title)}`);
|
|
347
|
+
}
|
|
348
|
+
console.log();
|
|
349
|
+
if (onlyLocal.length) info(`Run ${cyan("infernoflow team-sync push")} to share your changes`);
|
|
350
|
+
if (onlyShared.length) info(`Run ${cyan("infernoflow team-sync pull")} to get team changes`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ── main ──────────────────────────────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
export async function teamSyncCommand(rawArgs) {
|
|
359
|
+
const args = rawArgs.slice(1);
|
|
360
|
+
const asJson = args.includes("--json");
|
|
361
|
+
const force = args.includes("--force");
|
|
362
|
+
|
|
363
|
+
const branchIdx = args.indexOf("--branch");
|
|
364
|
+
const remoteIdx = args.indexOf("--remote");
|
|
365
|
+
const branch = branchIdx !== -1 ? args[branchIdx + 1] : "inferno-contracts";
|
|
366
|
+
const remote = remoteIdx !== -1 ? args[remoteIdx + 1] : "origin";
|
|
367
|
+
|
|
368
|
+
const sub = args.find(a => !a.startsWith("-")) || "status";
|
|
369
|
+
|
|
370
|
+
const cwd = process.cwd();
|
|
371
|
+
const infernoDir = path.join(cwd, "inferno");
|
|
372
|
+
|
|
373
|
+
if (!asJson) header("infernoflow team-sync");
|
|
374
|
+
|
|
375
|
+
if (!fs.existsSync(infernoDir)) {
|
|
376
|
+
const msg = "inferno/ not found — run: infernoflow init";
|
|
377
|
+
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
378
|
+
warn(msg); process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
switch (sub) {
|
|
382
|
+
case "init": initSharedBranch(cwd, remote, branch, infernoDir, asJson); break;
|
|
383
|
+
case "push": pushToShared(cwd, remote, branch, infernoDir, asJson, force); break;
|
|
384
|
+
case "pull": pullFromShared(cwd, remote, branch, infernoDir, asJson, force); break;
|
|
385
|
+
case "status":
|
|
386
|
+
default: showStatus(cwd, remote, branch, infernoDir, asJson); break;
|
|
387
|
+
}
|
|
388
|
+
}
|