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 +10 -14
- package/dist/flow.js +188 -0
- package/dist/main.js +20 -75
- package/package.json +1 -1
- package/postinstall.mjs +2 -83
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
|
-
|
|
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
|
|
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
|
|
362
|
-
npx great-cto [init] [options] Detect + bootstrap
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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("
|
|
585
|
-
|
|
586
|
-
//
|
|
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(`
|
|
591
|
+
log(` ${dim("Alternatives: " + alternatives.join(", "))}`);
|
|
593
592
|
}
|
|
594
|
-
log(` ${dim("
|
|
595
|
-
log(
|
|
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
package/postinstall.mjs
CHANGED
|
@@ -1,86 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* great-cto postinstall —
|
|
4
|
-
*
|
|
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 */ }
|