great-cto 2.20.0 → 2.22.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/dist/bootstrap.js CHANGED
@@ -4,12 +4,13 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { dim, success, warn } from "./ui.js";
6
6
  import { suggestJurisdictions } from "./jurisdictions.js";
7
+ import { compileFlow, renderFlowMd } from "./flow.js";
7
8
  export function bootstrap(dir, detection, archetype, compliance, detectionMeta) {
8
9
  const greatCtoDir = join(dir, ".great_cto");
9
10
  const projectMd = join(greatCtoDir, "PROJECT.md");
10
11
  if (existsSync(projectMd)) {
11
12
  warn(`.great_cto/PROJECT.md already exists — not overwriting.`);
12
- return { projectMdPath: projectMd, created: false, skippedReason: "already exists" };
13
+ return { projectMdPath: projectMd, created: false, skippedReason: "already exists", flow: null };
13
14
  }
14
15
  mkdirSync(greatCtoDir, { recursive: true });
15
16
  const title = inferProjectTitle(dir);
@@ -67,18 +68,6 @@ jurisdiction: [${jurisdictionLine}]
67
68
  > Supported codes: eu · us · us-ca · uk · in · br · au · sg
68
69
  > See docs/jurisdiction-compliance.md for what each code activates.
69
70
 
70
- ## Leash
71
-
72
- leash:
73
- tenant_id: ${slugifyTenant(title)}
74
- daily_cap_usd: 10
75
- session_prefix: gcto
76
-
77
- > \`leash.tenant_id\` is sent as \`X-LLM-Leash-Tenant-Id\` on every LLM call
78
- > through the proxy. Board's Security tab scopes stats to the active project
79
- > via this id. Change here if multiple repos share the same logical project.
80
- > \`session_prefix\` is prepended to auto-generated session ids so logs are
81
- > easy to filter across machines.
82
71
 
83
72
  ## Memory & Query Rule
84
73
 
@@ -120,7 +109,14 @@ when you actually start work:
120
109
  `;
121
110
  writeFileSync(projectMd, content, "utf-8");
122
111
  success(`created .great_cto/PROJECT.md ${dim(`(archetype: ${archetype})`)}`);
123
- return { projectMdPath: projectMd, created: true, skippedReason: null };
112
+ // Write FLOW.md compiled delivery flow for agents and user
113
+ const confidence = detectionMeta?.confidence ?? "medium";
114
+ const size = (detection.projectSize ?? "medium");
115
+ const flow = compileFlow(archetype, size, detection, compliance, confidence);
116
+ const flowMdPath = join(greatCtoDir, "FLOW.md");
117
+ const generatedAt = new Date().toISOString().slice(0, 10);
118
+ writeFileSync(flowMdPath, renderFlowMd(flow, generatedAt), "utf-8");
119
+ return { projectMdPath: projectMd, created: true, skippedReason: null, flow };
124
120
  }
