gnosys 5.2.7 → 5.2.8

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 CHANGED
@@ -2465,8 +2465,9 @@ program
2465
2465
  // ─── gnosys upgrade ─────────────────────────────────────────────────────
2466
2466
  program
2467
2467
  .command("upgrade")
2468
- .description("Re-initialize all registered projects after a Gnosys version upgrade. Updates agent rules, project registry, and stamps the central DB with the current version.")
2469
- .action(async () => {
2468
+ .description("Re-initialize all registered projects after a Gnosys version upgrade. Updates agent rules, project registry, stamps the central DB, and regenerates the portfolio dashboard.")
2469
+ .option("--skip-dashboard", "Skip regenerating the portfolio dashboard")
2470
+ .action(async (opts) => {
2470
2471
  const currentVersion = pkg.version;
2471
2472
  console.log(`Gnosys v${currentVersion} — upgrading registered projects...\n`);
2472
2473
  // 1. Read registered projects from file registry AND central DB
@@ -2659,6 +2660,28 @@ program
2659
2660
  console.log(`\nNote: ${skipped.length} project(s) not found on this machine.`);
2660
2661
  console.log(`If they exist on another machine, run 'gnosys upgrade' there too.`);
2661
2662
  }
2663
+ // 6. Regenerate portfolio dashboard
2664
+ if (!opts.skipDashboard) {
2665
+ try {
2666
+ const dashboardPath = path.join(home, "gnosys-dashboard.html");
2667
+ const dashboardMdPath = path.join(home, "gnosys-dashboard.md");
2668
+ const centralDb = GnosysDB.openCentral();
2669
+ if (centralDb.isAvailable()) {
2670
+ const { generatePortfolio, formatPortfolioMarkdown } = await import("./lib/portfolio.js");
2671
+ const { generatePortfolioHtml } = await import("./lib/portfolioHtml.js");
2672
+ const report = generatePortfolio(centralDb);
2673
+ await fs.writeFile(dashboardPath, generatePortfolioHtml(report, dashboardPath), "utf-8");
2674
+ await fs.writeFile(dashboardMdPath, formatPortfolioMarkdown(report), "utf-8");
2675
+ centralDb.close();
2676
+ console.log(`\nPortfolio dashboard regenerated:`);
2677
+ console.log(` HTML: ${dashboardPath}`);
2678
+ console.log(` MD: ${dashboardMdPath}`);
2679
+ }
2680
+ }
2681
+ catch {
2682
+ console.log(`\n Could not regenerate portfolio dashboard`);
2683
+ }
2684
+ }
2662
2685
  });
2663
2686
  // ─── gnosys doctor ──────────────────────────────────────────────────────
2664
2687
  program
@@ -3765,6 +3788,255 @@ program
3765
3788
  centralDb?.close();
3766
3789
  }
3767
3790
  });
