@cubedot/cli 0.1.2 → 0.1.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/cli.js +454 -130
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6
|
-
import { dirname as dirname4, join as
|
|
7
|
-
import { readFileSync as
|
|
6
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
7
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import pc from "picocolors";
|
|
11
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
11
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
12
12
|
import { join as join3 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/mcp.ts
|
|
@@ -83,7 +83,10 @@ import {
|
|
|
83
83
|
copyFileSync,
|
|
84
84
|
existsSync,
|
|
85
85
|
mkdirSync,
|
|
86
|
+
readdirSync,
|
|
86
87
|
readFileSync,
|
|
88
|
+
rmdirSync,
|
|
89
|
+
unlinkSync,
|
|
87
90
|
writeFileSync
|
|
88
91
|
} from "fs";
|
|
89
92
|
import { dirname, join } from "path";
|
|
@@ -178,17 +181,18 @@ var GITIGNORE_ENTRIES = [
|
|
|
178
181
|
function updateGitignore(cwd) {
|
|
179
182
|
const path = join(cwd, ".gitignore");
|
|
180
183
|
let content = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
181
|
-
const
|
|
182
|
-
|
|
184
|
+
const existing = content.split("\n");
|
|
185
|
+
const added = [];
|
|
183
186
|
for (const entry of GITIGNORE_ENTRIES) {
|
|
184
|
-
if (!
|
|
187
|
+
if (!existing.some((l) => l.trim() === entry)) {
|
|
185
188
|
content = content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n";
|
|
186
|
-
|
|
189
|
+
added.push(entry);
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
|
-
if (
|
|
192
|
+
if (added.length > 0) {
|
|
190
193
|
writeFileSync(path, content, "utf8");
|
|
191
194
|
}
|
|
195
|
+
return added;
|
|
192
196
|
}
|
|
193
197
|
function readHashes(cwd) {
|
|
194
198
|
const path = join(cwd, ".cubedot", "sync", "hashes.json");
|
|
@@ -208,8 +212,9 @@ function enableMcpServerInSettings(cwd, serverName = "cubedot") {
|
|
|
208
212
|
const dir = join(cwd, ".claude");
|
|
209
213
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
210
214
|
const path = join(dir, "settings.json");
|
|
215
|
+
const existed = existsSync(path);
|
|
211
216
|
let settings = {};
|
|
212
|
-
if (
|
|
217
|
+
if (existed) {
|
|
213
218
|
try {
|
|
214
219
|
settings = JSON.parse(readFileSync(path, "utf8"));
|
|
215
220
|
} catch {
|
|
@@ -220,6 +225,7 @@ function enableMcpServerInSettings(cwd, serverName = "cubedot") {
|
|
|
220
225
|
settings["enabledMcpjsonServers"] = [...cur, serverName];
|
|
221
226
|
}
|
|
222
227
|
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
228
|
+
return { created: !existed };
|
|
223
229
|
}
|
|
224
230
|
function readLinks(cwd) {
|
|
225
231
|
const path = join(cwd, ".cubedot", "links.json");
|
|
@@ -230,6 +236,76 @@ function readLinks(cwd) {
|
|
|
230
236
|
return {};
|
|
231
237
|
}
|
|
232
238
|
}
|
|
239
|
+
var MANIFEST_PATH = ".cubedot/install-manifest.json";
|
|
240
|
+
function readInstallManifest(cwd) {
|
|
241
|
+
const path = join(cwd, MANIFEST_PATH);
|
|
242
|
+
if (!existsSync(path)) return null;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function writeInstallManifest(cwd, manifest) {
|
|
250
|
+
writeFileSync(join(cwd, MANIFEST_PATH), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
251
|
+
}
|
|
252
|
+
function stripGitignoreLines(cwd, lines) {
|
|
253
|
+
const path = join(cwd, ".gitignore");
|
|
254
|
+
if (!existsSync(path)) return;
|
|
255
|
+
const content = readFileSync(path, "utf8");
|
|
256
|
+
const kept = content.split("\n").filter((l) => !lines.includes(l.trim()));
|
|
257
|
+
while (kept.length > 0 && !kept[kept.length - 1]?.trim()) kept.pop();
|
|
258
|
+
if (kept.length === 0) {
|
|
259
|
+
unlinkSync(path);
|
|
260
|
+
} else {
|
|
261
|
+
writeFileSync(path, kept.join("\n") + "\n", "utf8");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function removeCubedotFromSettings(cwd) {
|
|
265
|
+
const path = join(cwd, ".claude", "settings.json");
|
|
266
|
+
if (!existsSync(path)) return;
|
|
267
|
+
let settings;
|
|
268
|
+
try {
|
|
269
|
+
settings = JSON.parse(readFileSync(path, "utf8"));
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!Array.isArray(settings["enabledMcpjsonServers"])) return;
|
|
274
|
+
const filtered = settings["enabledMcpjsonServers"].filter(
|
|
275
|
+
(s) => s !== "cubedot"
|
|
276
|
+
);
|
|
277
|
+
if (filtered.length === 0) {
|
|
278
|
+
delete settings["enabledMcpjsonServers"];
|
|
279
|
+
} else {
|
|
280
|
+
settings["enabledMcpjsonServers"] = filtered;
|
|
281
|
+
}
|
|
282
|
+
if (Object.keys(settings).length === 0) {
|
|
283
|
+
unlinkSync(path);
|
|
284
|
+
} else {
|
|
285
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function stripCubedotMarkers(filePath) {
|
|
289
|
+
if (!existsSync(filePath)) return;
|
|
290
|
+
const content = readFileSync(filePath, "utf8");
|
|
291
|
+
const stripped = content.replace(
|
|
292
|
+
/<!-- CUBEDOT:START:(\w+) -->[\s\S]*?<!-- CUBEDOT:END:\1 -->\n?/g,
|
|
293
|
+
""
|
|
294
|
+
);
|
|
295
|
+
const cleaned = stripped.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
296
|
+
writeFileSync(filePath, cleaned ? cleaned + "\n" : "", "utf8");
|
|
297
|
+
}
|
|
298
|
+
function tryRmdir(dirPath) {
|
|
299
|
+
if (!existsSync(dirPath)) return false;
|
|
300
|
+
try {
|
|
301
|
+
const entries = readdirSync(dirPath);
|
|
302
|
+
if (entries.length > 0) return false;
|
|
303
|
+
rmdirSync(dirPath);
|
|
304
|
+
return true;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
233
309
|
|
|
234
310
|
// src/hotmemory.ts
|
|
235
311
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -396,6 +472,25 @@ function decodeToken(token) {
|
|
|
396
472
|
}
|
|
397
473
|
async function initCommand(opts) {
|
|
398
474
|
const cwd = process.cwd();
|
|
475
|
+
const ownPkgPath = join3(cwd, "package.json");
|
|
476
|
+
if (existsSync3(ownPkgPath) && !opts.force) {
|
|
477
|
+
try {
|
|
478
|
+
const ownPkg = JSON.parse(readFileSync3(ownPkgPath, "utf8"));
|
|
479
|
+
if (ownPkg.name === "@cubedot/cli") {
|
|
480
|
+
console.error(
|
|
481
|
+
pc.red(
|
|
482
|
+
"Error: This looks like the Cubedot CLI source repo, not a project to connect.\nRun `cubedot init` inside your target project directory, or pass --force to override."
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const claudeMdExisted = existsSync3(join3(cwd, "CLAUDE.md"));
|
|
491
|
+
const agentsMdExisted = existsSync3(join3(cwd, "AGENTS.md"));
|
|
492
|
+
const fieldNotesExisted = existsSync3(join3(cwd, ".cubedot", "field-notes.md"));
|
|
493
|
+
const linksJsonExisted = existsSync3(join3(cwd, ".cubedot", "links.json"));
|
|
399
494
|
let projectId;
|
|
400
495
|
let key;
|
|
401
496
|
let mcpUrl;
|
|
@@ -485,7 +580,7 @@ async function initCommand(opts) {
|
|
|
485
580
|
await client.close();
|
|
486
581
|
}
|
|
487
582
|
writeMcpJson(cwd, mcpUrl, projectId, key);
|
|
488
|
-
enableMcpServerInSettings(cwd);
|
|
583
|
+
const { created: settingsCreated } = enableMcpServerInSettings(cwd);
|
|
489
584
|
writeCubedotConfig(cwd, {
|
|
490
585
|
projectId,
|
|
491
586
|
mcpUrl,
|
|
@@ -498,7 +593,33 @@ async function initCommand(opts) {
|
|
|
498
593
|
generateMemoryFiles(cwd, identity, conventions, registry);
|
|
499
594
|
installAgentAssets(cwd);
|
|
500
595
|
scaffoldCubedotDirs(cwd);
|
|
501
|
-
updateGitignore(cwd);
|
|
596
|
+
const gitignoreAdded = updateGitignore(cwd);
|
|
597
|
+
const manifest = {
|
|
598
|
+
cliVersion: pkg.version,
|
|
599
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
600
|
+
createdFiles: [
|
|
601
|
+
".mcp.json",
|
|
602
|
+
".cubedot/config.json",
|
|
603
|
+
".cubedot/fia-operating-manual.md",
|
|
604
|
+
".claude/agents/CubedotVerifier.md",
|
|
605
|
+
...!fieldNotesExisted ? [".cubedot/field-notes.md"] : [],
|
|
606
|
+
...!linksJsonExisted ? [".cubedot/links.json"] : []
|
|
607
|
+
],
|
|
608
|
+
createdDirs: [
|
|
609
|
+
".cubedot/progress",
|
|
610
|
+
".cubedot/reports",
|
|
611
|
+
".cubedot/docs",
|
|
612
|
+
".cubedot/sync",
|
|
613
|
+
".cubedot",
|
|
614
|
+
".claude/agents",
|
|
615
|
+
".claude"
|
|
616
|
+
],
|
|
617
|
+
claudeMd: claudeMdExisted ? "inserted" : "created",
|
|
618
|
+
agentsMd: agentsMdExisted ? "inserted" : "created",
|
|
619
|
+
settingsCreated,
|
|
620
|
+
gitignoreAdded
|
|
621
|
+
};
|
|
622
|
+
writeInstallManifest(cwd, manifest);
|
|
502
623
|
const projectName = orientText.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "") ?? projectId;
|
|
503
624
|
console.log("\n" + pc.green("\u2713") + " Cubedot connected: " + pc.bold(projectName));
|
|
504
625
|
console.log(pc.dim(` Project ID : ${projectId}`));
|
|
@@ -510,6 +631,8 @@ async function initCommand(opts) {
|
|
|
510
631
|
const written = [
|
|
511
632
|
".mcp.json (gitignored \u2014 holds your key)",
|
|
512
633
|
".cubedot/config.json (gitignored)",
|
|
634
|
+
".cubedot/install-manifest.json",
|
|
635
|
+
".claude/settings.json (enabledMcpjsonServers)",
|
|
513
636
|
"AGENTS.md",
|
|
514
637
|
"CLAUDE.md",
|
|
515
638
|
".claude/agents/CubedotVerifier.md",
|
|
@@ -523,24 +646,224 @@ async function initCommand(opts) {
|
|
|
523
646
|
console.log("\nOpen Claude Code in this directory to start building. \u2728");
|
|
524
647
|
}
|
|
525
648
|
|
|
526
|
-
// src/commands/
|
|
649
|
+
// src/commands/uninstall.ts
|
|
527
650
|
import pc2 from "picocolors";
|
|
651
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
652
|
+
import { join as join4 } from "path";
|
|
653
|
+
async function uninstallCommand(opts) {
|
|
654
|
+
const cwd = process.cwd();
|
|
655
|
+
const hasCubedot = existsSync4(join4(cwd, ".mcp.json")) || existsSync4(join4(cwd, ".cubedot")) || existsSync4(join4(cwd, ".claude", "agents", "CubedotVerifier.md"));
|
|
656
|
+
if (!hasCubedot) {
|
|
657
|
+
console.log("Nothing to uninstall here.");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const manifest = readInstallManifest(cwd);
|
|
661
|
+
if (manifest) {
|
|
662
|
+
await runManifestUninstall(cwd, manifest, opts.yes ?? false);
|
|
663
|
+
} else {
|
|
664
|
+
await runHeuristicUninstall(cwd, opts.yes ?? false);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async function confirm(skipConfirm) {
|
|
668
|
+
if (skipConfirm) return true;
|
|
669
|
+
if (!process.stdin.isTTY) {
|
|
670
|
+
console.error(pc2.red("Non-interactive shell: pass --yes / -y to skip confirmation."));
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
const { default: input } = await import("@inquirer/input");
|
|
674
|
+
const answer = await input({ message: "Continue? [y/N]" });
|
|
675
|
+
return answer.trim().toLowerCase() === "y";
|
|
676
|
+
}
|
|
677
|
+
function execute(actions) {
|
|
678
|
+
for (const a of actions) {
|
|
679
|
+
try {
|
|
680
|
+
a.fn();
|
|
681
|
+
} catch (err) {
|
|
682
|
+
console.warn(pc2.yellow(` \u26A0 Could not complete "${a.desc}": ${err.message}`));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function runManifestUninstall(cwd, manifest, skipConfirm) {
|
|
687
|
+
const actions = [];
|
|
688
|
+
for (const rel of manifest.createdFiles) {
|
|
689
|
+
const abs = join4(cwd, rel);
|
|
690
|
+
if (existsSync4(abs)) {
|
|
691
|
+
actions.push({ desc: `delete ${rel}`, fn: () => unlinkSync2(abs) });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const claudePath = join4(cwd, "CLAUDE.md");
|
|
695
|
+
if (existsSync4(claudePath)) {
|
|
696
|
+
if (manifest.claudeMd === "created") {
|
|
697
|
+
actions.push({
|
|
698
|
+
desc: "delete CLAUDE.md (created by cubedot init)",
|
|
699
|
+
fn: () => unlinkSync2(claudePath)
|
|
700
|
+
});
|
|
701
|
+
} else {
|
|
702
|
+
actions.push({
|
|
703
|
+
desc: "CLAUDE.md \u2014 remove CUBEDOT marker blocks (preserves your content)",
|
|
704
|
+
fn: () => stripCubedotMarkers(claudePath)
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const agentsPath = join4(cwd, "AGENTS.md");
|
|
709
|
+
if (existsSync4(agentsPath)) {
|
|
710
|
+
if (manifest.agentsMd === "created") {
|
|
711
|
+
actions.push({
|
|
712
|
+
desc: "delete AGENTS.md (created by cubedot init)",
|
|
713
|
+
fn: () => unlinkSync2(agentsPath)
|
|
714
|
+
});
|
|
715
|
+
} else {
|
|
716
|
+
actions.push({
|
|
717
|
+
desc: "AGENTS.md \u2014 remove CUBEDOT marker blocks (preserves your content)",
|
|
718
|
+
fn: () => stripCubedotMarkers(agentsPath)
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const settingsPath = join4(cwd, ".claude", "settings.json");
|
|
723
|
+
if (existsSync4(settingsPath)) {
|
|
724
|
+
if (manifest.settingsCreated) {
|
|
725
|
+
actions.push({
|
|
726
|
+
desc: "delete .claude/settings.json (created by cubedot init)",
|
|
727
|
+
fn: () => unlinkSync2(settingsPath)
|
|
728
|
+
});
|
|
729
|
+
} else {
|
|
730
|
+
actions.push({
|
|
731
|
+
desc: '.claude/settings.json \u2014 remove "cubedot" from enabledMcpjsonServers',
|
|
732
|
+
fn: () => removeCubedotFromSettings(cwd)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (manifest.gitignoreAdded.length > 0 && existsSync4(join4(cwd, ".gitignore"))) {
|
|
737
|
+
actions.push({
|
|
738
|
+
desc: `.gitignore \u2014 remove lines: ${manifest.gitignoreAdded.join(", ")}`,
|
|
739
|
+
fn: () => stripGitignoreLines(cwd, manifest.gitignoreAdded)
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const manifestAbs = join4(cwd, MANIFEST_PATH);
|
|
743
|
+
if (existsSync4(manifestAbs)) {
|
|
744
|
+
actions.push({
|
|
745
|
+
desc: `delete ${MANIFEST_PATH}`,
|
|
746
|
+
fn: () => unlinkSync2(manifestAbs)
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (actions.length === 0) {
|
|
750
|
+
console.log("Nothing to uninstall here.");
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
printPlan(actions, true);
|
|
754
|
+
if (!await confirm(skipConfirm)) {
|
|
755
|
+
console.log("Aborted.");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
execute(actions);
|
|
759
|
+
const dirsToTry = [...manifest.createdDirs].reverse();
|
|
760
|
+
for (const rel of dirsToTry) {
|
|
761
|
+
tryRmdir(join4(cwd, rel));
|
|
762
|
+
}
|
|
763
|
+
console.log("\n" + pc2.green("\u2713") + " Uninstall complete. This folder is no longer connected to Cubedot.");
|
|
764
|
+
}
|
|
765
|
+
async function runHeuristicUninstall(cwd, skipConfirm) {
|
|
766
|
+
const actions = [];
|
|
767
|
+
const mcpPath = join4(cwd, ".mcp.json");
|
|
768
|
+
if (existsSync4(mcpPath)) {
|
|
769
|
+
actions.push({ desc: "delete .mcp.json", fn: () => unlinkSync2(mcpPath) });
|
|
770
|
+
}
|
|
771
|
+
const cubedotDir = join4(cwd, ".cubedot");
|
|
772
|
+
if (existsSync4(cubedotDir)) {
|
|
773
|
+
actions.push({
|
|
774
|
+
desc: "delete .cubedot/ (entire directory)",
|
|
775
|
+
fn: () => rmSync2(cubedotDir, { recursive: true, force: true })
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
const verifierPath = join4(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
779
|
+
if (existsSync4(verifierPath)) {
|
|
780
|
+
actions.push({
|
|
781
|
+
desc: "delete .claude/agents/CubedotVerifier.md",
|
|
782
|
+
fn: () => unlinkSync2(verifierPath)
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const settingsPath = join4(cwd, ".claude", "settings.json");
|
|
786
|
+
if (existsSync4(settingsPath)) {
|
|
787
|
+
actions.push({
|
|
788
|
+
desc: '.claude/settings.json \u2014 remove "cubedot" from enabledMcpjsonServers',
|
|
789
|
+
fn: () => removeCubedotFromSettings(cwd)
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const gitignorePath = join4(cwd, ".gitignore");
|
|
793
|
+
if (existsSync4(gitignorePath)) {
|
|
794
|
+
const content = readFileSync4(gitignorePath, "utf8");
|
|
795
|
+
const lines = content.split("\n");
|
|
796
|
+
if (GITIGNORE_ENTRIES.some((e) => lines.some((l) => l.trim() === e))) {
|
|
797
|
+
actions.push({
|
|
798
|
+
desc: ".gitignore \u2014 remove cubedot lines",
|
|
799
|
+
fn: () => stripGitignoreLines(cwd, GITIGNORE_ENTRIES)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const claudePath = join4(cwd, "CLAUDE.md");
|
|
804
|
+
if (existsSync4(claudePath)) {
|
|
805
|
+
const content = readFileSync4(claudePath, "utf8");
|
|
806
|
+
if (content.includes("<!-- CUBEDOT:START:")) {
|
|
807
|
+
actions.push({
|
|
808
|
+
desc: "delete CLAUDE.md (contains cubedot markers \u2014 treated as generated)",
|
|
809
|
+
fn: () => unlinkSync2(claudePath)
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const agentsPath = join4(cwd, "AGENTS.md");
|
|
814
|
+
if (existsSync4(agentsPath)) {
|
|
815
|
+
const content = readFileSync4(agentsPath, "utf8");
|
|
816
|
+
if (content.includes("<!-- CUBEDOT:START:")) {
|
|
817
|
+
actions.push({
|
|
818
|
+
desc: "delete AGENTS.md (contains cubedot markers \u2014 treated as generated)",
|
|
819
|
+
fn: () => unlinkSync2(agentsPath)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (actions.length === 0) {
|
|
824
|
+
console.log("Nothing to uninstall here.");
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
printPlan(actions, false);
|
|
828
|
+
if (!await confirm(skipConfirm)) {
|
|
829
|
+
console.log("Aborted.");
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
execute(actions);
|
|
833
|
+
tryRmdir(join4(cwd, ".claude", "agents"));
|
|
834
|
+
tryRmdir(join4(cwd, ".claude"));
|
|
835
|
+
console.log("\n" + pc2.green("\u2713") + " Uninstall complete. This folder is no longer connected to Cubedot.");
|
|
836
|
+
}
|
|
837
|
+
function printPlan(actions, hasManifest) {
|
|
838
|
+
const mode = hasManifest ? "manifest-based (precise)" : "heuristic (no manifest found)";
|
|
839
|
+
console.log(pc2.bold(`
|
|
840
|
+
Cubedot uninstall \u2014 ${mode}
|
|
841
|
+
`));
|
|
842
|
+
console.log("The following changes will be made:\n");
|
|
843
|
+
for (const a of actions) {
|
|
844
|
+
console.log(" " + pc2.dim("\u2022") + " " + a.desc);
|
|
845
|
+
}
|
|
846
|
+
console.log("");
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/commands/sync.ts
|
|
850
|
+
import pc3 from "picocolors";
|
|
528
851
|
import { createHash } from "crypto";
|
|
529
|
-
import { existsSync as
|
|
530
|
-
import { join as
|
|
852
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
853
|
+
import { join as join6 } from "path";
|
|
531
854
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
532
855
|
import { dirname as dirname3 } from "path";
|
|
533
856
|
|
|
534
857
|
// src/ledger.ts
|
|
535
858
|
import {
|
|
536
|
-
existsSync as
|
|
859
|
+
existsSync as existsSync5,
|
|
537
860
|
mkdirSync as mkdirSync2,
|
|
538
|
-
readFileSync as
|
|
539
|
-
readdirSync,
|
|
861
|
+
readFileSync as readFileSync5,
|
|
862
|
+
readdirSync as readdirSync2,
|
|
540
863
|
renameSync,
|
|
541
864
|
writeFileSync as writeFileSync3
|
|
542
865
|
} from "fs";
|
|
543
|
-
import { join as
|
|
866
|
+
import { join as join5 } from "path";
|
|
544
867
|
import { randomUUID } from "crypto";
|
|
545
868
|
var FN_CODE_REGEX = /^FN-\d{3}-\d{2,}$/;
|
|
546
869
|
function validateFnCode(code) {
|
|
@@ -551,13 +874,13 @@ function validateFnCode(code) {
|
|
|
551
874
|
}
|
|
552
875
|
}
|
|
553
876
|
function getLedgerPath(cwd, code) {
|
|
554
|
-
return
|
|
877
|
+
return join5(cwd, ".cubedot", "progress", `${code}.json`);
|
|
555
878
|
}
|
|
556
879
|
function readLedger(cwd, code) {
|
|
557
880
|
const path = getLedgerPath(cwd, code);
|
|
558
|
-
if (!
|
|
881
|
+
if (!existsSync5(path)) return null;
|
|
559
882
|
try {
|
|
560
|
-
return JSON.parse(
|
|
883
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
561
884
|
} catch {
|
|
562
885
|
throw new Error(
|
|
563
886
|
`Failed to read ledger for ${code}: file exists but contains malformed JSON.`
|
|
@@ -565,11 +888,11 @@ function readLedger(cwd, code) {
|
|
|
565
888
|
}
|
|
566
889
|
}
|
|
567
890
|
function readAllLedgers(cwd) {
|
|
568
|
-
const dir =
|
|
569
|
-
if (!
|
|
570
|
-
return
|
|
891
|
+
const dir = join5(cwd, ".cubedot", "progress");
|
|
892
|
+
if (!existsSync5(dir)) return [];
|
|
893
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
571
894
|
try {
|
|
572
|
-
return JSON.parse(
|
|
895
|
+
return JSON.parse(readFileSync5(join5(dir, f), "utf8"));
|
|
573
896
|
} catch {
|
|
574
897
|
return null;
|
|
575
898
|
}
|
|
@@ -577,8 +900,8 @@ function readAllLedgers(cwd) {
|
|
|
577
900
|
}
|
|
578
901
|
function writeLedger(cwd, record) {
|
|
579
902
|
const path = getLedgerPath(cwd, record.code);
|
|
580
|
-
const dir =
|
|
581
|
-
if (!
|
|
903
|
+
const dir = join5(cwd, ".cubedot", "progress");
|
|
904
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
582
905
|
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
583
906
|
writeFileSync3(tmp, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
584
907
|
renameSync(tmp, path);
|
|
@@ -671,12 +994,12 @@ function doneFn(cwd, code, actor = "human", force = false, discrepancies = []) {
|
|
|
671
994
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
672
995
|
var __dirname3 = dirname3(__filename2);
|
|
673
996
|
var pkg2 = JSON.parse(
|
|
674
|
-
|
|
997
|
+
readFileSync6(join6(__dirname3, "../package.json"), "utf8")
|
|
675
998
|
);
|
|
676
999
|
function fileHash(filePath) {
|
|
677
|
-
if (!
|
|
1000
|
+
if (!existsSync6(filePath)) return "";
|
|
678
1001
|
try {
|
|
679
|
-
return createHash("sha256").update(
|
|
1002
|
+
return createHash("sha256").update(readFileSync6(filePath)).digest("hex");
|
|
680
1003
|
} catch {
|
|
681
1004
|
return "";
|
|
682
1005
|
}
|
|
@@ -686,7 +1009,7 @@ async function syncCommand() {
|
|
|
686
1009
|
const config = readCubedotConfig(cwd);
|
|
687
1010
|
if (!config) {
|
|
688
1011
|
console.error(
|
|
689
|
-
|
|
1012
|
+
pc3.red(
|
|
690
1013
|
"Error: .cubedot/config.json not found. Run `cubedot init` first."
|
|
691
1014
|
)
|
|
692
1015
|
);
|
|
@@ -697,26 +1020,26 @@ async function syncCommand() {
|
|
|
697
1020
|
const ageDays = (Date.now() - new Date(config.lastSynced).getTime()) / 864e5;
|
|
698
1021
|
if (ageDays >= STALE_DAYS) {
|
|
699
1022
|
console.log(
|
|
700
|
-
|
|
1023
|
+
pc3.yellow(
|
|
701
1024
|
`\u26A0 Last synced ${Math.floor(ageDays)} day(s) ago \u2014 refreshing now.`
|
|
702
1025
|
)
|
|
703
1026
|
);
|
|
704
1027
|
}
|
|
705
1028
|
}
|
|
706
1029
|
const connectUrl = buildConnectUrl(config.mcpUrl, config.projectId);
|
|
707
|
-
console.log(
|
|
1030
|
+
console.log(pc3.dim(`Connecting to ${connectUrl} \u2026`));
|
|
708
1031
|
let client;
|
|
709
1032
|
try {
|
|
710
1033
|
client = await createMcpClient(connectUrl, await readKeyFromMcpJson(cwd));
|
|
711
1034
|
} catch (err) {
|
|
712
|
-
console.error(
|
|
1035
|
+
console.error(pc3.red(`Error: ${categorizeError(err)}`));
|
|
713
1036
|
process.exit(1);
|
|
714
1037
|
}
|
|
715
1038
|
try {
|
|
716
1039
|
await validateTools(client);
|
|
717
1040
|
} catch (err) {
|
|
718
1041
|
await client.close();
|
|
719
|
-
console.error(
|
|
1042
|
+
console.error(pc3.red(`Error: ${err.message}`));
|
|
720
1043
|
process.exit(1);
|
|
721
1044
|
}
|
|
722
1045
|
let orientText = "";
|
|
@@ -734,7 +1057,7 @@ async function syncCommand() {
|
|
|
734
1057
|
} catch (err) {
|
|
735
1058
|
await client.close();
|
|
736
1059
|
console.error(
|
|
737
|
-
|
|
1060
|
+
pc3.red(`Error fetching project data: ${err.message}`)
|
|
738
1061
|
);
|
|
739
1062
|
process.exit(1);
|
|
740
1063
|
} finally {
|
|
@@ -761,7 +1084,7 @@ async function syncCommand() {
|
|
|
761
1084
|
const skipOrphanDetection = specFnCodes.size === 0 && ledgers.length > 0;
|
|
762
1085
|
if (skipOrphanDetection) {
|
|
763
1086
|
console.log(
|
|
764
|
-
|
|
1087
|
+
pc3.yellow(
|
|
765
1088
|
"\u26A0 Spec tree returned no functionalities \u2014 skipping orphan detection this sync."
|
|
766
1089
|
)
|
|
767
1090
|
);
|
|
@@ -777,7 +1100,7 @@ async function syncCommand() {
|
|
|
777
1100
|
if (record.state === "done") {
|
|
778
1101
|
const files = links[record.code] ?? [];
|
|
779
1102
|
for (const filePath of files) {
|
|
780
|
-
const absPath =
|
|
1103
|
+
const absPath = join6(cwd, filePath);
|
|
781
1104
|
const newHash = fileHash(absPath);
|
|
782
1105
|
const oldHash = prevHashes[filePath] ?? "";
|
|
783
1106
|
nextHashes[filePath] = newHash;
|
|
@@ -795,35 +1118,35 @@ async function syncCommand() {
|
|
|
795
1118
|
}
|
|
796
1119
|
for (const [code, files] of Object.entries(links)) {
|
|
797
1120
|
for (const filePath of files) {
|
|
798
|
-
const absPath =
|
|
1121
|
+
const absPath = join6(cwd, filePath);
|
|
799
1122
|
nextHashes[filePath] = fileHash(absPath);
|
|
800
1123
|
}
|
|
801
1124
|
}
|
|
802
1125
|
writeHashes(cwd, nextHashes);
|
|
803
|
-
console.log(
|
|
1126
|
+
console.log(pc3.green("\u2713") + " Sync complete.");
|
|
804
1127
|
if (orphaned.length > 0) {
|
|
805
1128
|
console.log(
|
|
806
|
-
|
|
1129
|
+
pc3.yellow(` Orphaned (removed from spec): ${orphaned.join(", ")}`)
|
|
807
1130
|
);
|
|
808
1131
|
}
|
|
809
1132
|
if (needsReverification.length > 0) {
|
|
810
1133
|
console.log(
|
|
811
|
-
|
|
1134
|
+
pc3.yellow(
|
|
812
1135
|
` Needs re-verification (spec changed after done): ${needsReverification.join(", ")}`
|
|
813
1136
|
)
|
|
814
1137
|
);
|
|
815
1138
|
}
|
|
816
1139
|
if (orphaned.length === 0 && needsReverification.length === 0) {
|
|
817
|
-
console.log(
|
|
1140
|
+
console.log(pc3.dim(" No drift detected."));
|
|
818
1141
|
}
|
|
819
|
-
console.log(
|
|
1142
|
+
console.log(pc3.dim(" AGENTS.md and CLAUDE.md updated."));
|
|
820
1143
|
}
|
|
821
1144
|
async function readKeyFromMcpJson(cwd) {
|
|
822
|
-
const mcpPath =
|
|
823
|
-
if (!
|
|
1145
|
+
const mcpPath = join6(cwd, ".mcp.json");
|
|
1146
|
+
if (!existsSync6(mcpPath)) {
|
|
824
1147
|
throw new Error(".mcp.json not found. Run `cubedot init` first.");
|
|
825
1148
|
}
|
|
826
|
-
const mcp = JSON.parse(
|
|
1149
|
+
const mcp = JSON.parse(readFileSync6(mcpPath, "utf8"));
|
|
827
1150
|
const headers = mcp.mcpServers?.["cubedot"]?.headers ?? {};
|
|
828
1151
|
const authHeader = headers["Authorization"] ?? headers["authorization"] ?? "";
|
|
829
1152
|
const key = authHeader.replace(/^ApiKey\s+/i, "").trim();
|
|
@@ -836,90 +1159,90 @@ async function readKeyFromMcpJson(cwd) {
|
|
|
836
1159
|
}
|
|
837
1160
|
|
|
838
1161
|
// src/commands/status.ts
|
|
839
|
-
import
|
|
840
|
-
import { existsSync as
|
|
841
|
-
import { join as
|
|
1162
|
+
import pc4 from "picocolors";
|
|
1163
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1164
|
+
import { join as join7 } from "path";
|
|
842
1165
|
async function statusCommand() {
|
|
843
1166
|
const cwd = process.cwd();
|
|
844
1167
|
let ok = true;
|
|
845
1168
|
const config = readCubedotConfig(cwd);
|
|
846
1169
|
if (!config) {
|
|
847
|
-
console.log(
|
|
1170
|
+
console.log(pc4.red("\u2717") + " .cubedot/config.json \u2014 missing (run `cubedot init`)");
|
|
848
1171
|
ok = false;
|
|
849
1172
|
} else {
|
|
850
|
-
console.log(
|
|
851
|
-
console.log(
|
|
1173
|
+
console.log(pc4.green("\u2713") + " .cubedot/config.json");
|
|
1174
|
+
console.log(pc4.dim(` projectId: ${config.projectId}`));
|
|
852
1175
|
if (config.lastSynced) {
|
|
853
1176
|
const ageDays = (Date.now() - new Date(config.lastSynced).getTime()) / 864e5;
|
|
854
1177
|
const ageStr = ageDays < 1 ? "today" : `${Math.floor(ageDays)} day(s) ago`;
|
|
855
1178
|
const stale = ageDays >= 3;
|
|
856
1179
|
console.log(
|
|
857
|
-
(stale ?
|
|
1180
|
+
(stale ? pc4.yellow("\u26A0 ") : pc4.dim(" ")) + ` Last synced: ${ageStr}${stale ? " \u2014 consider running `cubedot sync`" : ""}`
|
|
858
1181
|
);
|
|
859
1182
|
}
|
|
860
1183
|
}
|
|
861
|
-
const mcpPath =
|
|
862
|
-
if (!
|
|
863
|
-
console.log(
|
|
1184
|
+
const mcpPath = join7(cwd, ".mcp.json");
|
|
1185
|
+
if (!existsSync7(mcpPath)) {
|
|
1186
|
+
console.log(pc4.red("\u2717") + " .mcp.json \u2014 missing");
|
|
864
1187
|
ok = false;
|
|
865
1188
|
} else {
|
|
866
1189
|
try {
|
|
867
|
-
const mcp = JSON.parse(
|
|
1190
|
+
const mcp = JSON.parse(readFileSync7(mcpPath, "utf8"));
|
|
868
1191
|
const headers = mcp.mcpServers?.["cubedot"]?.headers ?? {};
|
|
869
1192
|
const authHeader = headers["Authorization"] ?? headers["authorization"] ?? "";
|
|
870
1193
|
const keyRaw = authHeader.replace(/^ApiKey\s+/i, "").trim();
|
|
871
1194
|
const keyTail = keyRaw ? keyRaw.slice(-8) : "(empty)";
|
|
872
|
-
console.log(
|
|
1195
|
+
console.log(pc4.green("\u2713") + ` .mcp.json \u2014 key tail: \u2026${keyTail}`);
|
|
873
1196
|
} catch {
|
|
874
|
-
console.log(
|
|
1197
|
+
console.log(pc4.red("\u2717") + " .mcp.json \u2014 malformed JSON");
|
|
875
1198
|
ok = false;
|
|
876
1199
|
}
|
|
877
1200
|
}
|
|
878
1201
|
for (const file of ["AGENTS.md", "CLAUDE.md"]) {
|
|
879
|
-
const path =
|
|
880
|
-
if (!
|
|
881
|
-
console.log(
|
|
1202
|
+
const path = join7(cwd, file);
|
|
1203
|
+
if (!existsSync7(path)) {
|
|
1204
|
+
console.log(pc4.red("\u2717") + ` ${file} \u2014 missing`);
|
|
882
1205
|
ok = false;
|
|
883
1206
|
} else {
|
|
884
|
-
const content =
|
|
1207
|
+
const content = readFileSync7(path, "utf8");
|
|
885
1208
|
const markers = hasAllMarkers(content);
|
|
886
1209
|
console.log(
|
|
887
|
-
(markers ?
|
|
1210
|
+
(markers ? pc4.green("\u2713") : pc4.yellow("\u26A0")) + ` ${file}${markers ? "" : " \u2014 markers missing or malformed (run `cubedot sync`)"}`
|
|
888
1211
|
);
|
|
889
1212
|
}
|
|
890
1213
|
}
|
|
891
|
-
const verifier =
|
|
892
|
-
const manual =
|
|
1214
|
+
const verifier = join7(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
1215
|
+
const manual = join7(cwd, ".cubedot", "fia-operating-manual.md");
|
|
893
1216
|
console.log(
|
|
894
|
-
(
|
|
1217
|
+
(existsSync7(verifier) ? pc4.green("\u2713") : pc4.red("\u2717")) + " .claude/agents/CubedotVerifier.md"
|
|
895
1218
|
);
|
|
896
1219
|
console.log(
|
|
897
|
-
(
|
|
1220
|
+
(existsSync7(manual) ? pc4.green("\u2713") : pc4.red("\u2717")) + " .cubedot/fia-operating-manual.md"
|
|
898
1221
|
);
|
|
899
1222
|
const ledgers = readAllLedgers(cwd);
|
|
900
1223
|
if (ledgers.length === 0) {
|
|
901
|
-
console.log(
|
|
1224
|
+
console.log(pc4.dim(" Ledger: no entries yet"));
|
|
902
1225
|
} else {
|
|
903
1226
|
const counts = {};
|
|
904
1227
|
for (const l of ledgers) counts[l.state] = (counts[l.state] ?? 0) + 1;
|
|
905
1228
|
const summary = Object.entries(counts).map(([s, n]) => `${n} ${s}`).join(", ");
|
|
906
|
-
console.log(
|
|
1229
|
+
console.log(pc4.dim(` Ledger: ${ledgers.length} entries (${summary})`));
|
|
907
1230
|
}
|
|
908
1231
|
if (config) {
|
|
909
|
-
console.log(
|
|
1232
|
+
console.log(pc4.dim("\nChecking MCP connectivity \u2026"));
|
|
910
1233
|
try {
|
|
911
1234
|
const key = await readKeyFromMcpJson(cwd);
|
|
912
1235
|
const connectUrl = buildConnectUrl(config.mcpUrl, config.projectId);
|
|
913
1236
|
const client = await createMcpClient(connectUrl, key);
|
|
914
1237
|
try {
|
|
915
1238
|
await validateTools(client);
|
|
916
|
-
console.log(
|
|
1239
|
+
console.log(pc4.green("\u2713") + " MCP server reachable \u2014 all 7 tools present");
|
|
917
1240
|
} finally {
|
|
918
1241
|
await client.close();
|
|
919
1242
|
}
|
|
920
1243
|
} catch (err) {
|
|
921
1244
|
console.log(
|
|
922
|
-
|
|
1245
|
+
pc4.red("\u2717") + " MCP server: " + categorizeError(err)
|
|
923
1246
|
);
|
|
924
1247
|
ok = false;
|
|
925
1248
|
}
|
|
@@ -928,9 +1251,9 @@ async function statusCommand() {
|
|
|
928
1251
|
}
|
|
929
1252
|
|
|
930
1253
|
// src/commands/check.ts
|
|
931
|
-
import
|
|
932
|
-
import { existsSync as
|
|
933
|
-
import { join as
|
|
1254
|
+
import pc5 from "picocolors";
|
|
1255
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
1256
|
+
import { join as join8 } from "path";
|
|
934
1257
|
async function checkCommand() {
|
|
935
1258
|
const cwd = process.cwd();
|
|
936
1259
|
const issues = [];
|
|
@@ -938,12 +1261,12 @@ async function checkCommand() {
|
|
|
938
1261
|
if (!config) {
|
|
939
1262
|
issues.push(".cubedot/config.json missing \u2014 run `cubedot init`");
|
|
940
1263
|
}
|
|
941
|
-
const mcpPath =
|
|
942
|
-
if (!
|
|
1264
|
+
const mcpPath = join8(cwd, ".mcp.json");
|
|
1265
|
+
if (!existsSync8(mcpPath)) {
|
|
943
1266
|
issues.push(".mcp.json missing \u2014 run `cubedot init`");
|
|
944
1267
|
} else {
|
|
945
1268
|
try {
|
|
946
|
-
const mcp = JSON.parse(
|
|
1269
|
+
const mcp = JSON.parse(readFileSync8(mcpPath, "utf8"));
|
|
947
1270
|
if (!mcp.mcpServers?.["cubedot"]) {
|
|
948
1271
|
issues.push(
|
|
949
1272
|
".mcp.json missing mcpServers.cubedot \u2014 run `cubedot init`"
|
|
@@ -953,106 +1276,106 @@ async function checkCommand() {
|
|
|
953
1276
|
issues.push(".mcp.json contains malformed JSON");
|
|
954
1277
|
}
|
|
955
1278
|
}
|
|
956
|
-
const agentsPath =
|
|
957
|
-
if (!
|
|
1279
|
+
const agentsPath = join8(cwd, "AGENTS.md");
|
|
1280
|
+
if (!existsSync8(agentsPath)) {
|
|
958
1281
|
issues.push("AGENTS.md missing \u2014 run `cubedot init`");
|
|
959
1282
|
} else {
|
|
960
|
-
const content =
|
|
1283
|
+
const content = readFileSync8(agentsPath, "utf8");
|
|
961
1284
|
if (!hasAllMarkers(content)) {
|
|
962
1285
|
issues.push(
|
|
963
1286
|
"AGENTS.md is missing or has malformed CUBEDOT markers \u2014 run `cubedot sync`"
|
|
964
1287
|
);
|
|
965
1288
|
}
|
|
966
1289
|
}
|
|
967
|
-
const claudePath =
|
|
968
|
-
if (!
|
|
1290
|
+
const claudePath = join8(cwd, "CLAUDE.md");
|
|
1291
|
+
if (!existsSync8(claudePath)) {
|
|
969
1292
|
issues.push("CLAUDE.md missing \u2014 run `cubedot init`");
|
|
970
1293
|
} else {
|
|
971
|
-
const content =
|
|
1294
|
+
const content = readFileSync8(claudePath, "utf8");
|
|
972
1295
|
if (!hasAllMarkers(content)) {
|
|
973
1296
|
issues.push(
|
|
974
1297
|
"CLAUDE.md is missing or has malformed CUBEDOT markers \u2014 run `cubedot sync`"
|
|
975
1298
|
);
|
|
976
1299
|
}
|
|
977
1300
|
}
|
|
978
|
-
const verifier =
|
|
979
|
-
if (!
|
|
1301
|
+
const verifier = join8(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
1302
|
+
if (!existsSync8(verifier)) {
|
|
980
1303
|
issues.push(
|
|
981
1304
|
".claude/agents/CubedotVerifier.md missing \u2014 run `cubedot init`"
|
|
982
1305
|
);
|
|
983
1306
|
}
|
|
984
|
-
const manual =
|
|
985
|
-
if (!
|
|
1307
|
+
const manual = join8(cwd, ".cubedot", "fia-operating-manual.md");
|
|
1308
|
+
if (!existsSync8(manual)) {
|
|
986
1309
|
issues.push(
|
|
987
1310
|
".cubedot/fia-operating-manual.md missing \u2014 run `cubedot init`"
|
|
988
1311
|
);
|
|
989
1312
|
}
|
|
990
1313
|
for (const dir of [".cubedot/progress", ".cubedot/reports"]) {
|
|
991
|
-
if (!
|
|
1314
|
+
if (!existsSync8(join8(cwd, dir))) {
|
|
992
1315
|
issues.push(`${dir}/ missing \u2014 run 'cubedot init'`);
|
|
993
1316
|
}
|
|
994
1317
|
}
|
|
995
|
-
if (
|
|
996
|
-
const content =
|
|
997
|
-
if (content.includes("fia-operating-manual") && !
|
|
1318
|
+
if (existsSync8(claudePath)) {
|
|
1319
|
+
const content = readFileSync8(claudePath, "utf8");
|
|
1320
|
+
if (content.includes("fia-operating-manual") && !existsSync8(join8(cwd, ".cubedot", "fia-operating-manual.md"))) {
|
|
998
1321
|
issues.push(
|
|
999
1322
|
"CLAUDE.md references .cubedot/fia-operating-manual.md but it is missing"
|
|
1000
1323
|
);
|
|
1001
1324
|
}
|
|
1002
1325
|
}
|
|
1003
1326
|
if (issues.length === 0) {
|
|
1004
|
-
console.log(
|
|
1327
|
+
console.log(pc5.green("\u2713") + " All checks passed.");
|
|
1005
1328
|
process.exit(0);
|
|
1006
1329
|
} else {
|
|
1007
|
-
console.log(
|
|
1330
|
+
console.log(pc5.red(`\u2717 ${issues.length} issue(s) found:
|
|
1008
1331
|
`));
|
|
1009
1332
|
for (const issue of issues) {
|
|
1010
|
-
console.log(" " +
|
|
1333
|
+
console.log(" " + pc5.red("\u2022") + " " + issue);
|
|
1011
1334
|
}
|
|
1012
1335
|
process.exit(1);
|
|
1013
1336
|
}
|
|
1014
1337
|
}
|
|
1015
1338
|
|
|
1016
1339
|
// src/commands/start.ts
|
|
1017
|
-
import
|
|
1340
|
+
import pc6 from "picocolors";
|
|
1018
1341
|
function startCommand(fn, opts) {
|
|
1019
1342
|
const cwd = process.cwd();
|
|
1020
1343
|
const actor = opts.actor === "human" ? "human" : "agent";
|
|
1021
1344
|
try {
|
|
1022
1345
|
const record = startFn(cwd, fn, actor);
|
|
1023
1346
|
console.log(
|
|
1024
|
-
|
|
1347
|
+
pc6.green("\u2713") + ` ${fn} \u2192 in_progress (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1025
1348
|
);
|
|
1026
|
-
console.log(
|
|
1349
|
+
console.log(pc6.dim(` Ledger: .cubedot/progress/${fn}.json`));
|
|
1027
1350
|
} catch (err) {
|
|
1028
|
-
console.error(
|
|
1351
|
+
console.error(pc6.red(`Error: ${err.message}`));
|
|
1029
1352
|
process.exit(1);
|
|
1030
1353
|
}
|
|
1031
1354
|
}
|
|
1032
1355
|
|
|
1033
1356
|
// src/commands/complete.ts
|
|
1034
|
-
import
|
|
1357
|
+
import pc7 from "picocolors";
|
|
1035
1358
|
function completeCommand(fn, opts) {
|
|
1036
1359
|
const cwd = process.cwd();
|
|
1037
1360
|
const actor = opts.actor === "human" ? "human" : "agent";
|
|
1038
1361
|
try {
|
|
1039
1362
|
const record = completeFn(cwd, fn, actor);
|
|
1040
1363
|
console.log(
|
|
1041
|
-
|
|
1364
|
+
pc7.green("\u2713") + ` ${fn} \u2192 code-complete (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1042
1365
|
);
|
|
1043
1366
|
console.log(
|
|
1044
|
-
|
|
1367
|
+
pc7.dim(
|
|
1045
1368
|
` Next: spawn CubedotVerifier, then ask the developer to run \`cubedot done ${fn}\`.`
|
|
1046
1369
|
)
|
|
1047
1370
|
);
|
|
1048
1371
|
} catch (err) {
|
|
1049
|
-
console.error(
|
|
1372
|
+
console.error(pc7.red(`Error: ${err.message}`));
|
|
1050
1373
|
process.exit(1);
|
|
1051
1374
|
}
|
|
1052
1375
|
}
|
|
1053
1376
|
|
|
1054
1377
|
// src/commands/done.ts
|
|
1055
|
-
import
|
|
1378
|
+
import pc8 from "picocolors";
|
|
1056
1379
|
function doneCommand(fn, opts) {
|
|
1057
1380
|
const cwd = process.cwd();
|
|
1058
1381
|
const actor = opts.actor === "agent" ? "agent" : "human";
|
|
@@ -1064,43 +1387,43 @@ function doneCommand(fn, opts) {
|
|
|
1064
1387
|
try {
|
|
1065
1388
|
const record = doneFn(cwd, fn, actor, force, discrepancies);
|
|
1066
1389
|
console.log(
|
|
1067
|
-
|
|
1390
|
+
pc8.green("\u2713") + ` ${fn} \u2192 done (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1068
1391
|
);
|
|
1069
1392
|
if (record.overrides.length > 0) {
|
|
1070
1393
|
console.log(
|
|
1071
|
-
|
|
1394
|
+
pc8.yellow(
|
|
1072
1395
|
` \u26A0 Override recorded: ${record.overrides[record.overrides.length - 1].condition}`
|
|
1073
1396
|
)
|
|
1074
1397
|
);
|
|
1075
1398
|
}
|
|
1076
1399
|
if (discrepancies.length > 0) {
|
|
1077
1400
|
console.log(
|
|
1078
|
-
|
|
1401
|
+
pc8.yellow(
|
|
1079
1402
|
` \u26A0 ${discrepancies.length} discrepancy(ies) recorded (criteria accepted unmet):`
|
|
1080
1403
|
)
|
|
1081
1404
|
);
|
|
1082
1405
|
for (const d of discrepancies) {
|
|
1083
|
-
console.log(
|
|
1406
|
+
console.log(pc8.yellow(` - ${d.criterion}`));
|
|
1084
1407
|
}
|
|
1085
1408
|
}
|
|
1086
1409
|
} catch (err) {
|
|
1087
|
-
console.error(
|
|
1410
|
+
console.error(pc8.red(`Error: ${err.message}`));
|
|
1088
1411
|
process.exit(1);
|
|
1089
1412
|
}
|
|
1090
1413
|
}
|
|
1091
1414
|
|
|
1092
1415
|
// src/commands/progress.ts
|
|
1093
|
-
import
|
|
1094
|
-
import { existsSync as
|
|
1095
|
-
import { join as
|
|
1416
|
+
import pc9 from "picocolors";
|
|
1417
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1418
|
+
import { join as join9 } from "path";
|
|
1096
1419
|
function progressCommand() {
|
|
1097
1420
|
const cwd = process.cwd();
|
|
1098
1421
|
const ledgers = readAllLedgers(cwd);
|
|
1099
1422
|
let fnNames = /* @__PURE__ */ new Map();
|
|
1100
1423
|
for (const file of ["AGENTS.md", "CLAUDE.md"]) {
|
|
1101
|
-
const path =
|
|
1102
|
-
if (
|
|
1103
|
-
fnNames = parseRegistryBlock(
|
|
1424
|
+
const path = join9(cwd, file);
|
|
1425
|
+
if (existsSync9(path)) {
|
|
1426
|
+
fnNames = parseRegistryBlock(readFileSync9(path, "utf8"));
|
|
1104
1427
|
if (fnNames.size > 0) break;
|
|
1105
1428
|
}
|
|
1106
1429
|
}
|
|
@@ -1119,24 +1442,24 @@ function progressCommand() {
|
|
|
1119
1442
|
const ccCount = byState["code-complete"].length;
|
|
1120
1443
|
const ipCount = byState["in_progress"].length;
|
|
1121
1444
|
const todoCount = Math.max(0, total - doneCount - ccCount - ipCount);
|
|
1122
|
-
console.log("\n" +
|
|
1445
|
+
console.log("\n" + pc9.bold("Progress Summary"));
|
|
1123
1446
|
console.log("\u2501".repeat(40));
|
|
1124
|
-
console.log(` Done: ${
|
|
1447
|
+
console.log(` Done: ${pc9.green(String(doneCount))}/${total}`);
|
|
1125
1448
|
if (ccCount > 0)
|
|
1126
|
-
console.log(` Code-complete: ${
|
|
1449
|
+
console.log(` Code-complete: ${pc9.cyan(String(ccCount))}/${total}`);
|
|
1127
1450
|
if (ipCount > 0)
|
|
1128
|
-
console.log(` In progress: ${
|
|
1451
|
+
console.log(` In progress: ${pc9.yellow(String(ipCount))}/${total}`);
|
|
1129
1452
|
if (todoCount > 0)
|
|
1130
|
-
console.log(` Todo: ${
|
|
1453
|
+
console.log(` Todo: ${pc9.dim(String(todoCount))}/${total}`);
|
|
1131
1454
|
const active = [
|
|
1132
1455
|
...byState["in_progress"],
|
|
1133
1456
|
...byState["code-complete"]
|
|
1134
1457
|
];
|
|
1135
1458
|
if (active.length > 0) {
|
|
1136
|
-
console.log("\n" +
|
|
1459
|
+
console.log("\n" + pc9.bold("Active:"));
|
|
1137
1460
|
for (const l of active) {
|
|
1138
1461
|
const name = fnNames.get(l.code) ? ` \u2014 ${fnNames.get(l.code)}` : "";
|
|
1139
|
-
const stateLabel = l.state === "in_progress" ?
|
|
1462
|
+
const stateLabel = l.state === "in_progress" ? pc9.yellow("in_progress") : pc9.cyan("code-complete");
|
|
1140
1463
|
console.log(` ${l.code}${name} [${stateLabel}]`);
|
|
1141
1464
|
}
|
|
1142
1465
|
}
|
|
@@ -1145,21 +1468,21 @@ function progressCommand() {
|
|
|
1145
1468
|
const registryOrder = Array.from(fnNames.keys());
|
|
1146
1469
|
const nextCode = registryOrder.find((code) => !ledgerCodes.has(code));
|
|
1147
1470
|
if (nextCode) {
|
|
1148
|
-
console.log("\n" +
|
|
1471
|
+
console.log("\n" + pc9.bold("Next unblocked:"));
|
|
1149
1472
|
console.log(` ${nextCode} \u2014 ${fnNames.get(nextCode) ?? ""}`);
|
|
1150
1473
|
}
|
|
1151
1474
|
}
|
|
1152
1475
|
if (flagged.length > 0) {
|
|
1153
|
-
console.log("\n" +
|
|
1476
|
+
console.log("\n" + pc9.bold(pc9.yellow("Flagged:")));
|
|
1154
1477
|
for (const l of flagged) {
|
|
1155
1478
|
console.log(` ${l.code} [${l.flags.join(", ")}]`);
|
|
1156
1479
|
}
|
|
1157
1480
|
}
|
|
1158
1481
|
if (byState["done"].length > 0) {
|
|
1159
|
-
console.log("\n" +
|
|
1482
|
+
console.log("\n" + pc9.bold("Done:"));
|
|
1160
1483
|
for (const l of byState["done"]) {
|
|
1161
1484
|
const name = fnNames.get(l.code) ? ` \u2014 ${fnNames.get(l.code)}` : "";
|
|
1162
|
-
console.log(` ${
|
|
1485
|
+
console.log(` ${pc9.green("\u2713")} ${l.code}${name}`);
|
|
1163
1486
|
}
|
|
1164
1487
|
}
|
|
1165
1488
|
console.log("");
|
|
@@ -1169,15 +1492,16 @@ function progressCommand() {
|
|
|
1169
1492
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
1170
1493
|
var __dirname4 = dirname4(__filename3);
|
|
1171
1494
|
var pkg3 = JSON.parse(
|
|
1172
|
-
|
|
1495
|
+
readFileSync10(join10(__dirname4, "../package.json"), "utf8")
|
|
1173
1496
|
);
|
|
1174
1497
|
var program = new Command();
|
|
1175
1498
|
var collect = (value, prev) => [...prev, value];
|
|
1176
1499
|
program.name("cubedot").description(
|
|
1177
1500
|
"Connect your repo to the Cubedot MCP and drive the spec-first build loop."
|
|
1178
1501
|
).version(pkg3.version);
|
|
1179
|
-
program.command("init").description("Connect and scaffold the repo (validates MCP, writes config + hot-memory files).").option("--token <token>", "Base64url connect token {v:1, projectId, key, mcpUrl}").option("--project <projectId>", "Project ID (alternative to --token)").option("--key <key>", "MCP API key (alternative to --token)").option("--url <url>", "MCP URL (alternative to --token)").action(initCommand);
|
|
1502
|
+
program.command("init").description("Connect and scaffold the repo (validates MCP, writes config + hot-memory files).").option("--token <token>", "Base64url connect token {v:1, projectId, key, mcpUrl}").option("--project <projectId>", "Project ID (alternative to --token)").option("--key <key>", "MCP API key (alternative to --token)").option("--url <url>", "MCP URL (alternative to --token)").option("--force", "Override the self-install guard (run init inside the CLI source repo).").action(initCommand);
|
|
1180
1503
|
program.command("sync").description("Refresh local projection (AGENTS.md/CLAUDE.md markers) and detect drift.").action(syncCommand);
|
|
1504
|
+
program.command("uninstall").description("Remove all cubedot scaffold files from this repo and disconnect from the MCP.").option("-y, --yes", "Skip the confirmation prompt.").action(uninstallCommand);
|
|
1181
1505
|
program.command("status").description("Connectivity ping + file-health check (read-only).").action(statusCommand);
|
|
1182
1506
|
program.command("check").description("Validate markers, config, and server name (read-only; CI-friendly).").action(checkCommand);
|
|
1183
1507
|
program.command("start <fn>").description("Set a functionality to in_progress.").option("--actor <actor>", "Actor performing the action", "agent").action(startCommand);
|