125
121
  /**
126
122
  * Slugify a project title into a tenant-id safe for HTTP headers and audit
package/dist/flow.js ADDED
@@ -0,0 +1,188 @@
1
+ // flow.ts — compiles all detection outputs into a single user-facing FlowResult.
2
+ // Pure function: no I/O, no side effects.
3
+ // Called by bootstrap.ts (writes FLOW.md) and main.ts (prints summary).
4
+ import { reviewersFor, gatesFor } from "./archetypes.js";
5
+ import { suggestPackReviewers, suggestPackGates, suggestPacks } from "./packs.js";
6
+ import { suggestJurisdictions, suggestJurisdictionReviewers, suggestJurisdictionGates } from "./jurisdictions.js";
7
+ // ── Human-readable titles ─────────────────────────────────────────────────
8
+ const ARCHETYPE_TITLE = {
9
+ "fintech": "Fintech",
10
+ "healthcare": "Healthcare",
11
+ "enterprise-saas": "Enterprise SaaS",
12
+ "agent-product": "AI agent",
13
+ "ai-system": "AI system",
14
+ "mlops": "MLOps pipeline",
15
+ "commerce": "E-commerce",
16
+ "marketplace": "Marketplace",
17
+ "mobile-app": "Mobile app",
18
+ "web-service": "Web service",
19
+ "library": "Library / SDK",
20
+ "cli-tool": "CLI tool",
21
+ "data-platform": "Data platform",
22
+ "streaming": "Streaming system",
23
+ "infra": "Infrastructure",
24
+ "devtools": "Developer tool",
25
+ "browser-extension": "Browser extension",
26
+ "game": "Game",
27
+ "web3": "Web3 / DeFi",
28
+ "iot-embedded": "IoT / embedded",
29
+ "cms": "CMS",
30
+ "edtech": "EdTech",
31
+ "gov-public": "Government",
32
+ "insurance": "Insurance",
33
+ "regulated": "Regulated system",
34
+ "greenfield": "New project",
35
+ };
36
+ // Gate id (StandardGate) → user label
37
+ const GATE_LABEL = {
38
+ "plan": "gate:plan",
39
+ "arch": "gate:arch",
40
+ "code": "gate:code",
41
+ "qa": "gate:qa",
42
+ "security": "gate:security",
43
+ "compliance": "gate:compliance",
44
+ "ship": "gate:ship",
45
+ "cost": "gate:cost-forecast",
46
+ "oracle-review": "gate:oracle-review",
47
+ "edtech-review": "gate:edtech-review",
48
+ "gov-review": "gate:gov-review",
49
+ "insurance-review": "gate:insurance-review",
50
+ };
51
+ // Cost (low, high) per feature cycle by archetype tier
52
+ const ARCHETYPE_COST = {
53
+ "fintech": [8, 18],
54
+ "healthcare": [8, 18],
55
+ "agent-product": [8, 18],
56
+ "mlops": [8, 18],
57
+ "marketplace": [8, 18],
58
+ "enterprise-saas": [8, 18],
59
+ "regulated": [8, 18],
60
+ "edtech": [8, 18],
61
+ "gov-public": [8, 18],
62
+ "insurance": [8, 18],
63
+ "web3": [8, 18],
64
+ "commerce": [3, 8],
65
+ "mobile-app": [3, 8],
66
+ "web-service": [3, 8],
67
+ "data-platform": [3, 8],
68
+ "streaming": [3, 8],
69
+ "devtools": [3, 8],
70
+ "browser-extension": [3, 8],
71
+ "game": [3, 8],
72
+ "cms": [3, 8],
73
+ "ai-system": [3, 8],
74
+ "iot-embedded": [3, 8],
75
+ "infra": [3, 8],
76
+ "library": [0.5, 3],
77
+ "cli-tool": [0.5, 3],
78
+ "greenfield": [0.5, 3],
79
+ };
80
+ // ── Main export ───────────────────────────────────────────────────────────
81
+ /**
82
+ * Compile all detection outputs into a single FlowResult.
83
+ *
84
+ * Pure function — no file I/O. Called by bootstrap.ts (FLOW.md) and
85
+ * main.ts (summary output).
86
+ */
87
+ export function compileFlow(archetype, size, detection, compliance, confidence) {
88
+ // ── Agents ──────────────────────────────────────────────────────────────
89
+ const agentSet = new Set(reviewersFor(archetype));
90
+ for (const r of suggestPackReviewers(detection))
91
+ agentSet.add(r);
92
+ for (const r of suggestJurisdictionReviewers(detection))
93
+ agentSet.add(r);
94
+ // Always include base orchestration agents
95
+ agentSet.add("architect");
96
+ agentSet.add("senior-dev");
97
+ agentSet.add("qa-engineer");
98
+ // ── Gates ────────────────────────────────────────────────────────────────
99
+ const gateSet = new Set(gatesFor(archetype, size).map((g) => GATE_LABEL[g] ?? `gate:${g}`));
100
+ for (const g of suggestPackGates(detection))
101
+ gateSet.add(g);
102
+ for (const g of suggestJurisdictionGates(detection))
103
+ gateSet.add(g);
104
+ // ── Packs + jurisdictions for routing block ──────────────────────────────
105
+ const packs = suggestPacks(detection);
106
+ const jurisdictions = suggestJurisdictions(detection);
107
+ // ── Title ────────────────────────────────────────────────────────────────
108
+ const productLabel = ARCHETYPE_TITLE[archetype] ?? archetype;
109
+ const jCodes = jurisdictions
110
+ .slice(0, 3)
111
+ .map((j) => j.jurisdiction.toUpperCase())
112
+ .join(" + ");
113
+ const title = jCodes ? `${productLabel} · ${jCodes}` : productLabel;
114
+ // ── ID ───────────────────────────────────────────────────────────────────
115
+ const id = [archetype, ...jurisdictions.map((j) => j.jurisdiction)]
116
+ .join("-")
117
+ .toLowerCase();
118
+ // ── Cost range ────────────────────────────────────────────────────────────
119
+ const costEntry = ARCHETYPE_COST[archetype] ?? [3, 8];
120
+ const [low, high] = costEntry;
121
+ return {
122
+ id,
123
+ title,
124
+ agents: Array.from(agentSet).sort(),
125
+ gates: Array.from(gateSet).sort(),
126
+ compliance: [...new Set(compliance)].sort(),
127
+ costRange: { low, high },
128
+ routing: {
129
+ archetype,
130
+ packs: packs.map((p) => p.pack),
131
+ jurisdictions: jurisdictions.map((j) => j.jurisdiction),
132
+ confidence,
133
+ },
134
+ };
135
+ }
136
+ /**
137
+ * Render FLOW.md content from a FlowResult.
138
+ * Exported separately so bootstrap.ts can call it without depending on main.ts.
139
+ */
140
+ export function renderFlowMd(flow, generatedAt) {
141
+ const agentLines = flow.agents.map((a) => `- ${a}`).join("\n");
142
+ const gateLines = flow.gates.map((g) => `- ${g}`).join("\n");
143
+ const complianceLines = flow.compliance.length > 0
144
+ ? flow.compliance.map((c) => `- ${c}`).join("\n")
145
+ : "- none";
146
+ const packLines = flow.routing.packs.length > 0
147
+ ? flow.routing.packs.join(", ")
148
+ : "none";
149
+ const jLines = flow.routing.jurisdictions.length > 0
150
+ ? flow.routing.jurisdictions.join(", ")
151
+ : "none";
152
+ return `# Delivery Flow
153
+
154
+ > Auto-generated by \`great-cto init\` on ${generatedAt}.
155
+ > This file tells agents how to orchestrate your SDLC.
156
+ > Regenerates on \`npx great-cto init --force\`. Edit \`_routing:\` to tune.
157
+
158
+ ## Detected
159
+
160
+ ${flow.title}
161
+
162
+ ## Agents
163
+
164
+ ${agentLines}
165
+
166
+ ## Human gates
167
+
168
+ ${gateLines}
169
+
170
+ ## Compliance
171
+
172
+ ${complianceLines}
173
+
174
+ ## Cost estimate
175
+
176
+ $${flow.costRange.low}–$${flow.costRange.high} per feature cycle
177
+
178
+ ---
179
+
180
+ <!-- Internal routing — view with: great-cto flow explain -->
181
+ _routing:
182
+ id: ${flow.id}
183
+ archetype: ${flow.routing.archetype}
184
+ packs: [${packLines}]
185
+ jurisdictions: [${jLines}]
186
+ confidence: ${flow.routing.confidence}
187
+ `;
188
+ }
package/dist/main.js CHANGED
@@ -18,11 +18,11 @@ import { install, findInstalledVersions } from "./installer.js";
18
18
  import { enableGreatCto } from "./settings.js";
