@fabricorg/cli 0.3.1 → 0.3.3
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/dist/fabric.js +332 -11
- package/dist/fabric.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/fabric.js
CHANGED
|
@@ -57,7 +57,7 @@ import { Command as Command20 } from "commander";
|
|
|
57
57
|
// package.json
|
|
58
58
|
var package_default = {
|
|
59
59
|
name: "@fabricorg/cli",
|
|
60
|
-
version: "0.3.
|
|
60
|
+
version: "0.3.3",
|
|
61
61
|
description: "CLI for the Fabric AI platform",
|
|
62
62
|
keywords: [
|
|
63
63
|
"fabric",
|
|
@@ -1369,6 +1369,7 @@ import { chmod } from "fs/promises";
|
|
|
1369
1369
|
// src/lib/instructions/paths.ts
|
|
1370
1370
|
var DRIVE_LETTER = /^[A-Za-z]:/;
|
|
1371
1371
|
var RESERVED_DEVICE_NAME = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
|
|
1372
|
+
var FORBIDDEN_NAME_CHARACTER = /[<>:"|?*]/;
|
|
1372
1373
|
var RESERVED_ROOTS = [".git", ".fabric"];
|
|
1373
1374
|
var RESERVED_EXACT_PATHS = [".claude/settings.local.json"];
|
|
1374
1375
|
function hasControlCharacter(input) {
|
|
@@ -1418,10 +1419,10 @@ function checkRelativePath(input) {
|
|
|
1418
1419
|
detail: input
|
|
1419
1420
|
};
|
|
1420
1421
|
}
|
|
1421
|
-
if (
|
|
1422
|
+
if (FORBIDDEN_NAME_CHARACTER.test(segment)) {
|
|
1422
1423
|
return {
|
|
1423
1424
|
ok: false,
|
|
1424
|
-
reason: "
|
|
1425
|
+
reason: "forbidden_character",
|
|
1425
1426
|
detail: input
|
|
1426
1427
|
};
|
|
1427
1428
|
}
|
|
@@ -1472,8 +1473,8 @@ function describeRejection(rejection) {
|
|
|
1472
1473
|
return `path segment ending in a dot or space refused: ${rejection.detail}`;
|
|
1473
1474
|
case "reserved_device_name":
|
|
1474
1475
|
return `reserved device name refused: ${rejection.detail}`;
|
|
1475
|
-
case "
|
|
1476
|
-
return `
|
|
1476
|
+
case "forbidden_character":
|
|
1477
|
+
return `path contains a character Windows will not put in a filename (< > : " | ? *): ${rejection.detail}`;
|
|
1477
1478
|
case "reserved_path":
|
|
1478
1479
|
return `this tool never writes or deletes ${RESERVED_ROOTS.join(", ")} or ${RESERVED_EXACT_PATHS.join(", ")}: ${rejection.detail}`;
|
|
1479
1480
|
case "not_a_regular_file":
|
|
@@ -2578,11 +2579,158 @@ function nextLock(input) {
|
|
|
2578
2579
|
};
|
|
2579
2580
|
}
|
|
2580
2581
|
|
|
2582
|
+
// src/lib/instructions/push.ts
|
|
2583
|
+
import { createHash as createHash5 } from "crypto";
|
|
2584
|
+
var MAX_PUSH_CHANGES = 50;
|
|
2585
|
+
function assertPushablePath(candidate, source) {
|
|
2586
|
+
const check = checkRelativePath(candidate);
|
|
2587
|
+
if (!check.ok) {
|
|
2588
|
+
throw new Error(
|
|
2589
|
+
`Refusing to push: ${describeRejection(check)}. ${source}`
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
if (isReservedPath(candidate)) {
|
|
2593
|
+
throw new Error(
|
|
2594
|
+
`Refusing to push: ${describeRejection({
|
|
2595
|
+
ok: false,
|
|
2596
|
+
reason: "reserved_path",
|
|
2597
|
+
detail: candidate
|
|
2598
|
+
})}. ${source}`
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
function encodeContent(bytes) {
|
|
2603
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
2604
|
+
const roundTrip = new TextEncoder().encode(text);
|
|
2605
|
+
if (roundTrip.length === bytes.length && roundTrip.every((byte, index) => byte === bytes[index])) {
|
|
2606
|
+
return { content: text, encoding: "utf8" };
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
content: Buffer.from(bytes).toString("base64"),
|
|
2610
|
+
encoding: "base64"
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
function refuseStaleLedger(detail) {
|
|
2614
|
+
throw new Error(
|
|
2615
|
+
`Refusing to push: the sync lock does not match the published version \u2014 ${detail}. Run \`fabric instructions sync\` and try again. Nothing was read and nothing was sent.`
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
function assertLockMatchesManifest(lock, manifest) {
|
|
2619
|
+
const published = new Map(
|
|
2620
|
+
manifest.map((entry) => [entry.path, entry.sha256])
|
|
2621
|
+
);
|
|
2622
|
+
for (const [path5, entry] of Object.entries(lock.files)) {
|
|
2623
|
+
const expected = published.get(path5);
|
|
2624
|
+
if (expected === void 0) {
|
|
2625
|
+
refuseStaleLedger(
|
|
2626
|
+
`it lists ${path5}, which version ${lock.snapshotVersion} does not contain`
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
if (expected !== entry.sha256) {
|
|
2630
|
+
refuseStaleLedger(
|
|
2631
|
+
`its record of ${path5} is not the published file`
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
for (const entry of manifest) {
|
|
2636
|
+
if (!Object.hasOwn(lock.files, entry.path)) {
|
|
2637
|
+
refuseStaleLedger(
|
|
2638
|
+
`version ${lock.snapshotVersion} contains ${entry.path}, which the lock does not list`
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
function computePushPlan(input) {
|
|
2644
|
+
return __async(this, null, function* () {
|
|
2645
|
+
var _a2, _b;
|
|
2646
|
+
const { root, lock } = input;
|
|
2647
|
+
const lockedPaths = Object.keys(lock.files);
|
|
2648
|
+
for (const lockedPath of lockedPaths) {
|
|
2649
|
+
assertPushablePath(
|
|
2650
|
+
lockedPath,
|
|
2651
|
+
"The lock names a path this tool will not touch."
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
assertLockMatchesManifest(lock, input.manifest);
|
|
2655
|
+
const lockCollision = findCollision(lockedPaths);
|
|
2656
|
+
if (lockCollision !== null) {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
`Refusing to push: ${describeRejection({
|
|
2659
|
+
ok: false,
|
|
2660
|
+
reason: "collision",
|
|
2661
|
+
detail: `${lockCollision.first} and ${lockCollision.second}`
|
|
2662
|
+
})}. The lock names two paths this tool cannot tell apart.`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
const added = [];
|
|
2666
|
+
const locked = new Set(lockedPaths);
|
|
2667
|
+
for (const candidate of (_a2 = input.added) != null ? _a2 : []) {
|
|
2668
|
+
assertPushablePath(candidate, "That path cannot be pushed.");
|
|
2669
|
+
if (locked.has(candidate)) {
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
if (added.includes(candidate)) {
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
added.push(candidate);
|
|
2676
|
+
}
|
|
2677
|
+
const allCollision = findCollision([...lockedPaths, ...added]);
|
|
2678
|
+
if (allCollision !== null) {
|
|
2679
|
+
throw new Error(
|
|
2680
|
+
`Refusing to push: ${describeRejection({
|
|
2681
|
+
ok: false,
|
|
2682
|
+
reason: "collision",
|
|
2683
|
+
detail: `${allCollision.first} and ${allCollision.second}`
|
|
2684
|
+
})}. Two of these paths are one file on a case-insensitive filesystem.`
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
const entries = [];
|
|
2688
|
+
const changes = [];
|
|
2689
|
+
const unchanged = [];
|
|
2690
|
+
for (const lockedPath of lockedPaths.sort()) {
|
|
2691
|
+
const read = yield readFileSafely(root, lockedPath);
|
|
2692
|
+
if (read === null) {
|
|
2693
|
+
entries.push({ path: lockedPath, action: "delete" });
|
|
2694
|
+
changes.push({ op: "delete", path: lockedPath });
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
const actual = createHash5("sha256").update(read.bytes).digest("hex");
|
|
2698
|
+
if (actual === ((_b = lock.files[lockedPath]) == null ? void 0 : _b.sha256)) {
|
|
2699
|
+
unchanged.push(lockedPath);
|
|
2700
|
+
continue;
|
|
2701
|
+
}
|
|
2702
|
+
const encoded = encodeContent(read.bytes);
|
|
2703
|
+
entries.push({
|
|
2704
|
+
path: lockedPath,
|
|
2705
|
+
action: "put",
|
|
2706
|
+
size: read.bytes.length
|
|
2707
|
+
});
|
|
2708
|
+
changes.push(__spreadValues({ op: "put", path: lockedPath }, encoded));
|
|
2709
|
+
}
|
|
2710
|
+
for (const newPath of added.sort()) {
|
|
2711
|
+
const read = yield readFileSafely(root, newPath);
|
|
2712
|
+
if (read === null) {
|
|
2713
|
+
throw new Error(
|
|
2714
|
+
`Refusing to push: there is no file at ${newPath}.`
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
const encoded = encodeContent(read.bytes);
|
|
2718
|
+
entries.push({ path: newPath, action: "put", size: read.bytes.length });
|
|
2719
|
+
changes.push(__spreadValues({ op: "put", path: newPath }, encoded));
|
|
2720
|
+
}
|
|
2721
|
+
return { entries, changes, unchanged };
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2581
2725
|
// src/commands/instructions/index.ts
|
|
2582
2726
|
var HOOK_DEADLINE_MS = 1e4;
|
|
2583
2727
|
var CHECK_TIMEOUT_MS = 5e3;
|
|
2584
2728
|
var SYNC_TIMEOUT_MS = 15e3;
|
|
2585
2729
|
var BUNDLE_TIMEOUT_MS = 6e4;
|
|
2730
|
+
var PUSH_TIMEOUT_MS = 6e4;
|
|
2731
|
+
function collectAdded(value, previous) {
|
|
2732
|
+
return [...previous, value];
|
|
2733
|
+
}
|
|
2586
2734
|
var CliFailure = class extends Error {
|
|
2587
2735
|
constructor(message, exitCode) {
|
|
2588
2736
|
super(message);
|
|
@@ -2617,6 +2765,22 @@ function buildInstructionsCommand() {
|
|
|
2617
2765
|
yield run(opts, "sync", () => runSync(opts, formatFor(this)));
|
|
2618
2766
|
});
|
|
2619
2767
|
});
|
|
2768
|
+
instructions.command("push").description(
|
|
2769
|
+
"Suggest this checkout's edits to the project's coding instructions"
|
|
2770
|
+
).requiredOption("--project <id>", "Project ID").option("--dest <dir>", "Destination directory (default: cwd)").option("--org <slug>", "Organization context").option(
|
|
2771
|
+
"--add <path>",
|
|
2772
|
+
"Also send a file the last sync did not write (repeatable)",
|
|
2773
|
+
collectAdded,
|
|
2774
|
+
[]
|
|
2775
|
+
).option("--dry-run", "Print the change set and send nothing").option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2776
|
+
return __async(this, null, function* () {
|
|
2777
|
+
yield run(
|
|
2778
|
+
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
2779
|
+
"push",
|
|
2780
|
+
() => runPush(opts, formatFor(this))
|
|
2781
|
+
);
|
|
2782
|
+
});
|
|
2783
|
+
});
|
|
2620
2784
|
instructions.command("init").description(
|
|
2621
2785
|
"Write a SessionStart hook for a coding tool and take the first copy"
|
|
2622
2786
|
).requiredOption("--project <id>", "Project ID").requiredOption(
|
|
@@ -2721,7 +2885,7 @@ function destinationOf(opts) {
|
|
|
2721
2885
|
var _a2;
|
|
2722
2886
|
return path4.resolve((_a2 = opts.dest) != null ? _a2 : process.cwd());
|
|
2723
2887
|
}
|
|
2724
|
-
function instructionsClient(opts, timeoutMs) {
|
|
2888
|
+
function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
2725
2889
|
if (!getApiKey()) {
|
|
2726
2890
|
throw new CliFailure(
|
|
2727
2891
|
"Not authenticated. Run: fabric auth login --key <api-key>",
|
|
@@ -2730,11 +2894,22 @@ function instructionsClient(opts, timeoutMs) {
|
|
|
2730
2894
|
}
|
|
2731
2895
|
return getClient(
|
|
2732
2896
|
opts.hook ? {
|
|
2897
|
+
// Hook mode has ONE absolute deadline covering every
|
|
2898
|
+
// call it makes, so it cannot spend it on retries: a
|
|
2899
|
+
// session that will not start is worse than instructions
|
|
2900
|
+
// one version stale.
|
|
2733
2901
|
timeoutMs: Math.min(timeoutMs, HOOK_DEADLINE_MS),
|
|
2734
2902
|
retry: { maxRetries: 0 }
|
|
2735
|
-
} : { timeoutMs }
|
|
2903
|
+
} : neverRetry ? { timeoutMs, retry: { maxRetries: 0 } } : { timeoutMs }
|
|
2736
2904
|
).withoutContext();
|
|
2737
2905
|
}
|
|
2906
|
+
function downloadUrlClient(opts) {
|
|
2907
|
+
return instructionsClient(
|
|
2908
|
+
opts,
|
|
2909
|
+
opts.hook ? HOOK_DEADLINE_MS : BUNDLE_TIMEOUT_MS,
|
|
2910
|
+
{ neverRetry: true }
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2738
2913
|
function orgSlugFor(opts) {
|
|
2739
2914
|
return opts.org;
|
|
2740
2915
|
}
|
|
@@ -2945,10 +3120,9 @@ function syncOnce(opts) {
|
|
|
2945
3120
|
const org = orgSlugFor(opts);
|
|
2946
3121
|
let download;
|
|
2947
3122
|
try {
|
|
2948
|
-
download = yield
|
|
2949
|
-
opts
|
|
2950
|
-
|
|
2951
|
-
);
|
|
3123
|
+
download = yield downloadUrlClient(
|
|
3124
|
+
opts
|
|
3125
|
+
).instructions.createDownloadUrl(opts.project, { org });
|
|
2952
3126
|
} catch (error) {
|
|
2953
3127
|
throw asCliFailure(error);
|
|
2954
3128
|
}
|
|
@@ -3044,6 +3218,153 @@ function reportSync(outcome, opts) {
|
|
|
3044
3218
|
line(`Everything in ${outcome.destination} already matched.`);
|
|
3045
3219
|
}
|
|
3046
3220
|
}
|
|
3221
|
+
function runPush(opts, format) {
|
|
3222
|
+
return __async(this, null, function* () {
|
|
3223
|
+
var _a2;
|
|
3224
|
+
const destination = destinationOf(opts);
|
|
3225
|
+
const root = yield resolveExistingRoot(destination);
|
|
3226
|
+
const lock = yield readLockForProject(root, opts.project);
|
|
3227
|
+
if (lock === null) {
|
|
3228
|
+
throw new CliFailure(
|
|
3229
|
+
`No coding-instructions lock in ${destination}. Run \`fabric instructions sync --project ${opts.project}\` first: a push is a diff against the version that was last applied here, and without that ledger there is nothing to diff against.`,
|
|
3230
|
+
7
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
const client = instructionsClient(
|
|
3234
|
+
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
3235
|
+
PUSH_TIMEOUT_MS
|
|
3236
|
+
);
|
|
3237
|
+
const published = yield fetchPublished(client, opts);
|
|
3238
|
+
if (!published.published || !published.snapshot) {
|
|
3239
|
+
throw new CliFailure(
|
|
3240
|
+
"this project has no published coding instructions to change yet; the first version is uploaded from the Coding Instructions tab",
|
|
3241
|
+
4
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
if (published.snapshot.id !== lock.snapshotId) {
|
|
3245
|
+
throw new CliFailure(
|
|
3246
|
+
"published instructions moved past your last sync; run `fabric instructions sync` then push again",
|
|
3247
|
+
7
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
const plan = yield computePushPlan({
|
|
3251
|
+
root,
|
|
3252
|
+
lock,
|
|
3253
|
+
manifest: (_a2 = published.manifest) != null ? _a2 : [],
|
|
3254
|
+
added: opts.add
|
|
3255
|
+
});
|
|
3256
|
+
if (plan.changes.length === 0) {
|
|
3257
|
+
throw new CliFailure(
|
|
3258
|
+
`Nothing to push: every file the last sync wrote still matches version ${lock.snapshotVersion}. Add a new file with --add <path> if you meant to suggest one.`,
|
|
3259
|
+
7
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
if (plan.changes.length > MAX_PUSH_CHANGES) {
|
|
3263
|
+
throw new CliFailure(
|
|
3264
|
+
`Too many changes to push (${plan.changes.length} > ${MAX_PUSH_CHANGES}). A change set this large is a replacement rather than an edit \u2014 upload the folder from the project's Coding Instructions tab, which is also the only path that re-reads the project's exclusion rules.`,
|
|
3265
|
+
7
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3268
|
+
const outcome = {
|
|
3269
|
+
projectId: opts.project,
|
|
3270
|
+
destination: root,
|
|
3271
|
+
dryRun: Boolean(opts.dryRun),
|
|
3272
|
+
baseSnapshotId: lock.snapshotId,
|
|
3273
|
+
baseVersion: lock.snapshotVersion,
|
|
3274
|
+
put: plan.entries.filter((entry) => entry.action === "put").map((entry) => entry.path),
|
|
3275
|
+
deleted: plan.entries.filter((entry) => entry.action === "delete").map((entry) => entry.path),
|
|
3276
|
+
unchanged: plan.unchanged.length,
|
|
3277
|
+
snapshotId: null,
|
|
3278
|
+
version: null,
|
|
3279
|
+
proposalStatus: null,
|
|
3280
|
+
status: null
|
|
3281
|
+
};
|
|
3282
|
+
if (!opts.dryRun) {
|
|
3283
|
+
try {
|
|
3284
|
+
const submitted = yield client.instructions.submitChange(
|
|
3285
|
+
opts.project,
|
|
3286
|
+
lock.snapshotId,
|
|
3287
|
+
plan.changes,
|
|
3288
|
+
{ org: orgSlugFor(opts) }
|
|
3289
|
+
);
|
|
3290
|
+
outcome.snapshotId = submitted.snapshotId;
|
|
3291
|
+
outcome.version = submitted.version;
|
|
3292
|
+
outcome.proposalStatus = submitted.proposalStatus;
|
|
3293
|
+
outcome.status = submitted.status;
|
|
3294
|
+
} catch (error) {
|
|
3295
|
+
throw asPushFailure(error);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if (format === "json") {
|
|
3299
|
+
printOutput(outcome, { format: "json" });
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
reportPush(outcome);
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
function asPushFailure(error) {
|
|
3306
|
+
const code = error.code;
|
|
3307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3308
|
+
switch (code) {
|
|
3309
|
+
case "PULL_FIRST":
|
|
3310
|
+
return new CliFailure(
|
|
3311
|
+
"published instructions moved past your last sync; run `fabric instructions sync` then push again",
|
|
3312
|
+
7
|
|
3313
|
+
);
|
|
3314
|
+
case "REPOSITORY_SOURCE_OF_TRUTH":
|
|
3315
|
+
return new CliFailure(
|
|
3316
|
+
"this project's coding instructions come from its repository, so they are changed there and mirrored into Fabric \u2014 commit and push to the repository instead; nothing was sent",
|
|
3317
|
+
7
|
|
3318
|
+
);
|
|
3319
|
+
case "NOTHING_PUBLISHED":
|
|
3320
|
+
return new CliFailure(
|
|
3321
|
+
"this project has no published coding instructions to change yet; the first version is uploaded from the Coding Instructions tab",
|
|
3322
|
+
4
|
|
3323
|
+
);
|
|
3324
|
+
case "PROPOSAL_PROPOSER_LIMIT":
|
|
3325
|
+
case "PROPOSAL_PROJECT_LIMIT":
|
|
3326
|
+
return new CliFailure(`${message} Nothing was sent.`, 7);
|
|
3327
|
+
default:
|
|
3328
|
+
return asCliFailure(error);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
function pushVerdict(outcome) {
|
|
3332
|
+
var _a2;
|
|
3333
|
+
if (outcome.proposalStatus === "PENDING" && outcome.status === "RECEIVING") {
|
|
3334
|
+
return `An earlier attempt is still sending this change; if version ${outcome.version} stays unfinished, cancel it in the project's Coding Instructions tab, or leave it and it is closed out automatically after six hours.`;
|
|
3335
|
+
}
|
|
3336
|
+
if (outcome.proposalStatus === "PENDING" && outcome.status === "FAILED") {
|
|
3337
|
+
return `This proposal did not pass its checks; retry version ${outcome.version} from the project's Coding Instructions tab.`;
|
|
3338
|
+
}
|
|
3339
|
+
if (outcome.proposalStatus === "PENDING") {
|
|
3340
|
+
if (outcome.status === "REJECTED") {
|
|
3341
|
+
return `Version ${outcome.version} was rejected by its checks \u2014 cancel it in the project's Coding Instructions tab before proposing this change again.`;
|
|
3342
|
+
}
|
|
3343
|
+
return `Proposed as version ${outcome.version}. It is pending review \u2014 nothing is published until somebody who can edit this project's coding instructions approves it in the Coding Instructions tab.`;
|
|
3344
|
+
}
|
|
3345
|
+
if (outcome.proposalStatus === "APPROVED") {
|
|
3346
|
+
return `Version ${outcome.version} has already been approved and published \u2014 run \`fabric instructions sync\` to bring this checkout up to it.`;
|
|
3347
|
+
}
|
|
3348
|
+
return `Version ${outcome.version} is no longer open for review (${(_a2 = outcome.proposalStatus) != null ? _a2 : outcome.status}): an earlier attempt at this same change was closed out, so push again.`;
|
|
3349
|
+
}
|
|
3350
|
+
function reportPush(outcome) {
|
|
3351
|
+
const prefix = outcome.dryRun ? "Would send" : "Sent";
|
|
3352
|
+
line(
|
|
3353
|
+
`${prefix} ${outcome.put.length} changed file(s) and ${outcome.deleted.length} deletion(s) against version ${outcome.baseVersion} (${outcome.unchanged} unchanged).`
|
|
3354
|
+
);
|
|
3355
|
+
listPaths("changed", outcome.put);
|
|
3356
|
+
listPaths("deleted", outcome.deleted);
|
|
3357
|
+
if (outcome.dryRun) {
|
|
3358
|
+
line(
|
|
3359
|
+
"Nothing was sent. Without --dry-run this would open a proposal for review."
|
|
3360
|
+
);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
line(pushVerdict(outcome));
|
|
3364
|
+
line(
|
|
3365
|
+
"Your lock was not changed: it still names the published version, which is what `fabric instructions sync` compares against."
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3047
3368
|
function runInit(opts, format) {
|
|
3048
3369
|
return __async(this, null, function* () {
|
|
3049
3370
|
if (opts.tool !== "claude-code") {
|