3791
+ // ─── gnosys portfolio ───────────────────────────────────────────────────
3792
+ program
3793
+ .command("portfolio")
3794
+ .description("Portfolio dashboard — all projects with status, roadmap, and recent activity")
3795
+ .option("-o, --output <file>", "Write dashboard to a file (auto-detects format from extension)")
3796
+ .option("--html", "Output as HTML dashboard")
3797
+ .option("--json", "Output as JSON")
3798
+ .action(async (opts) => {
3799
+ let centralDb = null;
3800
+ try {
3801
+ centralDb = GnosysDB.openCentral();
3802
+ if (!centralDb.isAvailable()) {
3803
+ console.error("Central DB not available.");
3804
+ process.exit(1);
3805
+ }
3806
+ const { generatePortfolio, formatPortfolioMarkdown } = await import("./lib/portfolio.js");
3807
+ const report = generatePortfolio(centralDb);
3808
+ // Detect format from output extension if not explicitly set
3809
+ const useHtml = opts.html || (opts.output?.endsWith(".html") ?? false);
3810
+ const useJson = opts.json || (opts.output?.endsWith(".json") ?? false);
3811
+ if (useJson) {
3812
+ const json = JSON.stringify(report, null, 2);
3813
+ if (opts.output) {
3814
+ const { writeFileSync } = await import("fs");
3815
+ writeFileSync(opts.output, json, "utf-8");
3816
+ console.log(`Portfolio written to ${opts.output}`);
3817
+ }
3818
+ else {
3819
+ console.log(json);
3820
+ }
3821
+ return;
3822
+ }
3823
+ if (useHtml) {
3824
+ const { generatePortfolioHtml } = await import("./lib/portfolioHtml.js");
3825
+ const html = generatePortfolioHtml(report, opts.output);
3826
+ if (opts.output) {
3827
+ const { writeFileSync } = await import("fs");
3828
+ writeFileSync(opts.output, html, "utf-8");
3829
+ console.log(`Portfolio dashboard written to ${opts.output}`);
3830
+ }
3831
+ else {
3832
+ console.log(html);
3833
+ }
3834
+ return;
3835
+ }
3836
+ const markdown = formatPortfolioMarkdown(report);
3837
+ if (opts.output) {
3838
+ const { writeFileSync } = await import("fs");
3839
+ writeFileSync(opts.output, markdown, "utf-8");
3840
+ console.log(`Portfolio dashboard written to ${opts.output}`);
3841
+ }
3842
+ else {
3843
+ console.log(markdown);
3844
+ }
3845
+ }
3846
+ catch (err) {
3847
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
3848
+ process.exit(1);
3849
+ }
3850
+ finally {
3851
+ centralDb?.close();
3852
+ }
3853
+ });
3854
+ // ─── gnosys status ──────────────────────────────────────────────────────
3855
+ program
3856
+ .command("status")
3857
+ .description("Show project status. From a project dir: shows that project. With --global: shows all projects. With --web: opens the HTML dashboard.")
3858
+ .option("-d, --directory <dir>", "Project directory (auto-detects if omitted)")
3859
+ .option("-p, --project <id>", "Project ID")
3860
+ .option("-g, --global", "Show all projects")
3861
+ .option("-w, --web", "Open the HTML dashboard in the browser")
3862
+ .option("--json", "Output as JSON")
3863
+ .action(async (opts) => {
3864
+ let centralDb = null;
3865
+ try {
3866
+ centralDb = GnosysDB.openCentral();
3867
+ if (!centralDb.isAvailable()) {
3868
+ console.error("Central DB not available.");
3869
+ process.exit(1);
3870
+ }
3871
+ const { detectCurrentProject } = await import("./lib/federated.js");
3872
+ const { generatePortfolio, formatPortfolioMarkdown } = await import("./lib/portfolio.js");
3873
+ const report = generatePortfolio(centralDb);
3874
+ // --web: regenerate HTML dashboard and open it
3875
+ if (opts.web) {
3876
+ const { generatePortfolioHtml } = await import("./lib/portfolioHtml.js");
3877
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
3878
+ const dashboardPath = path.join(home, "gnosys-dashboard.html");
3879
+ const { writeFileSync } = await import("fs");
3880
+ writeFileSync(dashboardPath, generatePortfolioHtml(report, dashboardPath), "utf-8");
3881
+ const { exec } = await import("child_process");
3882
+ exec(`open "${dashboardPath}"`);
3883
+ console.log(`Dashboard opened: ${dashboardPath}`);
3884
+ return;
3885
+ }
3886
+ // --global: show all projects
3887
+ if (opts.global) {
3888
+ if (opts.json) {
3889
+ console.log(JSON.stringify(report, null, 2));
3890
+ return;
3891
+ }
3892
+ console.log(`\n Portfolio — ${report.totalProjects} projects, ${report.totalMemories} memories\n`);
3893
+ // Action items summary
3894
+ if (report.allActionItems.length > 0) {
3895
+ console.log(` \x1b[31mACTION ITEMS (${report.allActionItems.length}):\x1b[0m`);
3896
+ for (const a of report.allActionItems.slice(0, 8)) {
3897
+ const icon = a.type === "question" ? "?" : a.type === "blocker" ? "!" : a.type === "manual" ? ">" : "*";
3898
+ console.log(` [${icon}] ${a.projectName}: ${a.text.slice(0, 80)}`);
3899
+ }
3900
+ if (report.allActionItems.length > 8)
3901
+ console.log(` ... and ${report.allActionItems.length - 8} more`);
3902
+ console.log("");
3903
+ }
3904
+ // Per-project summary
3905
+ for (const snap of report.projects) {
3906
+ const r = snap.readiness;
3907
+ const color = r.score >= 90 ? "\x1b[32m" : r.score >= 65 ? "\x1b[34m" : r.score >= 40 ? "\x1b[33m" : "\x1b[31m";
3908
+ const reset = "\x1b[0m";
3909
+ const blockers = snap.actionItems.length + r.blocking.length;
3910
+ const blockerStr = blockers > 0 ? ` — \x1b[31m${blockers} blocker${blockers !== 1 ? "s" : ""}\x1b[0m` : "";
3911
+ console.log(` ${color}${String(r.score).padStart(3)}%${reset} ${r.label.padEnd(12)} ${snap.project.name}${blockerStr}`);
3912
+ }
3913
+ console.log(`\n Run 'gnosys status --web' to open the visual dashboard.`);
3914
+ return;
3915
+ }
3916
+ // Single project (default): auto-detect from cwd
3917
+ let pid = opts.project || null;
3918
+ if (!pid)
3919
+ pid = await detectCurrentProject(centralDb, opts.directory || undefined);
3920
+ if (!pid) {
3921
+ console.error("No project detected. Run from a project directory, use --project, or use --global for all.");
3922
+ process.exit(1);
3923
+ }
3924
+ const project = centralDb.getProject(pid);
3925
+ if (!project) {
3926
+ console.error(`Project not found: ${pid}`);
3927
+ process.exit(1);
3928
+ }
3929
+ const snap = report.projects.find((s) => s.project.id === pid);
3930
+ if (!snap) {
3931
+ console.error(`No memories found for project: ${project.name}`);
3932
+ console.log(`\nRun 'gnosys update-status' to create a status snapshot.`);
3933
+ process.exit(1);
3934
+ }
3935
+ if (opts.json) {
3936
+ console.log(JSON.stringify({
3937
+ project: project.name,
3938
+ readiness: snap.readiness,
3939
+ actionItems: snap.actionItems,
3940
+ memoryCounts: snap.memoryCounts,
3941
+ latestStatus: snap.latestStatus ? { id: snap.latestStatus.id, title: snap.latestStatus.title, modified: snap.latestStatus.modified } : null,
3942
+ }, null, 2));
3943
+ return;
3944
+ }
3945
+ // Formatted output
3946
+ const r = snap.readiness;
3947
+ const color = r.score >= 90 ? "\x1b[32m" : r.score >= 65 ? "\x1b[34m" : r.score >= 40 ? "\x1b[33m" : "\x1b[31m";
3948
+ const reset = "\x1b[0m";
3949
+ console.log(`\n ${project.name} — ${color}${r.label} (${r.score}%)${reset}`);
3950
+ console.log(` ${snap.memoryCounts.total} memories across ${Object.keys(snap.memoryCounts.byCategory).length} categories\n`);
3951
+ if (snap.latestStatus) {
3952
+ const age = Math.floor((Date.now() - new Date(snap.latestStatus.modified).getTime()) / (1000 * 60 * 60 * 24));
3953
+ const stale = age > 7 ? ` \x1b[33m(${age}d old — consider running 'gnosys update-status')\x1b[0m` : ` (${age}d ago)`;
3954
+ console.log(` Last status: ${snap.latestStatus.title}${stale}\n`);
3955
+ }
3956
+ else {
3957
+ console.log(` \x1b[33mNo status snapshot found. Run 'gnosys update-status' to create one.\x1b[0m\n`);
3958
+ }
3959
+ // Action items
3960
+ if (snap.actionItems.length > 0) {
3961
+ console.log(` ACTION ITEMS (${snap.actionItems.length}):`);
3962
+ for (const a of snap.actionItems) {
3963
+ const icon = a.type === "question" ? "?" : a.type === "blocker" ? "!" : a.type === "manual" ? ">" : "*";
3964
+ console.log(` [${icon}] ${a.text}`);
3965
+ }
3966
+ console.log("");
3967
+ }
3968
+ // Blocking
3969
+ if (r.blocking.length > 0) {
3970
+ console.log(` BLOCKING GO-LIVE (${r.blocking.length}):`);
3971
+ for (const b of r.blocking.slice(0, 10)) {
3972
+ console.log(` - ${b}`);
3973
+ }
3974
+ if (r.blocking.length > 10)
3975
+ console.log(` ... and ${r.blocking.length - 10} more`);
3976
+ console.log("");
3977
+ }
3978
+ // Done summary
3979
+ if (r.done.length > 0) {
3980
+ console.log(` COMPLETED (${r.done.length} items)`);
3981
+ for (const d of r.done.slice(0, 5)) {
3982
+ console.log(` + ${d}`);
3983
+ }
3984
+ if (r.done.length > 5)
3985
+ console.log(` ... and ${r.done.length - 5} more`);
3986
+ console.log("");
3987
+ }
3988
+ // Suggest update if no status or stale
3989
+ if (!snap.latestStatus) {
3990
+ console.log(` Tip: Run 'gnosys update-status' to generate a status snapshot.`);
3991
+ }
3992
+ }
3993
+ catch (err) {
3994
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
3995
+ process.exit(1);
3996
+ }
3997
+ finally {
3998
+ centralDb?.close();
3999
+ }
4000
+ });
4001
+ // ─── gnosys update-status ────────────────────────────────────────────────
4002
+ program
4003
+ .command("update-status")
4004
+ .description("Show the prompt to give an AI agent to update this project's status for the portfolio dashboard")
4005
+ .option("-d, --directory <dir>", "Project directory (auto-detects if omitted)")
4006
+ .option("-p, --project <id>", "Project ID")
4007
+ .action(async (opts) => {
4008
+ let centralDb = null;
4009
+ try {
4010
+ centralDb = GnosysDB.openCentral();
4011
+ if (!centralDb.isAvailable()) {
4012
+ console.error("Central DB not available.");
4013
+ process.exit(1);
4014
+ }
4015
+ const { detectCurrentProject } = await import("./lib/federated.js");
4016
+ const { generateStatusPrompt } = await import("./lib/portfolio.js");
4017
+ let pid = opts.project || null;
4018
+ if (!pid)
4019
+ pid = await detectCurrentProject(centralDb, opts.directory || undefined);
4020
+ if (!pid) {
4021
+ console.error("No project specified and none detected.");
4022
+ process.exit(1);
4023
+ }
4024
+ const project = centralDb.getProject(pid);
4025
+ if (!project) {
4026
+ console.error(`Project not found: ${pid}`);
4027
+ process.exit(1);
4028
+ }
4029
+ const prompt = generateStatusPrompt(project.name, project.working_directory);
4030
+ console.log(prompt);
4031
+ }
4032
+ catch (err) {
4033
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
4034
+ process.exit(1);
4035
+ }
4036
+ finally {
4037
+ centralDb?.close();
4038
+ }
4039
+ });
3768
4040
  // ─── gnosys working-set ──────────────────────────────────────────────────
3769
4041
  program
3770
4042
  .command("working-set")