19
19
  import { installAllCompanions } from "./companion.js";
20
20
  import { bootstrap } from "./bootstrap.js";
21
+ import { compileFlow } from "./flow.js";
21
22
  import { shouldUseLlmFallback, suggestArchetypeFromLlm } from "./llm-fallback.js";
22
23
  import { readFileSync, copyFileSync, chmodSync, existsSync as fsExistsSync } from "node:fs";
23
24
  import { dirname, join } from "node:path";
24
25
  import { fileURLToPath } from "node:url";
25
- import { homedir } from "node:os";
26
26
  function getCliVersion() {
27
27
  try {
28
28
  const here = dirname(fileURLToPath(import.meta.url));
@@ -97,8 +97,6 @@ function parseArgs(argv) {
97
97
  args.command = "webhook";
98
98
  else if (a === "report")
99
99
  args.command = "report";
100
- else if (a === "leash")
101
- args.command = "leash";
102
100
  else if (a === "upgrade")
103
101
  args.command = "upgrade";
104
102
  // Slash-commands surfaced as CLI subcommands so users get a clear hint
@@ -118,9 +116,7 @@ function parseArgs(argv) {
118
116
  else if (a === "--dir")
119
117
  args.dir = argv[++i] ?? args.dir;
120
118
  else if (a === "init" || a === "install" || a === "help" || a === "version") {
121
- // `install` is an alias for `init`. Both run the same flow; only
122
- // difference: `install` upgrades llm-leash to latest on every run,
123
- // while `init` is silent-skip when already installed.
119
+ // `install` is an alias for `init`. Both run the same flow.
124
120
  args.command = (a === "install" ? "init" : a);
125
121
  if (a === "install") {
126
122
  args._fromInstall = true;
@@ -358,8 +354,8 @@ function printHelp() {
358
354
  log(`${bold("great-cto")} — one-command install for the great_cto Claude Code plugin
359
355
 
360
356
  ${bold("Usage:")}
361
- npx great-cto install [options] Same as init; also upgrades llm-leash
362
- npx great-cto [init] [options] Detect + bootstrap; installs llm-leash if absent
357
+ npx great-cto install [options] Same as init
358
+ npx great-cto [init] [options] Detect + bootstrap
363
359
  npx great-cto board [--port 3141] [--no-open]
364
360
  npx great-cto register [--dir PATH]
365
361
  npx great-cto scan [path] [--severity LVL] [--scanner NAME] [--sarif FILE]
@@ -576,23 +572,26 @@ async function runInit(args) {
576
572
  }
577
573
  }
578
574
  const compliance = suggestCompliance(detection, archetype);
579
- log(` ${dim("archetype:")} ${cyan(archetype)} ${dim(`(confidence: ${confidence})`)}`);
580
- log(` ${dim("rationale:")} ${rationale}`);
581
- if (alternatives.length > 0) {
582
- log(` ${dim("alternatives:")} ${alternatives.join(", ")}`);
575
+ // Compile flow — used for user-facing summary AND written to FLOW.md by bootstrap()
576
+ const compiledFlow = compileFlow(archetype, (detection.projectSize ?? "medium"), detection, compliance, confidence);
577
+ // ── User-facing "Compiled flow" summary ──────────────────────────────────
578
+ log("");
579
+ log(`${bold("Compiled flow:")} ${cyan(compiledFlow.title)}`);
580
+ log(` ${dim("Agents:")} ${compiledFlow.agents.join(" · ")}`);
581
+ log(` ${dim("Gates:")} ${compiledFlow.gates.join(" · ")}`);
582
+ if (compiledFlow.compliance.length > 0) {
583
+ log(` ${dim("Compliance:")} ${compiledFlow.compliance.join(", ")}`);
583
584
  }
584
- log(` ${dim("suggested compliance:")} ${compliance.length > 0 ? compliance.join(", ") : "none"}`);
585
- // v1.0.144+: ask user to confirm archetype if confidence is low
586
- // OR if alternatives are present and not user-specified
585
+ log(` ${dim("Cost:")} ~$${compiledFlow.costRange.low}–$${compiledFlow.costRange.high} per feature cycle`);
586
+ log("");
587
+ // Low-confidence notice show only when actionable
587
588
  if (!args.yes && !args.archetype && (confidence === "low" || (confidence === "medium" && alternatives.length >= 2))) {
588
- log("");
589
- log(`${bold("⚠ Archetype detection confidence:")} ${cyan(confidence)}`);
590
- log(` Top candidate: ${cyan(archetype)} — ${dim(rationale)}`);
589
+ log(` ${yellow("")} ${dim(`Detected as ${cyan(archetype)} (${confidence} confidence).`)}`);
591
590
  if (alternatives.length > 0) {
592
- log(` Alternatives: ${alternatives.map(a => cyan(a)).join(", ")}`);
591
+ log(` ${dim("Alternatives: " + alternatives.join(", "))}`);
593
592
  }
594
- log(` ${dim("If wrong, override with: --archetype " + (alternatives[0] ?? "<name>"))}`);
595
- log(` ${dim("Or edit .great_cto/PROJECT.md after install — agents read 'archetype:' field.")}`);
593
+ log(` ${dim("Override: npx great-cto init --archetype <name>")}`);
594
+ log("");
596
595
  }
597
596
  // Confirmation
598
597
  if (!args.yes) {
@@ -796,12 +795,6 @@ async function runInit(args) {
796
795
  }
797
796
  // ── 6. install pre-push git hook ─────────────────────────
798
797
  installPrePushHook(args.dir);
799
- // ── 7. install / update llm-leash (runtime governance) ───
800
- // `init` is idempotent (silent skip when present). `install` always
801
- // upgrades to the latest commit on llm-leash main. Both best-effort:
802
- // missing git/python doesn't fail the flow.
803
- const fromInstall = args._fromInstall === true;
804
- await tryInstallLeash(fromInstall);
805
798
  // ── done ─────────────────────────────────────────────────
806
799
  log("");
807
800
  log(green(bold("✓ great_cto is ready.")));
@@ -847,41 +840,6 @@ function installPrePushHook(projectDir) {
847
840
  // Best-effort: hook failure must never block init
848
841
  }
849
842
  }
850
- /**
851
- * Best-effort llm-leash install — runs after bootstrap so every great-cto
852
- * init turns on runtime governance for free.
853
- *
854
- * forceUpdate=false (called from `init`) — silent-skip if installed
855
- * forceUpdate=true (called from `install`) — git pull + reinstall
856
- *
857
- * Never throws. Opt out via env: GREAT_CTO_SKIP_LEASH=1
858
- */
859
- async function tryInstallLeash(forceUpdate = false) {
860
- if (process.env.GREAT_CTO_SKIP_LEASH === "1") {
861
- log(` ${dim("skipped llm-leash install (GREAT_CTO_SKIP_LEASH=1)")}`);
862
- return;
863
- }
864
- try {
865
- const { runLeash } = await import("./leash.js");
866
- const { existsSync } = await import("node:fs");
867
- const installRoot = join(homedir(), ".great_cto", "llm-leash");
868
- if (existsSync(installRoot)) {
869
- if (forceUpdate) {
870
- log(` ${dim("updating llm-leash to latest …")}`);
871
- await runLeash(["update"]);
872
- }
873
- else {
874
- log(` ${dim("llm-leash already installed — skipped")}`);
875
- }
876
- return;
877
- }
878
- log(` ${dim("installing llm-leash (runtime governance) …")}`);
879
- await runLeash(["install"]);
880
- }
881
- catch {
882
- warn("llm-leash install failed — run `great-cto leash install` manually later");
883
- }
884
- }
885
843
  async function runUpgrade(rawArgv) {
886
844
  const { upgradePlugin, upgradeAll } = await import("./upgrade.js");
887
845
  const { COMPANION_PLUGINS } = await import("./companion.js");
@@ -1039,19 +997,6 @@ async function main() {
1039
997
  process.exit(2);
1040
998
  }
1041
999
  }
1042
- if (args.command === "leash") {
1043
- try {
1044
- const { runLeash } = await import("./leash.js");
1045
- // rawArgv[0] is "leash" — pass the rest as subcommand + flags
1046
- const leashArgs = rawArgv.slice(rawArgv.indexOf("leash") + 1);
1047
- const result = await runLeash(leashArgs);
1048
- process.exit(result.exitCode);
1049
- }
1050
- catch (e) {
1051
- error(e.message);
1052
- process.exit(2);
1053
- }
1054
- }
1055
1000
  if (args.command === "upgrade") {
1056
1001
  try {
1057
1002
  const code = await runUpgrade(rawArgv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "2.20.0",
3
+ "version": "2.22.0",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",
package/postinstall.mjs CHANGED
@@ -1,86 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * great-cto postinstall — best-effort one-time setup that runs when the npm
4
- * package is installed globally or as a dependency.
5
- *
6
- * Currently does one thing: install llm-leash (https://github.com/avelikiy/llm-leash)
7
- * for runtime governance — budget caps, audit log, kill switch, HITL gates.
8
- *
9
- * Design rules:
10
- * - NEVER fail the npm install. All errors swallowed.
11
- * - Idempotent — skips if ~/.great_cto/llm-leash already cloned.
12
- * - Honors GREAT_CTO_SKIP_LEASH=1 to opt out (CI envs, restricted machines).
13
- * - Skips on CI by default unless GREAT_CTO_FORCE_LEASH=1 — npm install in
14
- * CI shouldn't trigger 30s of git clone + pip install per build.
15
- * - Skips if `npm install` was invoked with --ignore-scripts (npm sets
16
- * `npm_config_ignore_scripts=true` — actually no, it just doesn't run
17
- * scripts; we can't detect it from inside).
18
- * - Detached output — postinstall noise is intentional and short.
19
- *
20
- * The "real" install path remains `great-cto leash install`. This hook just
21
- * makes the common case (one-shot `npm install -g great-cto`) feel zero-config.
3
+ * great-cto postinstall — no-op placeholder.
4
+ * Reserved for future setup steps; currently does nothing.
22
5
  */
23
-
24
- import { existsSync } from 'node:fs';
25
- import { spawnSync } from 'node:child_process';
26
- import { homedir } from 'node:os';
27
- import path from 'node:path';
28
-
29
- const INSTALL_ROOT = path.join(homedir(), '.great_cto', 'llm-leash');
30
-
31
- function main() {
32
- // ── opt-outs ─────────────────────────────────────────────────────────────
33
- if (process.env.GREAT_CTO_SKIP_LEASH === '1') {
34
- return; // silent
35
- }
36
-
37
- // Skip in CI unless explicitly forced — CI builds get no benefit from
38
- // having leash installed in the runner's home dir, and the latency hurts.
39
- const inCI = process.env.CI === 'true' || process.env.CI === '1';
40
- if (inCI && process.env.GREAT_CTO_FORCE_LEASH !== '1') {
41
- return;
42
- }
43
-
44
- // Already installed — fast exit
45
- if (existsSync(INSTALL_ROOT)) {
46
- return;
47
- }
48
-
49
- // Need git + python3 — bail silently if either is missing
50
- if (!hasCommand('git') || !hasCommand('python3')) {
51
- console.log('[great-cto] llm-leash skipped — git or python3 not on PATH');
52
- console.log('[great-cto] run `great-cto leash install` later to enable runtime governance');
53
- return;
54
- }
55
-
56
- // Locate the bundled dist/main.js — postinstall runs with cwd=package root
57
- const here = path.dirname(new URL(import.meta.url).pathname);
58
- const cli = path.join(here, 'dist', 'main.js');
59
- if (!existsSync(cli)) {
60
- return; // package built incorrectly — fail-safe
61
- }
62
-
63
- console.log('[great-cto] installing llm-leash for runtime governance (~30s) …');
64
- console.log('[great-cto] opt out next time: GREAT_CTO_SKIP_LEASH=1 npm install -g great-cto');
65
-
66
- const result = spawnSync(process.execPath, [cli, 'leash', 'install'], {
67
- stdio: 'inherit',
68
- timeout: 300_000,
69
- env: { ...process.env, NO_COLOR: process.env.NO_COLOR || '1' },
70
- });
71
-
72
- if (result.status !== 0) {
73
- console.log('[great-cto] llm-leash install hit an issue — run `great-cto leash install` later');
74
- }
75
- }
76
-
77
- function hasCommand(cmd) {
78
- try {
79
- const r = spawnSync(cmd, ['--version'], { stdio: 'ignore', timeout: 3000 });
80
- return r.status === 0;
81
- } catch {
82
- return false;
83
- }
84
- }
85
-
86
- try { main(); } catch { /* never fail npm install */ }